X-Git-Url: http://git.ipfire.org/?a=blobdiff_plain;f=cups%2Fhttp.c;h=b3abbe73e0d02e9951599dfc3a5bcd72a5053692;hb=8072030b3c862315c367c73663b27f0427325919;hp=83a7a79d5ba88968041832c8577c73fd215700df;hpb=82f972324ddde683be8a6d302f9937532ab0cf81;p=thirdparty%2Fcups.git diff --git a/cups/http.c b/cups/http.c index 83a7a79d5..b3abbe73e 100644 --- a/cups/http.c +++ b/cups/http.c @@ -1,119 +1,19 @@ /* - * "$Id: http.c 7850 2008-08-20 00:07:25Z mike $" + * HTTP routines for CUPS. * - * HTTP routines for CUPS. + * Copyright 2007-2015 by Apple Inc. + * Copyright 1997-2007 by Easy Software Products, all rights reserved. * - * Copyright 2007-2011 by Apple Inc. - * Copyright 1997-2007 by Easy Software Products, all rights reserved. + * This file contains Kerberos support code, copyright 2006 by + * Jelmer Vernooij. * - * This file contains Kerberos support code, copyright 2006 by - * Jelmer Vernooij. + * These coded instructions, statements, and computer programs are the + * property of Apple Inc. and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "LICENSE.txt" + * which should have been included with this file. If this file is + * file is missing or damaged, see the license at "http://www.cups.org/". * - * These coded instructions, statements, and computer programs are the - * property of Apple Inc. and are protected by Federal copyright - * law. Distribution and use rights are outlined in the file "LICENSE.txt" - * which should have been included with this file. If this file is - * file is missing or damaged, see the license at "http://www.cups.org/". - * - * This file is subject to the Apple OS-Developed Software exception. - * - * Contents: - * - * httpAddCredential() - Allocates and adds a single credential to an - * array. - * _httpBIOMethods() - Get the OpenSSL BIO methods for HTTP - * connections. - * httpBlocking() - Set blocking/non-blocking behavior on a - * connection. - * httpCheck() - Check to see if there is a pending response - * from the server. - * httpClearCookie() - Clear the cookie value(s). - * httpClearFields() - Clear HTTP request fields. - * httpClose() - Close an HTTP connection. - * httpConnect() - Connect to a HTTP server. - * httpConnectEncrypt() - Connect to a HTTP server using encryption. - * httpCopyCredentials() - Copy the credentials associated with an - * encrypted connection. - * _httpConvertCredentials() - Convert credentials to the internal format. - * _httpCreate() - Create an unconnected HTTP connection. - * httpDelete() - Send a DELETE request to the server. - * _httpDisconnect() - Disconnect a HTTP connection. - * httpEncryption() - Set the required encryption on the link. - * httpError() - Get the last error on a connection. - * httpFlush() - Flush data from a HTTP connection. - * httpFlushWrite() - Flush data in write buffer. - * _httpFreeCredentials() - Free internal credentials. - * httpFreeCredentials() - Free an array of credentials. - * httpGet() - Send a GET request to the server. - * httpGetAuthString() - Get the current authorization string. - * httpGetBlocking() - Get the blocking/non-block state of a - * connection. - * httpGetCookie() - Get any cookie data from the response. - * httpGetFd() - Get the file descriptor associated with a - * connection. - * httpGetField() - Get a field value from a request/response. - * httpGetLength() - Get the amount of data remaining from the - * content-length or transfer-encoding fields. - * httpGetLength2() - Get the amount of data remaining from the - * content-length or transfer-encoding fields. - * httpGetStatus() - Get the status of the last HTTP request. - * httpGetSubField() - Get a sub-field value. - * httpGetSubField2() - Get a sub-field value. - * httpGets() - Get a line of text from a HTTP connection. - * httpHead() - Send a HEAD request to the server. - * httpInitialize() - Initialize the HTTP interface library and set - * the default HTTP proxy (if any). - * httpOptions() - Send an OPTIONS request to the server. - * _httpPeek() - Peek at data from a HTTP connection. - * httpPost() - Send a POST request to the server. - * httpPrintf() - Print a formatted string to a HTTP connection. - * httpPut() - Send a PUT request to the server. - * httpRead() - Read data from a HTTP connection. - * httpRead2() - Read data from a HTTP connection. - * _httpReadCDSA() - Read function for the CDSA library. - * _httpReadGNUTLS() - Read function for the GNU TLS library. - * httpReconnect() - Reconnect to a HTTP server. - * httpSetAuthString() - Set the current authorization string. - * httpSetCredentials() - Set the credentials associated with an - * encrypted connection. - * httpSetCookie() - Set the cookie value(s). - * httpSetExpect() - Set the Expect: header in a request. - * httpSetField() - Set the value of an HTTP header. - * httpSetLength() - Set the content-length and content-encoding. - * _httpSetTimeout() - Set read/write timeouts and an optional - * callback. - * httpTrace() - Send an TRACE request to the server. - * _httpUpdate() - Update the current HTTP status for incoming - * data. - * httpUpdate() - Update the current HTTP state for incoming - * data. - * _httpWait() - Wait for data available on a connection (no - * flush). - * httpWait() - Wait for data available on a connection. - * httpWrite() - Write data to a HTTP connection. - * httpWrite2() - Write data to a HTTP connection. - * _httpWriteCDSA() - Write function for the CDSA library. - * _httpWriteGNUTLS() - Write function for the GNU TLS library. - * http_bio_ctrl() - Control the HTTP connection. - * http_bio_free() - Free OpenSSL data. - * http_bio_new() - Initialize an OpenSSL BIO structure. - * http_bio_puts() - Send a string for OpenSSL. - * http_bio_read() - Read data for OpenSSL. - * http_bio_write() - Write data for OpenSSL. - * http_debug_hex() - Do a hex dump of a buffer. - * http_field() - Return the field index for a field name. - * http_read_ssl() - Read from a SSL/TLS connection. - * http_locking_cb() - Lock/unlock a thread's mutex. - * http_send() - Send a request with all fields and the trailing - * blank line. - * http_set_credentials() - Set the SSL/TLS credentials. - * http_setup_ssl() - Set up SSL/TLS support on a connection. - * http_shutdown_ssl() - Shut down SSL/TLS on a connection. - * http_threadid_cb() - Return the current thread ID. - * http_upgrade() - Force upgrade to TLS encryption. - * http_write() - Write a buffer to a HTTP connection. - * http_write_chunk() - Write a chunked buffer. - * http_write_ssl() - Write to a SSL/TLS connection. + * This file is subject to the Apple OS-Developed Software exception. */ /* @@ -122,6 +22,7 @@ #include "cups-private.h" #include +#include #ifdef WIN32 # include #else @@ -130,57 +31,42 @@ # include #endif /* WIN32 */ #ifdef HAVE_POLL -# include +# include #endif /* HAVE_POLL */ -/* - * Some operating systems have done away with the Fxxxx constants for - * the fcntl() call; this works around that "feature"... - */ - -#ifndef FNONBLK -# define FNONBLK O_NONBLOCK -#endif /* !FNONBLK */ - - /* * Local functions... */ +#ifdef HAVE_LIBZ +static void http_content_coding_finish(http_t *http); +static void http_content_coding_start(http_t *http, + const char *value); +#endif /* HAVE_LIBZ */ +static http_t *http_create(const char *host, int port, + http_addrlist_t *addrlist, int family, + http_encryption_t encryption, + int blocking, _http_mode_t mode); #ifdef DEBUG static void http_debug_hex(const char *prefix, const char *buffer, int bytes); #endif /* DEBUG */ -static http_field_t http_field(const char *name); +static ssize_t http_read(http_t *http, char *buffer, size_t length); +static ssize_t http_read_buffered(http_t *http, char *buffer, size_t length); +static ssize_t http_read_chunk(http_t *http, char *buffer, size_t length); static int http_send(http_t *http, http_state_t request, const char *uri); -static int http_write(http_t *http, const char *buffer, - int length); -static int http_write_chunk(http_t *http, const char *buffer, - int length); +static ssize_t http_write(http_t *http, const char *buffer, + size_t length); +static ssize_t http_write_chunk(http_t *http, const char *buffer, + size_t length); +static off_t http_set_length(http_t *http); +static void http_set_timeout(int fd, double timeout); +static void http_set_wait(http_t *http); + #ifdef HAVE_SSL -static int http_read_ssl(http_t *http, char *buf, int len); -# if defined(HAVE_CDSASSL) && defined(HAVE_SECCERTIFICATECOPYDATA) -static int http_set_credentials(http_t *http); -# endif /* HAVE_CDSASSL ** HAVE_SECCERTIFICATECOPYDATA */ -static int http_setup_ssl(http_t *http); -static void http_shutdown_ssl(http_t *http); -static int http_upgrade(http_t *http); -static int http_write_ssl(http_t *http, const char *buf, int len); - -# ifdef HAVE_GNUTLS -# ifdef HAVE_PTHREAD_H -GCRY_THREAD_OPTION_PTHREAD_IMPL; -# endif /* HAVE_PTHREAD_H */ - -# elif defined(HAVE_LIBSSL) -static _cups_mutex_t *http_locks; /* OpenSSL lock mutexes */ - -static void http_locking_cb(int mode, int type, const char *file, - int line); -static unsigned long http_threadid_cb(void); -# endif /* HAVE_GNUTLS */ +static int http_tls_upgrade(http_t *http); #endif /* HAVE_SSL */ @@ -216,55 +102,100 @@ static const char * const http_fields[] = "Transfer-Encoding", "Upgrade", "User-Agent", - "WWW-Authenticate" - }; -#ifdef DEBUG -static const char * const http_states[] = - { - "HTTP_WAITING", - "HTTP_OPTIONS", - "HTTP_GET", - "HTTP_GET_SEND", - "HTTP_HEAD", - "HTTP_POST", - "HTTP_POST_RECV", - "HTTP_POST_SEND", - "HTTP_PUT", - "HTTP_PUT_RECV", - "HTTP_DELETE", - "HTTP_TRACE", - "HTTP_CLOSE", - "HTTP_STATUS" + "WWW-Authenticate", + "Accept-Encoding", + "Allow", + "Server" }; -#endif /* DEBUG */ -#if defined(HAVE_SSL) && defined(HAVE_LIBSSL) /* - * BIO methods for OpenSSL... + * 'httpAcceptConnection()' - Accept a new HTTP client connection from the + * specified listening socket. + * + * @since CUPS 1.7/macOS 10.9@ */ -static int http_bio_write(BIO *h, const char *buf, int num); -static int http_bio_read(BIO *h, char *buf, int size); -static int http_bio_puts(BIO *h, const char *str); -static long http_bio_ctrl(BIO *h, int cmd, long arg1, void *arg2); -static int http_bio_new(BIO *h); -static int http_bio_free(BIO *data); +http_t * /* O - HTTP connection or @code NULL@ */ +httpAcceptConnection(int fd, /* I - Listen socket file descriptor */ + int blocking) /* I - 1 if the connection should be + blocking, 0 otherwise */ +{ + http_t *http; /* HTTP connection */ + http_addrlist_t addrlist; /* Dummy address list */ + socklen_t addrlen; /* Length of address */ + int val; /* Socket option value */ -static BIO_METHOD http_bio_methods = - { - BIO_TYPE_SOCKET, - "http", - http_bio_write, - http_bio_read, - http_bio_puts, - NULL, /* http_bio_gets, */ - http_bio_ctrl, - http_bio_new, - http_bio_free, - NULL, - }; -#endif /* HAVE_SSL && HAVE_LIBSSL */ + + /* + * Range check input... + */ + + if (fd < 0) + return (NULL); + + /* + * Create the client connection... + */ + + memset(&addrlist, 0, sizeof(addrlist)); + + if ((http = http_create(NULL, 0, &addrlist, AF_UNSPEC, + HTTP_ENCRYPTION_IF_REQUESTED, blocking, + _HTTP_MODE_SERVER)) == NULL) + return (NULL); + + /* + * Accept the client and get the remote address... + */ + + addrlen = sizeof(http_addr_t); + + if ((http->fd = accept(fd, (struct sockaddr *)&(http->addrlist->addr), + &addrlen)) < 0) + { + _cupsSetHTTPError(HTTP_STATUS_ERROR); + httpClose(http); + + return (NULL); + } + + http->hostaddr = &(http->addrlist->addr); + + if (httpAddrLocalhost(http->hostaddr)) + strlcpy(http->hostname, "localhost", sizeof(http->hostname)); + else + httpAddrString(http->hostaddr, http->hostname, sizeof(http->hostname)); + +#ifdef SO_NOSIGPIPE + /* + * Disable SIGPIPE for this socket. + */ + + val = 1; + setsockopt(http->fd, SOL_SOCKET, SO_NOSIGPIPE, CUPS_SOCAST &val, sizeof(val)); +#endif /* SO_NOSIGPIPE */ + + /* + * Using TCP_NODELAY improves responsiveness, especially on systems + * with a slow loopback interface. Since we write large buffers + * when sending print files and requests, there shouldn't be any + * performance penalty for this... + */ + + val = 1; + setsockopt(http->fd, IPPROTO_TCP, TCP_NODELAY, CUPS_SOCAST &val, sizeof(val)); + +#ifdef FD_CLOEXEC + /* + * Close this socket when starting another process... + */ + + fcntl(http->fd, F_SETFD, FD_CLOEXEC); +#endif /* FD_CLOEXEC */ + + return (http); +} /* @@ -272,7 +203,7 @@ static BIO_METHOD http_bio_methods = * * Use @code cupsArrayNew(NULL, NULL)@ to create a credentials array. * - * @since CUPS 1.5@ + * @since CUPS 1.5/macOS 10.7@ */ int /* O - 0 on success, -1 on error */ @@ -302,29 +233,19 @@ httpAddCredential( } -#if defined(HAVE_SSL) && defined(HAVE_LIBSSL) -/* - * '_httpBIOMethods()' - Get the OpenSSL BIO methods for HTTP connections. - */ - -BIO_METHOD * /* O - BIO methods for OpenSSL */ -_httpBIOMethods(void) -{ - return (&http_bio_methods); -} -#endif /* HAVE_SSL && HAVE_LIBSSL */ - - /* * 'httpBlocking()' - Set blocking/non-blocking behavior on a connection. */ void -httpBlocking(http_t *http, /* I - Connection to server */ +httpBlocking(http_t *http, /* I - HTTP connection */ int b) /* I - 1 = blocking, 0 = non-blocking */ { if (http) + { http->blocking = b; + http_set_wait(http); + } } @@ -333,7 +254,7 @@ httpBlocking(http_t *http, /* I - Connection to server */ */ int /* O - 0 = no data, 1 = data available */ -httpCheck(http_t *http) /* I - Connection to server */ +httpCheck(http_t *http) /* I - HTTP connection */ { return (httpWait(http, 0)); } @@ -342,11 +263,11 @@ httpCheck(http_t *http) /* I - Connection to server */ /* * 'httpClearCookie()' - Clear the cookie value(s). * - * @since CUPS 1.1.19/Mac OS X 10.3@ + * @since CUPS 1.1.19/macOS 10.3@ */ void -httpClearCookie(http_t *http) /* I - Connection to server */ +httpClearCookie(http_t *http) /* I - HTTP connection */ { if (!http) return; @@ -364,15 +285,21 @@ httpClearCookie(http_t *http) /* I - Connection to server */ */ void -httpClearFields(http_t *http) /* I - Connection to server */ +httpClearFields(http_t *http) /* I - HTTP connection */ { + DEBUG_printf(("httpClearFields(http=%p)", (void *)http)); + if (http) { memset(http->fields, 0, sizeof(http->fields)); - if (http->hostname[0] == '/') - httpSetField(http, HTTP_FIELD_HOST, "localhost"); - else - httpSetField(http, HTTP_FIELD_HOST, http->hostname); + + if (http->mode == _HTTP_MODE_CLIENT) + { + if (http->hostname[0] == '/') + httpSetField(http, HTTP_FIELD_HOST, "localhost"); + else + httpSetField(http, HTTP_FIELD_HOST, http->hostname); + } if (http->field_authorization) { @@ -380,6 +307,24 @@ httpClearFields(http_t *http) /* I - Connection to server */ http->field_authorization = NULL; } + if (http->accept_encoding) + { + _cupsStrFree(http->accept_encoding); + http->accept_encoding = NULL; + } + + if (http->allow) + { + _cupsStrFree(http->allow); + http->allow = NULL; + } + + if (http->server) + { + _cupsStrFree(http->server); + http->server = NULL; + } + http->expect = (http_status_t)0; } } @@ -390,14 +335,14 @@ httpClearFields(http_t *http) /* I - Connection to server */ */ void -httpClose(http_t *http) /* I - Connection to server */ +httpClose(http_t *http) /* I - HTTP connection */ { #ifdef HAVE_GSSAPI OM_uint32 minor_status; /* Minor status code */ #endif /* HAVE_GSSAPI */ - DEBUG_printf(("httpClose(http=%p)", http)); + DEBUG_printf(("httpClose(http=%p)", (void *)http)); /* * Range check input... @@ -443,10 +388,34 @@ httpClose(http_t *http) /* I - Connection to server */ } +/* + * 'httpCompareCredentials()' - Compare two sets of X.509 credentials. + * + * @since CUPS 2.0/OS 10.10@ + */ + +int /* O - 1 if they match, 0 if they do not */ +httpCompareCredentials( + cups_array_t *cred1, /* I - First set of X.509 credentials */ + cups_array_t *cred2) /* I - Second set of X.509 credentials */ +{ + http_credential_t *temp1, *temp2; /* Temporary credentials */ + + + for (temp1 = (http_credential_t *)cupsArrayFirst(cred1), temp2 = (http_credential_t *)cupsArrayFirst(cred2); temp1 && temp2; temp1 = (http_credential_t *)cupsArrayNext(cred1), temp2 = (http_credential_t *)cupsArrayNext(cred2)) + if (temp1->datalen != temp2->datalen) + return (0); + else if (memcmp(temp1->data, temp2->data, temp1->datalen)) + return (0); + + return (temp1 == temp2); +} + + /* * 'httpConnect()' - Connect to a HTTP server. * - * This function is deprecated - use @link httpConnectEncrypt@ instead. + * This function is deprecated - use @link httpConnect2@ instead. * * @deprecated@ */ @@ -455,248 +424,78 @@ http_t * /* O - New HTTP connection */ httpConnect(const char *host, /* I - Host to connect to */ int port) /* I - Port number */ { - return (httpConnectEncrypt(host, port, HTTP_ENCRYPT_IF_REQUESTED)); + return (httpConnect2(host, port, NULL, AF_UNSPEC, + HTTP_ENCRYPTION_IF_REQUESTED, 1, 30000, NULL)); } /* - * 'httpConnectEncrypt()' - Connect to a HTTP server using encryption. + * 'httpConnect2()' - Connect to a HTTP server. + * + * @since CUPS 1.7/macOS 10.9@ */ http_t * /* O - New HTTP connection */ -httpConnectEncrypt( +httpConnect2( const char *host, /* I - Host to connect to */ int port, /* I - Port number */ - http_encryption_t encryption) /* I - Type of encryption to use */ + http_addrlist_t *addrlist, /* I - List of addresses or NULL to lookup */ + int family, /* I - Address family to use or @code AF_UNSPEC@ for any */ + http_encryption_t encryption, /* I - Type of encryption to use */ + int blocking, /* I - 1 for blocking connection, 0 for non-blocking */ + int msec, /* I - Connection timeout in milliseconds, 0 means don't connect */ + int *cancel) /* I - Pointer to "cancel" variable */ { http_t *http; /* New HTTP connection */ - DEBUG_printf(("httpConnectEncrypt(host=\"%s\", port=%d, encryption=%d)", - host, port, encryption)); + DEBUG_printf(("httpConnect2(host=\"%s\", port=%d, addrlist=%p, family=%d, encryption=%d, blocking=%d, msec=%d, cancel=%p)", host, port, (void *)addrlist, family, encryption, blocking, msec, (void *)cancel)); /* * Create the HTTP structure... */ - if ((http = _httpCreate(host, port, NULL, encryption, AF_UNSPEC)) == NULL) + if ((http = http_create(host, port, addrlist, family, encryption, blocking, + _HTTP_MODE_CLIENT)) == NULL) return (NULL); /* - * Connect to the remote system... + * Optionally connect to the remote system... */ - if (!httpReconnect(http)) + if (msec == 0 || !httpReconnect2(http, msec, cancel)) return (http); /* * Could not connect to any known address - bail out! */ - httpAddrFreeList(http->addrlist); - - free(http); + httpClose(http); return (NULL); } /* - * 'httpCopyCredentials()' - Copy the credentials associated with an encrypted - * connection. + * 'httpConnectEncrypt()' - Connect to a HTTP server using encryption. * - * @since CUPS 1.5@ - */ - -int /* O - Status of call (0 = success) */ -httpCopyCredentials( - http_t *http, /* I - Connection to server */ - cups_array_t **credentials) /* O - Array of credentials */ -{ -# ifdef HAVE_LIBSSL -# elif defined(HAVE_GNUTLS) -# elif defined(HAVE_CDSASSL) && defined(HAVE_SECCERTIFICATECOPYDATA) - OSStatus error; /* Error code */ - CFIndex count; /* Number of credentials */ - CFArrayRef peerCerts; /* Peer certificates */ - SecCertificateRef secCert; /* Certificate reference */ - CFDataRef data; /* Certificate data */ - int i; /* Looping var */ -# elif defined(HAVE_SSPISSL) -# endif /* HAVE_LIBSSL */ - - - if (credentials) - *credentials = NULL; - - if (!http || !http->tls || !credentials) - return (-1); - -# ifdef HAVE_LIBSSL - return (-1); - -# elif defined(HAVE_GNUTLS) - return (-1); - -# elif defined(HAVE_CDSASSL) && defined(HAVE_SECCERTIFICATECOPYDATA) - if (!(error = SSLCopyPeerCertificates(http->tls, &peerCerts)) && peerCerts) - { - if ((*credentials = cupsArrayNew(NULL, NULL)) != NULL) - { - for (i = 0, count = CFArrayGetCount(peerCerts); i < count; i++) - { - secCert = (SecCertificateRef)CFArrayGetValueAtIndex(peerCerts, i); - if ((data = SecCertificateCopyData(secCert))) - { - httpAddCredential(*credentials, CFDataGetBytePtr(data), - CFDataGetLength(data)); - CFRelease(data); - } - } - } - - CFRelease(peerCerts); - } - - return (error); - -# elif defined(HAVE_SSPISSL) - return (-1); - -# else - return (-1); -# endif /* HAVE_LIBSSL */ -} - - -/* - * '_httpConvertCredentials()' - Convert credentials to the internal format. - */ - -http_tls_credentials_t /* O - Internal credentials */ -_httpConvertCredentials( - cups_array_t *credentials) /* I - Array of credentials */ -{ - if (!credentials) - return (NULL); - -# ifdef HAVE_LIBSSL - return (NULL); - -# elif defined(HAVE_GNUTLS) - return (NULL); - -# elif defined(HAVE_CDSASSL) && defined(HAVE_SECCERTIFICATECOPYDATA) - CFMutableArrayRef peerCerts; /* Peer credentials reference */ - SecCertificateRef secCert; /* Certificate reference */ - CFDataRef data; /* Credential data reference */ - http_credential_t *credential; /* Credential data */ - - - if ((peerCerts = CFArrayCreateMutable(kCFAllocatorDefault, - cupsArrayCount(credentials), - &kCFTypeArrayCallBacks)) == NULL) - return (NULL); - - for (credential = (http_credential_t *)cupsArrayFirst(credentials); - credential; - credential = (http_credential_t *)cupsArrayNext(credentials)) - { - if ((data = CFDataCreate(kCFAllocatorDefault, credential->data, - credential->datalen))) - { - if ((secCert = SecCertificateCreateWithData(kCFAllocatorDefault, data)) - != NULL) - { - CFArrayAppendValue(peerCerts, secCert); - CFRelease(secCert); - } - - CFRelease(data); - } - } - - return (peerCerts); - -# elif defined(HAVE_SSPISSL) - return (NULL); - -# else - return (NULL); -# endif /* HAVE_LIBSSL */ -} - - -/* - * '_httpCreate()' - Create an unconnected HTTP connection. + * This function is now deprecated. Please use the @link httpConnect2@ function + * instead. + * + * @deprecated@ */ -http_t * /* O - HTTP connection */ -_httpCreate( - const char *host, /* I - Hostname */ +http_t * /* O - New HTTP connection */ +httpConnectEncrypt( + const char *host, /* I - Host to connect to */ int port, /* I - Port number */ - http_addrlist_t *addrlist, /* I - Address list or NULL */ - http_encryption_t encryption, /* I - Encryption to use */ - int family) /* I - Address family or AF_UNSPEC */ + http_encryption_t encryption) /* I - Type of encryption to use */ { - http_t *http; /* New HTTP connection */ - char service[255]; /* Service name */ - - - DEBUG_printf(("4_httpCreate(host=\"%s\", port=%d, encryption=%d)", + DEBUG_printf(("httpConnectEncrypt(host=\"%s\", port=%d, encryption=%d)", host, port, encryption)); - if (!host) - return (NULL); - - httpInitialize(); - - /* - * Lookup the host... - */ - - sprintf(service, "%d", port); - - if (!addrlist) - if ((addrlist = httpAddrGetList(host, family, service)) == NULL) - return (NULL); - - /* - * Allocate memory for the structure... - */ - - if ((http = calloc(sizeof(http_t), 1)) == NULL) - { - httpAddrFreeList(addrlist); - return (NULL); - } - - /* - * Initialize the HTTP data... - */ - - http->activity = time(NULL); - http->addrlist = addrlist; - http->blocking = 1; - http->fd = -1; -#ifdef HAVE_GSSAPI - http->gssctx = GSS_C_NO_CONTEXT; - http->gssname = GSS_C_NO_NAME; -#endif /* HAVE_GSSAPI */ - http->version = HTTP_1_1; - - strlcpy(http->hostname, host, sizeof(http->hostname)); - - if (port == 443) /* Always use encryption for https */ - http->encryption = HTTP_ENCRYPT_ALWAYS; - else - http->encryption = encryption; - - /* - * Return the new structure... - */ - - return (http); + return (httpConnect2(host, port, NULL, AF_UNSPEC, encryption, 1, 30000, + NULL)); } @@ -705,10 +504,10 @@ _httpCreate( */ int /* O - Status of call (0 = success) */ -httpDelete(http_t *http, /* I - Connection to server */ +httpDelete(http_t *http, /* I - HTTP connection */ const char *uri) /* I - URI to delete */ { - return (http_send(http, HTTP_DELETE, uri)); + return (http_send(http, HTTP_STATE_DELETE, uri)); } @@ -717,18 +516,14 @@ httpDelete(http_t *http, /* I - Connection to server */ */ void -_httpDisconnect(http_t *http) /* I - Connection to server */ +_httpDisconnect(http_t *http) /* I - HTTP connection */ { #ifdef HAVE_SSL if (http->tls) - http_shutdown_ssl(http); + _httpTLSStop(http); #endif /* HAVE_SSL */ -#ifdef WIN32 - closesocket(http->fd); -#else - close(http->fd); -#endif /* WIN32 */ + httpAddrClose(NULL, http->fd); http->fd = -1; } @@ -739,26 +534,40 @@ _httpDisconnect(http_t *http) /* I - Connection to server */ */ int /* O - -1 on error, 0 on success */ -httpEncryption(http_t *http, /* I - Connection to server */ +httpEncryption(http_t *http, /* I - HTTP connection */ http_encryption_t e) /* I - New encryption preference */ { - DEBUG_printf(("httpEncryption(http=%p, e=%d)", http, e)); + DEBUG_printf(("httpEncryption(http=%p, e=%d)", (void *)http, e)); #ifdef HAVE_SSL if (!http) return (0); - http->encryption = e; + if (http->mode == _HTTP_MODE_CLIENT) + { + http->encryption = e; - if ((http->encryption == HTTP_ENCRYPT_ALWAYS && !http->tls) || - (http->encryption == HTTP_ENCRYPT_NEVER && http->tls)) - return (httpReconnect(http)); - else if (http->encryption == HTTP_ENCRYPT_REQUIRED && !http->tls) - return (http_upgrade(http)); + if ((http->encryption == HTTP_ENCRYPTION_ALWAYS && !http->tls) || + (http->encryption == HTTP_ENCRYPTION_NEVER && http->tls)) + return (httpReconnect2(http, 30000, NULL)); + else if (http->encryption == HTTP_ENCRYPTION_REQUIRED && !http->tls) + return (http_tls_upgrade(http)); + else + return (0); + } else - return (0); + { + if (e == HTTP_ENCRYPTION_NEVER && http->tls) + return (-1); + + http->encryption = e; + if (e != HTTP_ENCRYPTION_IF_REQUESTED && !http->tls) + return (_httpTLSStart(http)); + else + return (0); + } #else - if (e == HTTP_ENCRYPT_ALWAYS || e == HTTP_ENCRYPT_REQUIRED) + if (e == HTTP_ENCRYPTION_ALWAYS || e == HTTP_ENCRYPTION_REQUIRED) return (-1); else return (0); @@ -771,7 +580,7 @@ httpEncryption(http_t *http, /* I - Connection to server */ */ int /* O - Error code (errno) value */ -httpError(http_t *http) /* I - Connection to server */ +httpError(http_t *http) /* I - HTTP connection */ { if (http) return (http->error); @@ -780,20 +589,45 @@ httpError(http_t *http) /* I - Connection to server */ } +/* + * 'httpFieldValue()' - Return the HTTP field enumeration value for a field + * name. + */ + +http_field_t /* O - Field index */ +httpFieldValue(const char *name) /* I - String name */ +{ + int i; /* Looping var */ + + + for (i = 0; i < HTTP_FIELD_MAX; i ++) + if (!_cups_strcasecmp(name, http_fields[i])) + return ((http_field_t)i); + + return (HTTP_FIELD_UNKNOWN); +} + + /* * 'httpFlush()' - Flush data from a HTTP connection. */ void -httpFlush(http_t *http) /* I - Connection to server */ +httpFlush(http_t *http) /* I - HTTP connection */ { char buffer[8192]; /* Junk buffer */ int blocking; /* To block or not to block */ http_state_t oldstate; /* Old state */ - DEBUG_printf(("httpFlush(http=%p), state=%s", http, - http_states[http->state])); + DEBUG_printf(("httpFlush(http=%p), state=%s", (void *)http, httpStateString(http->state))); + + /* + * Nothing to do if we are in the "waiting" state... + */ + + if (http->state == HTTP_STATE_WAITING) + return; /* * Temporarily set non-blocking mode so we don't get stuck in httpRead()... @@ -816,24 +650,28 @@ httpFlush(http_t *http) /* I - Connection to server */ http->blocking = blocking; - if (http->state == oldstate && http->state != HTTP_WAITING && http->fd >= 0) + if (http->state == oldstate && http->state != HTTP_STATE_WAITING && + http->fd >= 0) { /* * Didn't get the data back, so close the current connection. */ - http->state = HTTP_WAITING; +#ifdef HAVE_LIBZ + if (http->coding) + http_content_coding_finish(http); +#endif /* HAVE_LIBZ */ + + DEBUG_puts("1httpFlush: Setting state to HTTP_STATE_WAITING and closing."); + + http->state = HTTP_STATE_WAITING; #ifdef HAVE_SSL if (http->tls) - http_shutdown_ssl(http); + _httpTLSStop(http); #endif /* HAVE_SSL */ -#ifdef WIN32 - closesocket(http->fd); -#else - close(http->fd); -#endif /* WIN32 */ + httpAddrClose(NULL, http->fd); http->fd = -1; } @@ -843,16 +681,16 @@ httpFlush(http_t *http) /* I - Connection to server */ /* * 'httpFlushWrite()' - Flush data in write buffer. * - * @since CUPS 1.2/Mac OS X 10.5@ + * @since CUPS 1.2/macOS 10.5@ */ int /* O - Bytes written or -1 on error */ -httpFlushWrite(http_t *http) /* I - Connection to server */ +httpFlushWrite(http_t *http) /* I - HTTP connection */ { - int bytes; /* Bytes written */ + ssize_t bytes; /* Bytes written */ - DEBUG_printf(("httpFlushWrite(http=%p)", http)); + DEBUG_printf(("httpFlushWrite(http=%p) data_encoding=%d", (void *)http, http ? http->data_encoding : 100)); if (!http || !http->wused) { @@ -861,43 +699,16 @@ httpFlushWrite(http_t *http) /* I - Connection to server */ return (0); } - if (http->data_encoding == HTTP_ENCODE_CHUNKED) - bytes = http_write_chunk(http, http->wbuffer, http->wused); + if (http->data_encoding == HTTP_ENCODING_CHUNKED) + bytes = http_write_chunk(http, http->wbuffer, (size_t)http->wused); else - bytes = http_write(http, http->wbuffer, http->wused); + bytes = http_write(http, http->wbuffer, (size_t)http->wused); http->wused = 0; - DEBUG_printf(("1httpFlushWrite: Returning %d.", bytes)); - - return (bytes); -} - - -/* - * '_httpFreeCredentials()' - Free internal credentials. - */ - -void -_httpFreeCredentials( - http_tls_credentials_t credentials) /* I - Internal credentials */ -{ - if (!credentials) - return; - -#ifdef HAVE_LIBSSL - (void)credentials; - -#elif defined(HAVE_GNUTLS) - (void)credentials; - -#elif defined(HAVE_CDSASSL) - CFRelease(credentials); + DEBUG_printf(("1httpFlushWrite: Returning %d, errno=%d.", (int)bytes, errno)); -#elif defined(HAVE_SSPISSL) - (void)credentials; - -#endif /* HAVE_LIBSSL */ + return ((int)bytes); } @@ -930,10 +741,25 @@ httpFreeCredentials( */ int /* O - Status of call (0 = success) */ -httpGet(http_t *http, /* I - Connection to server */ +httpGet(http_t *http, /* I - HTTP connection */ const char *uri) /* I - URI to get */ { - return (http_send(http, HTTP_GET, uri)); + return (http_send(http, HTTP_STATE_GET, uri)); +} + + +/* + * 'httpGetActivity()' - Get the most recent activity for a connection. + * + * The return value is the UNIX time of the last read or write. + * + * @since CUPS 2.0/OS 10.10@ + */ + +time_t /* O - Time of last read or write */ +httpGetActivity(http_t *http) /* I - HTTP connection */ +{ + return (http ? http->activity : 0); } @@ -945,11 +771,11 @@ httpGet(http_t *http, /* I - Connection to server */ * string to use with httpSetField() for the HTTP_FIELD_AUTHORIZATION * value. * - * @since CUPS 1.3/Mac OS X 10.5@ + * @since CUPS 1.3/macOS 10.5@ */ char * /* O - Authorization string */ -httpGetAuthString(http_t *http) /* I - Connection to server */ +httpGetAuthString(http_t *http) /* I - HTTP connection */ { if (http) return (http->authstring); @@ -961,37 +787,161 @@ httpGetAuthString(http_t *http) /* I - Connection to server */ /* * 'httpGetBlocking()' - Get the blocking/non-block state of a connection. * - * @since CUPS 1.2/Mac OS X 10.5@ + * @since CUPS 1.2/macOS 10.5@ */ int /* O - 1 if blocking, 0 if non-blocking */ -httpGetBlocking(http_t *http) /* I - Connection to server */ +httpGetBlocking(http_t *http) /* I - HTTP connection */ { return (http ? http->blocking : 0); } +/* + * 'httpGetContentEncoding()' - Get a common content encoding, if any, between + * the client and server. + * + * This function uses the value of the Accepts-Encoding HTTP header and must be + * called after receiving a response from the server or a request from the + * client. The value returned can be use in subsequent requests (for clients) + * or in the response (for servers) in order to compress the content stream. + * + * @since CUPS 1.7/macOS 10.9@ + */ + +const char * /* O - Content-Coding value or + @code NULL@ for the identity + coding. */ +httpGetContentEncoding(http_t *http) /* I - HTTP connection */ +{ +#ifdef HAVE_LIBZ + if (http && http->accept_encoding) + { + int i; /* Looping var */ + char temp[HTTP_MAX_VALUE], /* Copy of Accepts-Encoding value */ + *start, /* Start of coding value */ + *end; /* End of coding value */ + double qvalue; /* "qvalue" for coding */ + struct lconv *loc = localeconv(); /* Locale data */ + static const char * const codings[] = + { /* Supported content codings */ + "deflate", + "gzip", + "x-deflate", + "x-gzip" + }; + + strlcpy(temp, http->accept_encoding, sizeof(temp)); + + for (start = temp; *start; start = end) + { + /* + * Find the end of the coding name... + */ + + qvalue = 1.0; + end = start; + while (*end && *end != ';' && *end != ',' && !isspace(*end & 255)) + end ++; + + if (*end == ';') + { + /* + * Grab the qvalue as needed... + */ + + if (!strncmp(end, ";q=", 3)) + qvalue = _cupsStrScand(end + 3, NULL, loc); + + /* + * Skip past all attributes... + */ + + *end++ = '\0'; + while (*end && *end != ',' && !isspace(*end & 255)) + end ++; + } + else if (*end) + *end++ = '\0'; + + while (*end && isspace(*end & 255)) + end ++; + + /* + * Check value if it matches something we support... + */ + + if (qvalue <= 0.0) + continue; + + for (i = 0; i < (int)(sizeof(codings) / sizeof(codings[0])); i ++) + if (!strcmp(start, codings[i])) + return (codings[i]); + } + } +#endif /* HAVE_LIBZ */ + + return (NULL); +} + + /* * 'httpGetCookie()' - Get any cookie data from the response. * - * @since CUPS 1.1.19/Mac OS X 10.3@ + * @since CUPS 1.1.19/macOS 10.3@ */ const char * /* O - Cookie data or NULL */ -httpGetCookie(http_t *http) /* I - HTTP connecion */ +httpGetCookie(http_t *http) /* I - HTTP connection */ { return (http ? http->cookie : NULL); } +/* + * 'httpGetEncryption()' - Get the current encryption mode of a connection. + * + * This function returns the encryption mode for the connection. Use the + * @link httpIsEncrypted@ function to determine whether a TLS session has + * been established. + * + * @since CUPS 2.0/OS 10.10@ + */ + +http_encryption_t /* O - Current encryption mode */ +httpGetEncryption(http_t *http) /* I - HTTP connection */ +{ + return (http ? http->encryption : HTTP_ENCRYPTION_IF_REQUESTED); +} + + +/* + * 'httpGetExpect()' - Get the value of the Expect header, if any. + * + * Returns @code HTTP_STATUS_NONE@ if there is no Expect header, otherwise + * returns the expected HTTP status code, typically @code HTTP_STATUS_CONTINUE@. + * + * @since CUPS 1.7/macOS 10.9@ + */ + +http_status_t /* O - Expect: status, if any */ +httpGetExpect(http_t *http) /* I - HTTP connection */ +{ + if (!http) + return (HTTP_STATUS_ERROR); + else + return (http->expect); +} + + /* * 'httpGetFd()' - Get the file descriptor associated with a connection. * - * @since CUPS 1.2/Mac OS X 10.5@ + * @since CUPS 1.2/macOS 10.5@ */ int /* O - File descriptor or -1 if none */ -httpGetFd(http_t *http) /* I - Connection to server */ +httpGetFd(http_t *http) /* I - HTTP connection */ { return (http ? http->fd : -1); } @@ -1002,23 +952,50 @@ httpGetFd(http_t *http) /* I - Connection to server */ */ const char * /* O - Field value */ -httpGetField(http_t *http, /* I - Connection to server */ +httpGetField(http_t *http, /* I - HTTP connection */ http_field_t field) /* I - Field to get */ { if (!http || field <= HTTP_FIELD_UNKNOWN || field >= HTTP_FIELD_MAX) return (NULL); - else if (field == HTTP_FIELD_AUTHORIZATION && - http->field_authorization) + + switch (field) { - /* - * Special case for WWW-Authenticate: as its contents can be - * longer than HTTP_MAX_VALUE... - */ + case HTTP_FIELD_ACCEPT_ENCODING : + return (http->accept_encoding); + + case HTTP_FIELD_ALLOW : + return (http->allow); + + case HTTP_FIELD_SERVER : + return (http->server); - return (http->field_authorization); + case HTTP_FIELD_AUTHORIZATION : + if (http->field_authorization) + { + /* + * Special case for WWW-Authenticate: as its contents can be + * longer than HTTP_MAX_VALUE... + */ + + return (http->field_authorization); + } + + default : + return (http->fields[field]); } - else - return (http->fields[field]); +} + + +/* + * 'httpGetKeepAlive()' - Get the current Keep-Alive state of the connection. + * + * @since CUPS 2.0/OS 10.10@ + */ + +http_keepalive_t /* O - Keep-Alive state */ +httpGetKeepAlive(http_t *http) /* I - HTTP connection */ +{ + return (http ? http->keep_alive : HTTP_KEEPALIVE_OFF); } @@ -1033,7 +1010,7 @@ httpGetField(http_t *http, /* I - Connection to server */ */ int /* O - Content length */ -httpGetLength(http_t *http) /* I - Connection to server */ +httpGetLength(http_t *http) /* I - HTTP connection */ { /* * Get the read content length and return the 32-bit value. @@ -1057,224 +1034,115 @@ httpGetLength(http_t *http) /* I - Connection to server */ * This function returns the complete content length, even for * content larger than 2^31 - 1. * - * @since CUPS 1.2/Mac OS X 10.5@ + * @since CUPS 1.2/macOS 10.5@ */ off_t /* O - Content length */ -httpGetLength2(http_t *http) /* I - Connection to server */ +httpGetLength2(http_t *http) /* I - HTTP connection */ { - DEBUG_printf(("2httpGetLength2(http=%p), state=%s", http, - http_states[http->state])); + off_t remaining; /* Remaining length */ + + + DEBUG_printf(("2httpGetLength2(http=%p), state=%s", (void *)http, httpStateString(http->state))); if (!http) return (-1); - if (!strcasecmp(http->fields[HTTP_FIELD_TRANSFER_ENCODING], "chunked")) + if (!_cups_strcasecmp(http->fields[HTTP_FIELD_TRANSFER_ENCODING], "chunked")) { DEBUG_puts("4httpGetLength2: chunked request!"); - - http->data_encoding = HTTP_ENCODE_CHUNKED; - http->data_remaining = 0; + remaining = 0; } else { - http->data_encoding = HTTP_ENCODE_LENGTH; - /* * The following is a hack for HTTP servers that don't send a - * content-length or transfer-encoding field... + * Content-Length or Transfer-Encoding field... * - * If there is no content-length then the connection must close + * If there is no Content-Length then the connection must close * after the transfer is complete... */ if (!http->fields[HTTP_FIELD_CONTENT_LENGTH][0]) { /* - * Default content length is 0 for errors and 2^31-1 for other - * successful requests... + * Default content length is 0 for errors and certain types of operations, + * and 2^31-1 for other successful requests... */ - if (http->status >= HTTP_MULTIPLE_CHOICES) - http->data_remaining = 0; + if (http->status >= HTTP_STATUS_MULTIPLE_CHOICES || + http->state == HTTP_STATE_OPTIONS || + (http->state == HTTP_STATE_GET && http->mode == _HTTP_MODE_SERVER) || + http->state == HTTP_STATE_HEAD || + (http->state == HTTP_STATE_PUT && http->mode == _HTTP_MODE_CLIENT) || + http->state == HTTP_STATE_DELETE || + http->state == HTTP_STATE_TRACE || + http->state == HTTP_STATE_CONNECT) + remaining = 0; else - http->data_remaining = 2147483647; + remaining = 2147483647; } - else - http->data_remaining = strtoll(http->fields[HTTP_FIELD_CONTENT_LENGTH], - NULL, 10); + else if ((remaining = strtoll(http->fields[HTTP_FIELD_CONTENT_LENGTH], + NULL, 10)) < 0) + remaining = -1; DEBUG_printf(("4httpGetLength2: content_length=" CUPS_LLFMT, - CUPS_LLCAST http->data_remaining)); + CUPS_LLCAST remaining)); } - if (http->data_remaining <= INT_MAX) - http->_data_remaining = (int)http->data_remaining; - else - http->_data_remaining = INT_MAX; - - return (http->data_remaining); + return (remaining); } /* - * 'httpGetStatus()' - Get the status of the last HTTP request. + * 'httpGetPending()' - Get the number of bytes that are buffered for writing. * - * @since CUPS 1.2/Mac OS X 10.5@ + * @since CUPS 2.0/OS 10.10@ */ -http_status_t /* O - HTTP status */ -httpGetStatus(http_t *http) /* I - Connection to server */ +size_t /* O - Number of bytes buffered */ +httpGetPending(http_t *http) /* I - HTTP connection */ { - return (http ? http->status : HTTP_ERROR); + return (http ? (size_t)http->wused : 0); } /* - * 'httpGetSubField()' - Get a sub-field value. + * 'httpGetReady()' - Get the number of bytes that can be read without blocking. * - * @deprecated@ + * @since CUPS 2.0/OS 10.10@ */ -char * /* O - Value or NULL */ -httpGetSubField(http_t *http, /* I - Connection to server */ - http_field_t field, /* I - Field index */ - const char *name, /* I - Name of sub-field */ - char *value) /* O - Value string */ +size_t /* O - Number of bytes available */ +httpGetReady(http_t *http) /* I - HTTP connection */ { - return (httpGetSubField2(http, field, name, value, HTTP_MAX_VALUE)); + if (!http) + return (0); + else if (http->used > 0) + return ((size_t)http->used); +#ifdef HAVE_SSL + else if (http->tls) + return (_httpTLSPending(http)); +#endif /* HAVE_SSL */ + + return (0); } /* - * 'httpGetSubField2()' - Get a sub-field value. + * 'httpGetRemaining()' - Get the number of remaining bytes in the message + * body or current chunk. * - * @since CUPS 1.2/Mac OS X 10.5@ + * The @link httpIsChunked@ function can be used to determine whether the + * message body is chunked or fixed-length. + * + * @since CUPS 2.0/OS 10.10@ */ -char * /* O - Value or NULL */ -httpGetSubField2(http_t *http, /* I - Connection to server */ - http_field_t field, /* I - Field index */ - const char *name, /* I - Name of sub-field */ - char *value, /* O - Value string */ - int valuelen) /* I - Size of value buffer */ +size_t /* O - Remaining bytes */ +httpGetRemaining(http_t *http) /* I - HTTP connection */ { - const char *fptr; /* Pointer into field */ - char temp[HTTP_MAX_VALUE], /* Temporary buffer for name */ - *ptr, /* Pointer into string buffer */ - *end; /* End of value buffer */ - - DEBUG_printf(("2httpGetSubField2(http=%p, field=%d, name=\"%s\", value=%p, " - "valuelen=%d)", http, field, name, value, valuelen)); - - if (!http || !name || !value || valuelen < 2 || - field <= HTTP_FIELD_UNKNOWN || field >= HTTP_FIELD_MAX) - return (NULL); - - end = value + valuelen - 1; - - for (fptr = http->fields[field]; *fptr;) - { - /* - * Skip leading whitespace... - */ - - while (_cups_isspace(*fptr)) - fptr ++; - - if (*fptr == ',') - { - fptr ++; - continue; - } - - /* - * Get the sub-field name... - */ - - for (ptr = temp; - *fptr && *fptr != '=' && !_cups_isspace(*fptr) && - ptr < (temp + sizeof(temp) - 1); - *ptr++ = *fptr++); - - *ptr = '\0'; - - DEBUG_printf(("4httpGetSubField2: name=\"%s\"", temp)); - - /* - * Skip trailing chars up to the '='... - */ - - while (_cups_isspace(*fptr)) - fptr ++; - - if (!*fptr) - break; - - if (*fptr != '=') - continue; - - /* - * Skip = and leading whitespace... - */ - - fptr ++; - - while (_cups_isspace(*fptr)) - fptr ++; - - if (*fptr == '\"') - { - /* - * Read quoted string... - */ - - for (ptr = value, fptr ++; - *fptr && *fptr != '\"' && ptr < end; - *ptr++ = *fptr++); - - *ptr = '\0'; - - while (*fptr && *fptr != '\"') - fptr ++; - - if (*fptr) - fptr ++; - } - else - { - /* - * Read unquoted string... - */ - - for (ptr = value; - *fptr && !_cups_isspace(*fptr) && *fptr != ',' && ptr < end; - *ptr++ = *fptr++); - - *ptr = '\0'; - - while (*fptr && !_cups_isspace(*fptr) && *fptr != ',') - fptr ++; - } - - DEBUG_printf(("4httpGetSubField2: value=\"%s\"", value)); - - /* - * See if this is the one... - */ - - if (!strcmp(name, temp)) - { - DEBUG_printf(("3httpGetSubField2: Returning \"%s\"", value)); - return (value); - } - } - - value[0] = '\0'; - - DEBUG_puts("3httpGetSubField2: Returning NULL"); - - return (NULL); + return (http ? (size_t)http->data_remaining : 0); } @@ -1285,19 +1153,19 @@ httpGetSubField2(http_t *http, /* I - Connection to server */ char * /* O - Line or NULL */ httpGets(char *line, /* I - Line to read into */ int length, /* I - Max length of buffer */ - http_t *http) /* I - Connection to server */ + http_t *http) /* I - HTTP connection */ { - char *lineptr, /* Pointer into line */ - *lineend, /* End of line */ - *bufptr, /* Pointer into input buffer */ - *bufend; /* Pointer to end of buffer */ - int bytes, /* Number of bytes read */ - eol; /* End-of-line? */ + char *lineptr, /* Pointer into line */ + *lineend, /* End of line */ + *bufptr, /* Pointer into input buffer */ + *bufend; /* Pointer to end of buffer */ + ssize_t bytes; /* Number of bytes read */ + int eol; /* End-of-line? */ - DEBUG_printf(("2httpGets(line=%p, length=%d, http=%p)", line, length, http)); + DEBUG_printf(("2httpGets(line=%p, length=%d, http=%p)", (void *)line, length, (void *)http)); - if (http == NULL || line == NULL) + if (!http || !line || length <= 1) return (NULL); /* @@ -1327,8 +1195,11 @@ httpGets(char *line, /* I - Line to read into */ * No newline; see if there is more data to be read... */ - if (!http->blocking && !_httpWait(http, 10000, 1)) + while (!_httpWait(http, http->wait_value, 1)) { + if (http->timeout_cb && (*http->timeout_cb)(http, http->timeout_data)) + continue; + DEBUG_puts("3httpGets: Timed out!"); #ifdef WIN32 http->error = WSAETIMEDOUT; @@ -1338,16 +1209,9 @@ httpGets(char *line, /* I - Line to read into */ return (NULL); } -#ifdef HAVE_SSL - if (http->tls) - bytes = http_read_ssl(http, http->buffer + http->used, - HTTP_MAX_BUFFER - http->used); - else -#endif /* HAVE_SSL */ - bytes = recv(http->fd, http->buffer + http->used, - HTTP_MAX_BUFFER - http->used, 0); + bytes = http_read(http, http->buffer + http->used, (size_t)(HTTP_MAX_BUFFER - http->used)); - DEBUG_printf(("4httpGets: read %d bytes...", bytes)); + DEBUG_printf(("4httpGets: read " CUPS_LLFMT " bytes.", CUPS_LLCAST bytes)); if (bytes < 0) { @@ -1407,7 +1271,7 @@ httpGets(char *line, /* I - Line to read into */ * Yup, update the amount used... */ - http->used += bytes; + http->used += (int)bytes; } /* @@ -1431,7 +1295,7 @@ httpGets(char *line, /* I - Line to read into */ http->used -= (int)(bufptr - http->buffer); if (http->used > 0) - memmove(http->buffer, bufptr, http->used); + memmove(http->buffer, bufptr, (size_t)http->used); if (eol) { @@ -1456,15 +1320,197 @@ httpGets(char *line, /* I - Line to read into */ /* - * 'httpHead()' - Send a HEAD request to the server. + * 'httpGetState()' - Get the current state of the HTTP request. */ -int /* O - Status of call (0 = success) */ -httpHead(http_t *http, /* I - Connection to server */ - const char *uri) /* I - URI for head */ +http_state_t /* O - HTTP state */ +httpGetState(http_t *http) /* I - HTTP connection */ { - DEBUG_printf(("httpHead(http=%p, uri=\"%s\")", http, uri)); - return (http_send(http, HTTP_HEAD, uri)); + return (http ? http->state : HTTP_STATE_ERROR); +} + + +/* + * 'httpGetStatus()' - Get the status of the last HTTP request. + * + * @since CUPS 1.2/macOS 10.5@ + */ + +http_status_t /* O - HTTP status */ +httpGetStatus(http_t *http) /* I - HTTP connection */ +{ + return (http ? http->status : HTTP_STATUS_ERROR); +} + + +/* + * 'httpGetSubField()' - Get a sub-field value. + * + * @deprecated@ + */ + +char * /* O - Value or NULL */ +httpGetSubField(http_t *http, /* I - HTTP connection */ + http_field_t field, /* I - Field index */ + const char *name, /* I - Name of sub-field */ + char *value) /* O - Value string */ +{ + return (httpGetSubField2(http, field, name, value, HTTP_MAX_VALUE)); +} + + +/* + * 'httpGetSubField2()' - Get a sub-field value. + * + * @since CUPS 1.2/macOS 10.5@ + */ + +char * /* O - Value or NULL */ +httpGetSubField2(http_t *http, /* I - HTTP connection */ + http_field_t field, /* I - Field index */ + const char *name, /* I - Name of sub-field */ + char *value, /* O - Value string */ + int valuelen) /* I - Size of value buffer */ +{ + const char *fptr; /* Pointer into field */ + char temp[HTTP_MAX_VALUE], /* Temporary buffer for name */ + *ptr, /* Pointer into string buffer */ + *end; /* End of value buffer */ + + DEBUG_printf(("2httpGetSubField2(http=%p, field=%d, name=\"%s\", value=%p, valuelen=%d)", (void *)http, field, name, (void *)value, valuelen)); + + if (!http || !name || !value || valuelen < 2 || + field <= HTTP_FIELD_UNKNOWN || field >= HTTP_FIELD_MAX) + return (NULL); + + end = value + valuelen - 1; + + for (fptr = http->fields[field]; *fptr;) + { + /* + * Skip leading whitespace... + */ + + while (_cups_isspace(*fptr)) + fptr ++; + + if (*fptr == ',') + { + fptr ++; + continue; + } + + /* + * Get the sub-field name... + */ + + for (ptr = temp; + *fptr && *fptr != '=' && !_cups_isspace(*fptr) && + ptr < (temp + sizeof(temp) - 1); + *ptr++ = *fptr++); + + *ptr = '\0'; + + DEBUG_printf(("4httpGetSubField2: name=\"%s\"", temp)); + + /* + * Skip trailing chars up to the '='... + */ + + while (_cups_isspace(*fptr)) + fptr ++; + + if (!*fptr) + break; + + if (*fptr != '=') + continue; + + /* + * Skip = and leading whitespace... + */ + + fptr ++; + + while (_cups_isspace(*fptr)) + fptr ++; + + if (*fptr == '\"') + { + /* + * Read quoted string... + */ + + for (ptr = value, fptr ++; + *fptr && *fptr != '\"' && ptr < end; + *ptr++ = *fptr++); + + *ptr = '\0'; + + while (*fptr && *fptr != '\"') + fptr ++; + + if (*fptr) + fptr ++; + } + else + { + /* + * Read unquoted string... + */ + + for (ptr = value; + *fptr && !_cups_isspace(*fptr) && *fptr != ',' && ptr < end; + *ptr++ = *fptr++); + + *ptr = '\0'; + + while (*fptr && !_cups_isspace(*fptr) && *fptr != ',') + fptr ++; + } + + DEBUG_printf(("4httpGetSubField2: value=\"%s\"", value)); + + /* + * See if this is the one... + */ + + if (!strcmp(name, temp)) + { + DEBUG_printf(("3httpGetSubField2: Returning \"%s\"", value)); + return (value); + } + } + + value[0] = '\0'; + + DEBUG_puts("3httpGetSubField2: Returning NULL"); + + return (NULL); +} + + +/* + * 'httpGetVersion()' - Get the HTTP version at the other end. + */ + +http_version_t /* O - Version number */ +httpGetVersion(http_t *http) /* I - HTTP connection */ +{ + return (http ? http->version : HTTP_VERSION_1_0); +} + + +/* + * 'httpHead()' - Send a HEAD request to the server. + */ + +int /* O - Status of call (0 = success) */ +httpHead(http_t *http, /* I - HTTP connection */ + const char *uri) /* I - URI for head */ +{ + DEBUG_printf(("httpHead(http=%p, uri=\"%s\")", (void *)http, uri)); + return (http_send(http, HTTP_STATE_HEAD, uri)); } @@ -1480,10 +1526,6 @@ httpInitialize(void) #ifdef WIN32 WSADATA winsockdata; /* WinSock data */ #endif /* WIN32 */ -#ifdef HAVE_LIBSSL - int i; /* Looping var */ - unsigned char data[1024]; /* Seed data */ -#endif /* HAVE_LIBSSL */ _cupsGlobalLock(); @@ -1517,57 +1559,43 @@ httpInitialize(void) # endif /* !SO_NOSIGPIPE */ #endif /* WIN32 */ -#ifdef HAVE_GNUTLS - /* - * Make sure we handle threading properly... - */ - -# ifdef HAVE_PTHREAD_H - gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); -# endif /* HAVE_PTHREAD_H */ - - /* - * Initialize GNU TLS... - */ - - gnutls_global_init(); +# ifdef HAVE_SSL + _httpTLSInitialize(); +# endif /* HAVE_SSL */ -#elif defined(HAVE_LIBSSL) - /* - * Initialize OpenSSL... - */ - - SSL_load_error_strings(); - SSL_library_init(); - - /* - * Set the threading callbacks... - */ - - http_locks = calloc(CRYPTO_num_locks(), sizeof(_cups_mutex_t)); -# ifdef HAVE_PTHREAD_H - for (i = 0; i < CRYPTO_num_locks(); i ++) - pthread_mutex_init(http_locks + i, NULL); -# endif /* HAVE_PTHREAD_H */ + initialized = 1; + _cupsGlobalUnlock(); +} - CRYPTO_set_id_callback(http_threadid_cb); - CRYPTO_set_locking_callback(http_locking_cb); - /* - * Using the current time is a dubious random seed, but on some systems - * it is the best we can do (on others, this seed isn't even used...) - */ +/* + * 'httpIsChunked()' - Report whether a message body is chunked. + * + * This function returns non-zero if the message body is composed of + * variable-length chunks. + * + * @since CUPS 2.0/OS 10.10@ + */ - CUPS_SRAND(time(NULL)); +int /* O - 1 if chunked, 0 if not */ +httpIsChunked(http_t *http) /* I - HTTP connection */ +{ + return (http ? http->data_encoding == HTTP_ENCODING_CHUNKED : 0); +} - for (i = 0; i < sizeof(data); i ++) - data[i] = CUPS_RAND(); - RAND_seed(data, sizeof(data)); -#endif /* HAVE_GNUTLS */ +/* + * 'httpIsEncrypted()' - Report whether a connection is encrypted. + * + * This function returns non-zero if the connection is currently encrypted. + * + * @since CUPS 2.0/OS 10.10@ + */ - initialized = 1; - _cupsGlobalUnlock(); +int /* O - 1 if encrypted, 0 if not */ +httpIsEncrypted(http_t *http) /* I - HTTP connection */ +{ + return (http ? http->tls != NULL : 0); } @@ -1576,34 +1604,35 @@ httpInitialize(void) */ int /* O - Status of call (0 = success) */ -httpOptions(http_t *http, /* I - Connection to server */ +httpOptions(http_t *http, /* I - HTTP connection */ const char *uri) /* I - URI for options */ { - return (http_send(http, HTTP_OPTIONS, uri)); + return (http_send(http, HTTP_STATE_OPTIONS, uri)); } /* - * '_httpPeek()' - Peek at data from a HTTP connection. + * 'httpPeek()' - Peek at data from a HTTP connection. * * This function copies available data from the given HTTP connection, reading * a buffer as needed. The data is still available for reading using * @link httpRead@ or @link httpRead2@. * * For non-blocking connections the usual timeouts apply. + * + * @since CUPS 1.7/macOS 10.9@ */ ssize_t /* O - Number of bytes copied */ -_httpPeek(http_t *http, /* I - Connection to server */ - char *buffer, /* I - Buffer for data */ - size_t length) /* I - Maximum number of bytes */ +httpPeek(http_t *http, /* I - HTTP connection */ + char *buffer, /* I - Buffer for data */ + size_t length) /* I - Maximum number of bytes */ { ssize_t bytes; /* Bytes read */ char len[32]; /* Length string */ - DEBUG_printf(("_httpPeek(http=%p, buffer=%p, length=" CUPS_LLFMT ")", - http, buffer, CUPS_LLCAST length)); + DEBUG_printf(("httpPeek(http=%p, buffer=%p, length=" CUPS_LLFMT ")", (void *)http, (void *)buffer, CUPS_LLCAST length)); if (http == NULL || buffer == NULL) return (-1); @@ -1614,108 +1643,191 @@ _httpPeek(http_t *http, /* I - Connection to server */ if (length <= 0) return (0); - if (http->data_encoding == HTTP_ENCODE_CHUNKED && + if (http->data_encoding == HTTP_ENCODING_CHUNKED && http->data_remaining <= 0) { - DEBUG_puts("2_httpPeek: Getting chunk length..."); + DEBUG_puts("2httpPeek: Getting chunk length..."); if (httpGets(len, sizeof(len), http) == NULL) { - DEBUG_puts("1_httpPeek: Could not get length!"); + DEBUG_puts("1httpPeek: Could not get length!"); return (0); } + if (!len[0]) + { + DEBUG_puts("1httpPeek: Blank chunk length, trying again..."); + if (!httpGets(len, sizeof(len), http)) + { + DEBUG_puts("1httpPeek: Could not get chunk length."); + return (0); + } + } + http->data_remaining = strtoll(len, NULL, 16); + if (http->data_remaining < 0) { - DEBUG_puts("1_httpPeek: Negative chunk length!"); + DEBUG_puts("1httpPeek: Negative chunk length!"); return (0); } } - DEBUG_printf(("2_httpPeek: data_remaining=" CUPS_LLFMT, + DEBUG_printf(("2httpPeek: data_remaining=" CUPS_LLFMT, CUPS_LLCAST http->data_remaining)); - if (http->data_remaining <= 0) + if (http->data_remaining <= 0 && http->data_encoding != HTTP_ENCODING_FIELDS) { /* * A zero-length chunk ends a transfer; unless we are reading POST * data, go idle... */ - if (http->data_encoding == HTTP_ENCODE_CHUNKED) +#ifdef HAVE_LIBZ + if (http->coding >= _HTTP_CODING_GUNZIP) + http_content_coding_finish(http); +#endif /* HAVE_LIBZ */ + + if (http->data_encoding == HTTP_ENCODING_CHUNKED) httpGets(len, sizeof(len), http); - if (http->state == HTTP_POST_RECV) + if (http->state == HTTP_STATE_POST_RECV) http->state ++; else - http->state = HTTP_WAITING; + http->state = HTTP_STATE_STATUS; + + DEBUG_printf(("1httpPeek: 0-length chunk, set state to %s.", + httpStateString(http->state))); /* * Prevent future reads for this request... */ - http->data_encoding = HTTP_ENCODE_LENGTH; + http->data_encoding = HTTP_ENCODING_FIELDS; return (0); } else if (length > (size_t)http->data_remaining) length = (size_t)http->data_remaining; +#ifdef HAVE_LIBZ + if (http->used == 0 && + (http->coding == _HTTP_CODING_IDENTITY || + (http->coding >= _HTTP_CODING_GUNZIP && http->stream.avail_in == 0))) +#else if (http->used == 0) +#endif /* HAVE_LIBZ */ { /* * Buffer small reads for better performance... */ - if (!http->blocking && !httpWait(http, 10000)) - return (0); + ssize_t buflen; /* Length of read for buffer */ - if (http->data_remaining > sizeof(http->buffer)) - bytes = sizeof(http->buffer); - else - bytes = http->data_remaining; + if (!http->blocking) + { + while (!httpWait(http, http->wait_value)) + { + if (http->timeout_cb && (*http->timeout_cb)(http, http->timeout_data)) + continue; -#ifdef HAVE_SSL - if (http->tls) - bytes = http_read_ssl(http, http->buffer, bytes); + return (0); + } + } + + if ((size_t)http->data_remaining > sizeof(http->buffer)) + buflen = sizeof(http->buffer); else -#endif /* HAVE_SSL */ + buflen = (ssize_t)http->data_remaining; + + DEBUG_printf(("2httpPeek: Reading %d bytes into buffer.", (int)buflen)); + bytes = http_read(http, http->buffer, (size_t)buflen); + + DEBUG_printf(("2httpPeek: Read " CUPS_LLFMT " bytes into buffer.", + CUPS_LLCAST bytes)); + if (bytes > 0) + { +#ifdef DEBUG + http_debug_hex("httpPeek", http->buffer, (int)bytes); +#endif /* DEBUG */ + + http->used = (int)bytes; + } + } + +#ifdef HAVE_LIBZ + if (http->coding >= _HTTP_CODING_GUNZIP) + { +# ifdef HAVE_INFLATECOPY + int zerr; /* Decompressor error */ + z_stream stream; /* Copy of decompressor stream */ + + if (http->used > 0 && http->stream.avail_in < HTTP_MAX_BUFFER) { - DEBUG_printf(("2_httpPeek: reading %d bytes from socket into buffer...", - (int)bytes)); + size_t buflen = buflen = HTTP_MAX_BUFFER - http->stream.avail_in; + /* Number of bytes to copy */ + + if (http->stream.avail_in > 0 && + http->stream.next_in > http->sbuffer) + memmove(http->sbuffer, http->stream.next_in, http->stream.avail_in); - bytes = recv(http->fd, http->buffer, bytes, 0); + http->stream.next_in = http->sbuffer; - DEBUG_printf(("2_httpPeek: read %d bytes from socket into buffer...", - (int)bytes)); + if (buflen > (size_t)http->data_remaining) + buflen = (size_t)http->data_remaining; + + if (buflen > (size_t)http->used) + buflen = (size_t)http->used; + + DEBUG_printf(("1httpPeek: Copying %d more bytes of data into " + "decompression buffer.", (int)buflen)); + + memcpy(http->sbuffer + http->stream.avail_in, http->buffer, buflen); + http->stream.avail_in += buflen; + http->used -= (int)buflen; + http->data_remaining -= (off_t)buflen; + + if (http->used > 0) + memmove(http->buffer, http->buffer + buflen, (size_t)http->used); } - if (bytes > 0) - http->used = bytes; - else if (bytes < 0) + DEBUG_printf(("2httpPeek: length=%d, avail_in=%d", (int)length, + (int)http->stream.avail_in)); + + if (inflateCopy(&stream, &(http->stream)) != Z_OK) { -#ifdef WIN32 - if (WSAGetLastError() != WSAEINTR && WSAGetLastError() != WSAEWOULDBLOCK) - { - http->error = WSAGetLastError(); - return (-1); - } -#else - if (errno != EINTR && errno != EAGAIN) - { - http->error = errno; - return (-1); - } -#endif /* WIN32 */ + DEBUG_puts("2httpPeek: Unable to copy decompressor stream."); + http->error = ENOMEM; + return (-1); } - else + + stream.next_out = (Bytef *)buffer; + stream.avail_out = (uInt)length; + + zerr = inflate(&stream, Z_SYNC_FLUSH); + inflateEnd(&stream); + + if (zerr < Z_OK) { - http->error = EPIPE; - return (0); + DEBUG_printf(("2httpPeek: zerr=%d", zerr)); +#ifdef DEBUG + http_debug_hex("2httpPeek", (char *)http->sbuffer, (int)http->stream.avail_in); +#endif /* DEBUG */ + + http->error = EIO; + return (-1); } - } + bytes = (ssize_t)(length - http->stream.avail_out); + +# else + DEBUG_puts("2httpPeek: No inflateCopy on this platform, httpPeek does not " + "work with compressed streams."); + return (-1); +# endif /* HAVE_INFLATECOPY */ + } + else +#endif /* HAVE_LIBZ */ if (http->used > 0) { if (length > (size_t)http->used) @@ -1723,7 +1835,7 @@ _httpPeek(http_t *http, /* I - Connection to server */ bytes = (ssize_t)length; - DEBUG_printf(("2_httpPeek: grabbing %d bytes from input buffer...", + DEBUG_printf(("2httpPeek: grabbing %d bytes from input buffer...", (int)bytes)); memcpy(buffer, http->buffer, length); @@ -1751,10 +1863,6 @@ _httpPeek(http_t *http, /* I - Connection to server */ return (0); } -#ifdef DEBUG - http_debug_hex("_httpPeek", buffer, (int)bytes); -#endif /* DEBUG */ - return (bytes); } @@ -1764,10 +1872,10 @@ _httpPeek(http_t *http, /* I - Connection to server */ */ int /* O - Status of call (0 = success) */ -httpPost(http_t *http, /* I - Connection to server */ +httpPost(http_t *http, /* I - HTTP connection */ const char *uri) /* I - URI for post */ { - return (http_send(http, HTTP_POST, uri)); + return (http_send(http, HTTP_STATE_POST, uri)); } @@ -1778,25 +1886,25 @@ httpPost(http_t *http, /* I - Connection to server */ */ int /* O - Number of bytes written */ -httpPrintf(http_t *http, /* I - Connection to server */ +httpPrintf(http_t *http, /* I - HTTP connection */ const char *format, /* I - printf-style format string */ ...) /* I - Additional args as needed */ { - int bytes; /* Number of bytes to write */ + ssize_t bytes; /* Number of bytes to write */ char buf[16384]; /* Buffer for formatted string */ va_list ap; /* Variable argument pointer */ - DEBUG_printf(("2httpPrintf(http=%p, format=\"%s\", ...)", http, format)); + DEBUG_printf(("2httpPrintf(http=%p, format=\"%s\", ...)", (void *)http, format)); va_start(ap, format); bytes = vsnprintf(buf, sizeof(buf), format, ap); va_end(ap); - DEBUG_printf(("3httpPrintf: %s", buf)); + DEBUG_printf(("3httpPrintf: (" CUPS_LLFMT " bytes) %s", CUPS_LLCAST bytes, buf)); - if (http->data_encoding == HTTP_ENCODE_FIELDS) - return (httpWrite2(http, buf, bytes)); + if (http->data_encoding == HTTP_ENCODING_FIELDS) + return ((int)httpWrite2(http, buf, (size_t)bytes)); else { if (http->wused) @@ -1807,7 +1915,7 @@ httpPrintf(http_t *http, /* I - Connection to server */ return (-1); } - return (http_write(http, buf, bytes)); + return ((int)http_write(http, buf, (size_t)bytes)); } } @@ -1817,11 +1925,11 @@ httpPrintf(http_t *http, /* I - Connection to server */ */ int /* O - Status of call (0 = success) */ -httpPut(http_t *http, /* I - Connection to server */ +httpPut(http_t *http, /* I - HTTP connection */ const char *uri) /* I - URI to put */ { - DEBUG_printf(("httpPut(http=%p, uri=\"%s\")", http, uri)); - return (http_send(http, HTTP_PUT, uri)); + DEBUG_printf(("httpPut(http=%p, uri=\"%s\")", (void *)http, uri)); + return (http_send(http, HTTP_STATE_PUT, uri)); } @@ -1835,381 +1943,410 @@ httpPut(http_t *http, /* I - Connection to server */ */ int /* O - Number of bytes read */ -httpRead(http_t *http, /* I - Connection to server */ +httpRead(http_t *http, /* I - HTTP connection */ char *buffer, /* I - Buffer for data */ int length) /* I - Maximum number of bytes */ { - return ((int)httpRead2(http, buffer, length)); + return ((int)httpRead2(http, buffer, (size_t)length)); } /* * 'httpRead2()' - Read data from a HTTP connection. * - * @since CUPS 1.2/Mac OS X 10.5@ + * @since CUPS 1.2/macOS 10.5@ */ ssize_t /* O - Number of bytes read */ -httpRead2(http_t *http, /* I - Connection to server */ +httpRead2(http_t *http, /* I - HTTP connection */ char *buffer, /* I - Buffer for data */ size_t length) /* I - Maximum number of bytes */ { ssize_t bytes; /* Bytes read */ - char len[32]; /* Length string */ - DEBUG_printf(("httpRead2(http=%p, buffer=%p, length=" CUPS_LLFMT ")", - http, buffer, CUPS_LLCAST length)); +#ifdef HAVE_LIBZ + DEBUG_printf(("httpRead2(http=%p, buffer=%p, length=" CUPS_LLFMT ") coding=%d data_encoding=%d data_remaining=" CUPS_LLFMT, (void *)http, (void *)buffer, CUPS_LLCAST length, http->coding, http->data_encoding, CUPS_LLCAST http->data_remaining)); +#else + DEBUG_printf(("httpRead2(http=%p, buffer=%p, length=" CUPS_LLFMT ") data_encoding=%d data_remaining=" CUPS_LLFMT, (void *)http, (void *)buffer, CUPS_LLCAST length, http->data_encoding, CUPS_LLCAST http->data_remaining)); +#endif /* HAVE_LIBZ */ if (http == NULL || buffer == NULL) return (-1); http->activity = time(NULL); - http->error = 0; - - if (length <= 0) - return (0); - - if (http->data_encoding == HTTP_ENCODE_CHUNKED && - http->data_remaining <= 0) - { - DEBUG_puts("2httpRead2: Getting chunk length..."); - - if (httpGets(len, sizeof(len), http) == NULL) - { - DEBUG_puts("1httpRead2: Could not get length!"); - return (0); - } - - http->data_remaining = strtoll(len, NULL, 16); - if (http->data_remaining < 0) - { - DEBUG_puts("1httpRead2: Negative chunk length!"); - return (0); - } - } - - DEBUG_printf(("2httpRead2: data_remaining=" CUPS_LLFMT, - CUPS_LLCAST http->data_remaining)); - - if (http->data_remaining <= 0) - { - /* - * A zero-length chunk ends a transfer; unless we are reading POST - * data, go idle... - */ - - if (http->data_encoding == HTTP_ENCODE_CHUNKED) - httpGets(len, sizeof(len), http); - - if (http->state == HTTP_POST_RECV) - http->state ++; - else - http->state = HTTP_WAITING; - - /* - * Prevent future reads for this request... - */ - - http->data_encoding = HTTP_ENCODE_LENGTH; - - return (0); - } - else if (length > (size_t)http->data_remaining) - length = (size_t)http->data_remaining; - - if (http->used == 0 && length <= 256) - { - /* - * Buffer small reads for better performance... - */ - - if (!http->blocking && !httpWait(http, 10000)) - return (0); + http->error = 0; - if (http->data_remaining > sizeof(http->buffer)) - bytes = sizeof(http->buffer); - else - bytes = http->data_remaining; + if (length <= 0) + return (0); -#ifdef HAVE_SSL - if (http->tls) - bytes = http_read_ssl(http, http->buffer, bytes); - else -#endif /* HAVE_SSL */ +#ifdef HAVE_LIBZ + if (http->coding >= _HTTP_CODING_GUNZIP) + { + do { - DEBUG_printf(("2httpRead2: reading %d bytes from socket into buffer...", - (int)bytes)); + if (http->stream.avail_in > 0) + { + int zerr; /* Decompressor error */ - bytes = recv(http->fd, http->buffer, bytes, 0); + DEBUG_printf(("2httpRead2: avail_in=%d, avail_out=%d", + (int)http->stream.avail_in, (int)length)); - DEBUG_printf(("2httpRead2: read %d bytes from socket into buffer...", - (int)bytes)); - } + http->stream.next_out = (Bytef *)buffer; + http->stream.avail_out = (uInt)length; - if (bytes > 0) - http->used = bytes; - else if (bytes < 0) - { -#ifdef WIN32 - if (WSAGetLastError() != WSAEINTR) - { - http->error = WSAGetLastError(); - return (-1); - } - else if (WSAGetLastError() == WSAEWOULDBLOCK) - { - if (!http->timeout_cb || !(*http->timeout_cb)(http, http->timeout_data)) - { - http->error = WSAEWOULDBLOCK; - return (-1); - } - } -#else - if (errno == EWOULDBLOCK || errno == EAGAIN) - { - if (http->timeout_cb && !(*http->timeout_cb)(http, http->timeout_data)) - { - http->error = errno; - return (-1); - } - else if (!http->timeout_cb && errno != EAGAIN) + if ((zerr = inflate(&(http->stream), Z_SYNC_FLUSH)) < Z_OK) { - http->error = errno; + DEBUG_printf(("2httpRead2: zerr=%d", zerr)); +#ifdef DEBUG + http_debug_hex("2httpRead2", (char *)http->sbuffer, (int)http->stream.avail_in); +#endif /* DEBUG */ + + http->error = EIO; return (-1); } + + bytes = (ssize_t)(length - http->stream.avail_out); + + DEBUG_printf(("2httpRead2: avail_in=%d, avail_out=%d, bytes=%d", + http->stream.avail_in, http->stream.avail_out, + (int)bytes)); } - else if (errno != EINTR) + else + bytes = 0; + + if (bytes == 0) { - http->error = errno; - return (-1); + ssize_t buflen = HTTP_MAX_BUFFER - (ssize_t)http->stream.avail_in; + /* Additional bytes for buffer */ + + if (buflen > 0) + { + if (http->stream.avail_in > 0 && + http->stream.next_in > http->sbuffer) + memmove(http->sbuffer, http->stream.next_in, http->stream.avail_in); + + http->stream.next_in = http->sbuffer; + + DEBUG_printf(("1httpRead2: Reading up to %d more bytes of data into " + "decompression buffer.", (int)buflen)); + + if (http->data_remaining > 0) + { + if (buflen > http->data_remaining) + buflen = (ssize_t)http->data_remaining; + + bytes = http_read_buffered(http, (char *)http->sbuffer + http->stream.avail_in, (size_t)buflen); + } + else if (http->data_encoding == HTTP_ENCODING_CHUNKED) + bytes = http_read_chunk(http, (char *)http->sbuffer + http->stream.avail_in, (size_t)buflen); + else + bytes = 0; + + if (bytes < 0) + return (bytes); + else if (bytes == 0) + break; + + DEBUG_printf(("1httpRead2: Adding " CUPS_LLFMT " bytes to " + "decompression buffer.", CUPS_LLCAST bytes)); + + http->data_remaining -= bytes; + http->stream.avail_in += (uInt)bytes; + + if (http->data_remaining <= 0 && + http->data_encoding == HTTP_ENCODING_CHUNKED) + { + /* + * Read the trailing blank line now... + */ + + char len[32]; /* Length string */ + + httpGets(len, sizeof(len), http); + } + + bytes = 0; + } + else + return (0); } -#endif /* WIN32 */ - } - else - { - http->error = EPIPE; - return (0); } + while (bytes == 0); } - - if (http->used > 0) + else +#endif /* HAVE_LIBZ */ + if (http->data_remaining == 0 && http->data_encoding == HTTP_ENCODING_CHUNKED) { - if (length > (size_t)http->used) - length = (size_t)http->used; - - bytes = (ssize_t)length; + if ((bytes = http_read_chunk(http, buffer, length)) > 0) + { + http->data_remaining -= bytes; - DEBUG_printf(("2httpRead2: grabbing %d bytes from input buffer...", - (int)bytes)); + if (http->data_remaining <= 0) + { + /* + * Read the trailing blank line now... + */ - memcpy(buffer, http->buffer, length); - http->used -= (int)length; + char len[32]; /* Length string */ - if (http->used > 0) - memmove(http->buffer, http->buffer + length, http->used); + httpGets(len, sizeof(len), http); + } + } } -#ifdef HAVE_SSL - else if (http->tls) + else if (http->data_remaining <= 0) { - if (!http->blocking && !httpWait(http, 10000)) - return (0); + /* + * No more data to read... + */ - bytes = (ssize_t)http_read_ssl(http, buffer, (int)length); + return (0); } -#endif /* HAVE_SSL */ else { - if (!http->blocking && !httpWait(http, 10000)) - return (0); + DEBUG_printf(("1httpRead2: Reading up to %d bytes into buffer.", + (int)length)); - DEBUG_printf(("2httpRead2: reading " CUPS_LLFMT " bytes from socket...", - CUPS_LLCAST length)); + if (length > (size_t)http->data_remaining) + length = (size_t)http->data_remaining; -#ifdef WIN32 - while ((bytes = (ssize_t)recv(http->fd, buffer, (int)length, 0)) < 0) - { - if (WSAGetLastError() == WSAEWOULDBLOCK) - { - if (!http->timeout_cb || !(*http->timeout_cb)(http, http->timeout_data)) - break; - } - else if (WSAGetLastError() != WSAEINTR) - break; - } -#else - while ((bytes = recv(http->fd, buffer, length, 0)) < 0) + if ((bytes = http_read_buffered(http, buffer, length)) > 0) { - if (errno == EWOULDBLOCK || errno == EAGAIN) + http->data_remaining -= bytes; + + if (http->data_remaining <= 0 && + http->data_encoding == HTTP_ENCODING_CHUNKED) { - if (http->timeout_cb && !(*http->timeout_cb)(http, http->timeout_data)) - break; - else if (!http->timeout_cb && errno != EAGAIN) - break; + /* + * Read the trailing blank line now... + */ + + char len[32]; /* Length string */ + + httpGets(len, sizeof(len), http); } - else if (errno != EINTR) - break; } -#endif /* WIN32 */ - - DEBUG_printf(("2httpRead2: read " CUPS_LLFMT " bytes from socket...", - CUPS_LLCAST bytes)); } - if (bytes > 0) + if ( +#ifdef HAVE_LIBZ + (http->coding == _HTTP_CODING_IDENTITY || + (http->coding >= _HTTP_CODING_GUNZIP && http->stream.avail_in == 0)) && +#endif /* HAVE_LIBZ */ + ((http->data_remaining <= 0 && + http->data_encoding == HTTP_ENCODING_LENGTH) || + (http->data_encoding == HTTP_ENCODING_CHUNKED && bytes == 0))) { - http->data_remaining -= bytes; +#ifdef HAVE_LIBZ + if (http->coding >= _HTTP_CODING_GUNZIP) + http_content_coding_finish(http); +#endif /* HAVE_LIBZ */ - if (http->data_remaining <= INT_MAX) - http->_data_remaining = (int)http->data_remaining; + if (http->state == HTTP_STATE_POST_RECV) + http->state ++; + else if (http->state == HTTP_STATE_GET_SEND || + http->state == HTTP_STATE_POST_SEND) + http->state = HTTP_STATE_WAITING; else - http->_data_remaining = INT_MAX; + http->state = HTTP_STATE_STATUS; + + DEBUG_printf(("1httpRead2: End of content, set state to %s.", + httpStateString(http->state))); } - else if (bytes < 0) + + return (bytes); +} + + +/* + * 'httpReadRequest()' - Read a HTTP request from a connection. + * + * @since CUPS 1.7/macOS 10.9@ + */ + +http_state_t /* O - New state of connection */ +httpReadRequest(http_t *http, /* I - HTTP connection */ + char *uri, /* I - URI buffer */ + size_t urilen) /* I - Size of URI buffer */ +{ + char line[4096], /* HTTP request line */ + *req_method, /* HTTP request method */ + *req_uri, /* HTTP request URI */ + *req_version; /* HTTP request version number string */ + + + /* + * Range check input... + */ + + DEBUG_printf(("httpReadRequest(http=%p, uri=%p, urilen=" CUPS_LLFMT ")", (void *)http, (void *)uri, CUPS_LLCAST urilen)); + + if (uri) + *uri = '\0'; + + if (!http || !uri || urilen < 1) { -#ifdef WIN32 - if (WSAGetLastError() == WSAEINTR) - bytes = 0; - else - http->error = WSAGetLastError(); -#else - if (errno == EINTR || (errno == EAGAIN && !http->timeout_cb)) - bytes = 0; - else - http->error = errno; -#endif /* WIN32 */ + DEBUG_puts("1httpReadRequest: Bad arguments, returning HTTP_STATE_ERROR."); + return (HTTP_STATE_ERROR); } - else + else if (http->state != HTTP_STATE_WAITING) { - http->error = EPIPE; - return (0); + DEBUG_printf(("1httpReadRequest: Bad state %s, returning HTTP_STATE_ERROR.", + httpStateString(http->state))); + return (HTTP_STATE_ERROR); } - if (http->data_remaining == 0) + /* + * Reset state... + */ + + httpClearFields(http); + + http->activity = time(NULL); + http->data_encoding = HTTP_ENCODING_FIELDS; + http->data_remaining = 0; + http->keep_alive = HTTP_KEEPALIVE_OFF; + http->status = HTTP_STATUS_OK; + http->version = HTTP_VERSION_1_1; + + /* + * Read a line from the socket... + */ + + if (!httpGets(line, sizeof(line), http)) { - if (http->data_encoding == HTTP_ENCODE_CHUNKED) - httpGets(len, sizeof(len), http); + DEBUG_puts("1httpReadRequest: Unable to read, returning HTTP_STATE_ERROR"); + return (HTTP_STATE_ERROR); + } - if (http->data_encoding != HTTP_ENCODE_CHUNKED) - { - if (http->state == HTTP_POST_RECV) - http->state ++; - else - http->state = HTTP_WAITING; - } + if (!line[0]) + { + DEBUG_puts("1httpReadRequest: Blank line, returning HTTP_STATE_WAITING"); + return (HTTP_STATE_WAITING); } -#ifdef DEBUG - http_debug_hex("httpRead2", buffer, (int)bytes); -#endif /* DEBUG */ + DEBUG_printf(("1httpReadRequest: %s", line)); - return (bytes); -} + /* + * Parse it... + */ + req_method = line; + req_uri = line; -#if defined(HAVE_SSL) && defined(HAVE_CDSASSL) -/* - * '_httpReadCDSA()' - Read function for the CDSA library. - */ + while (*req_uri && !isspace(*req_uri & 255)) + req_uri ++; -OSStatus /* O - -1 on error, 0 on success */ -_httpReadCDSA( - SSLConnectionRef connection, /* I - SSL/TLS connection */ - void *data, /* I - Data buffer */ - size_t *dataLength) /* IO - Number of bytes */ -{ - OSStatus result; /* Return value */ - ssize_t bytes; /* Number of bytes read */ - http_t *http; /* HTTP connection */ + if (!*req_uri) + { + DEBUG_puts("1httpReadRequest: No request URI."); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No request URI."), 1); + return (HTTP_STATE_ERROR); + } + *req_uri++ = '\0'; - http = (http_t *)connection; + while (*req_uri && isspace(*req_uri & 255)) + req_uri ++; - if (!http->blocking) - { - /* - * Make sure we have data before we read... - */ + req_version = req_uri; - if (!_httpWait(http, 10000, 0)) - { - http->error = ETIMEDOUT; - return (-1); - } + while (*req_version && !isspace(*req_version & 255)) + req_version ++; + + if (!*req_version) + { + DEBUG_puts("1httpReadRequest: No request protocol version."); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No request protocol version."), 1); + return (HTTP_STATE_ERROR); } - do + *req_version++ = '\0'; + + while (*req_version && isspace(*req_version & 255)) + req_version ++; + + /* + * Validate... + */ + + if (!strcmp(req_method, "OPTIONS")) + http->state = HTTP_STATE_OPTIONS; + else if (!strcmp(req_method, "GET")) + http->state = HTTP_STATE_GET; + else if (!strcmp(req_method, "HEAD")) + http->state = HTTP_STATE_HEAD; + else if (!strcmp(req_method, "POST")) + http->state = HTTP_STATE_POST; + else if (!strcmp(req_method, "PUT")) + http->state = HTTP_STATE_PUT; + else if (!strcmp(req_method, "DELETE")) + http->state = HTTP_STATE_DELETE; + else if (!strcmp(req_method, "TRACE")) + http->state = HTTP_STATE_TRACE; + else if (!strcmp(req_method, "CONNECT")) + http->state = HTTP_STATE_CONNECT; + else { - bytes = recv(http->fd, data, *dataLength, 0); + DEBUG_printf(("1httpReadRequest: Unknown method \"%s\".", req_method)); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unknown request method."), 1); + return (HTTP_STATE_UNKNOWN_METHOD); } - while (bytes == -1 && (errno == EINTR || errno == EAGAIN)); - if (bytes == *dataLength) + DEBUG_printf(("1httpReadRequest: Set state to %s.", + httpStateString(http->state))); + + if (!strcmp(req_version, "HTTP/1.0")) { - result = 0; + http->version = HTTP_VERSION_1_0; + http->keep_alive = HTTP_KEEPALIVE_OFF; } - else if (bytes > 0) + else if (!strcmp(req_version, "HTTP/1.1")) { - *dataLength = bytes; - result = errSSLWouldBlock; + http->version = HTTP_VERSION_1_1; + http->keep_alive = HTTP_KEEPALIVE_ON; } else { - *dataLength = 0; - - if (bytes == 0) - result = errSSLClosedGraceful; - else if (errno == EAGAIN) - result = errSSLWouldBlock; - else - result = errSSLClosedAbort; + DEBUG_printf(("1httpReadRequest: Unknown version \"%s\".", req_version)); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unknown request version."), 1); + return (HTTP_STATE_UNKNOWN_VERSION); } - return (result); + DEBUG_printf(("1httpReadRequest: URI is \"%s\".", req_uri)); + strlcpy(uri, req_uri, urilen); + + return (http->state); } -#endif /* HAVE_SSL && HAVE_CDSASSL */ -#if defined(HAVE_SSL) && defined(HAVE_GNUTLS) /* - * '_httpReadGNUTLS()' - Read function for the GNU TLS library. + * 'httpReconnect()' - Reconnect to a HTTP server. + * + * This function is deprecated. Please use the @link httpReconnect2@ function + * instead. + * + * @deprecated@ */ -ssize_t /* O - Number of bytes read or -1 on error */ -_httpReadGNUTLS( - gnutls_transport_ptr ptr, /* I - Connection to server */ - void *data, /* I - Buffer */ - size_t length) /* I - Number of bytes to read */ +int /* O - 0 on success, non-zero on failure */ +httpReconnect(http_t *http) /* I - HTTP connection */ { - http_t *http; /* HTTP connection */ - - - http = (http_t *)ptr; - - if (!http->blocking) - { - /* - * Make sure we have data before we read... - */ - - if (!_httpWait(http, 10000, 0)) - { - http->error = ETIMEDOUT; - return (-1); - } - } + DEBUG_printf(("httpReconnect(http=%p)", (void *)http)); - return (recv(http->fd, data, length, 0)); + return (httpReconnect2(http, 30000, NULL)); } -#endif /* HAVE_SSL && HAVE_GNUTLS */ /* - * 'httpReconnect()' - Reconnect to a HTTP server. + * 'httpReconnect2()' - Reconnect to a HTTP server with timeout and optional + * cancel. */ int /* O - 0 on success, non-zero on failure */ -httpReconnect(http_t *http) /* I - Connection to server */ +httpReconnect2(http_t *http, /* I - HTTP connection */ + int msec, /* I - Timeout in milliseconds */ + int *cancel) /* I - Pointer to "cancel" variable */ { http_addrlist_t *addr; /* Connected address */ #ifdef DEBUG @@ -2218,16 +2355,19 @@ httpReconnect(http_t *http) /* I - Connection to server */ #endif /* DEBUG */ - DEBUG_printf(("httpReconnect(http=%p)", http)); + DEBUG_printf(("httpReconnect2(http=%p, msec=%d, cancel=%p)", (void *)http, msec, (void *)cancel)); if (!http) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); return (-1); + } #ifdef HAVE_SSL if (http->tls) { - DEBUG_puts("2httpReconnect: Shutting down SSL/TLS..."); - http_shutdown_ssl(http); + DEBUG_puts("2httpReconnect2: Shutting down SSL/TLS..."); + _httpTLSStop(http); } #endif /* HAVE_SSL */ @@ -2237,29 +2377,40 @@ httpReconnect(http_t *http) /* I - Connection to server */ if (http->fd >= 0) { - DEBUG_printf(("2httpReconnect: Closing socket %d...", http->fd)); + DEBUG_printf(("2httpReconnect2: Closing socket %d...", http->fd)); -#ifdef WIN32 - closesocket(http->fd); -#else - close(http->fd); -#endif /* WIN32 */ + httpAddrClose(NULL, http->fd); http->fd = -1; } + /* + * Reset all state (except fields, which may be reused)... + */ + + http->state = HTTP_STATE_WAITING; + http->version = HTTP_VERSION_1_1; + http->keep_alive = HTTP_KEEPALIVE_OFF; + memset(&http->_hostaddr, 0, sizeof(http->_hostaddr)); + http->data_encoding = HTTP_ENCODING_FIELDS; + http->_data_remaining = 0; + http->used = 0; + http->data_remaining = 0; + http->hostaddr = NULL; + http->wused = 0; + /* * Connect to the server... */ #ifdef DEBUG for (current = http->addrlist; current; current = current->next) - DEBUG_printf(("2httpReconnect: Address %s:%d", + DEBUG_printf(("2httpReconnect2: Address %s:%d", httpAddrString(&(current->addr), temp, sizeof(temp)), - _httpAddrPort(&(current->addr)))); + httpAddrPort(&(current->addr)))); #endif /* DEBUG */ - if ((addr = httpAddrConnect(http->addrlist, &(http->fd))) == NULL) + if ((addr = httpAddrConnect2(http->addrlist, &(http->fd), msec, cancel)) == NULL) { /* * Unable to connect... @@ -2270,64 +2421,43 @@ httpReconnect(http_t *http) /* I - Connection to server */ #else http->error = errno; #endif /* WIN32 */ - http->status = HTTP_ERROR; + http->status = HTTP_STATUS_ERROR; - DEBUG_printf(("1httpReconnect: httpAddrConnect failed: %s", + DEBUG_printf(("1httpReconnect2: httpAddrConnect failed: %s", strerror(http->error))); return (-1); } - DEBUG_printf(("2httpReconnect: New socket=%d", http->fd)); + DEBUG_printf(("2httpReconnect2: New socket=%d", http->fd)); - if (http->timeout_value.tv_sec > 0) - { -#ifdef WIN32 - DWORD timeout_value = http->timeout_value.tv_sec * 1000 + - http->timeout_value.tv_usec / 1000; - /* Timeout in milliseconds */ - - setsockopt(http->fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout_value, - sizeof(timeout_value)); - setsockopt(http->fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout_value, - sizeof(timeout_value)); -#else - setsockopt(http->fd, SOL_SOCKET, SO_RCVTIMEO, &(http->timeout_value), - sizeof(http->timeout_value)); - setsockopt(http->fd, SOL_SOCKET, SO_SNDTIMEO, &(http->timeout_value), - sizeof(http->timeout_value)); -#endif /* WIN32 */ - } + if (http->timeout_value > 0) + http_set_timeout(http->fd, http->timeout_value); http->hostaddr = &(addr->addr); http->error = 0; - http->status = HTTP_CONTINUE; #ifdef HAVE_SSL - if (http->encryption == HTTP_ENCRYPT_ALWAYS) + if (http->encryption == HTTP_ENCRYPTION_ALWAYS) { /* * Always do encryption via SSL. */ - if (http_setup_ssl(http) != 0) + if (_httpTLSStart(http) != 0) { -# ifdef WIN32 - closesocket(http->fd); -# else - close(http->fd); -# endif /* WIN32 */ + httpAddrClose(NULL, http->fd); return (-1); } } - else if (http->encryption == HTTP_ENCRYPT_REQUIRED) - return (http_upgrade(http)); + else if (http->encryption == HTTP_ENCRYPTION_REQUIRED && !http->tls_upgrade) + return (http_tls_upgrade(http)); #endif /* HAVE_SSL */ - DEBUG_printf(("1httpReconnect: Connected to %s:%d...", + DEBUG_printf(("1httpReconnect2: Connected to %s:%d...", httpAddrString(http->hostaddr, temp, sizeof(temp)), - _httpAddrPort(http->hostaddr))); + httpAddrPort(http->hostaddr))); return (0); } @@ -2341,11 +2471,11 @@ httpReconnect(http_t *http) /* I - Connection to server */ * HTTP_FIELD_AUTHORIZATION prior to issuing a HTTP request using httpGet(), * httpHead(), httpOptions(), httpPost, or httpPut(). * - * @since CUPS 1.3/Mac OS X 10.5@ + * @since CUPS 1.3/macOS 10.5@ */ void -httpSetAuthString(http_t *http, /* I - Connection to server */ +httpSetAuthString(http_t *http, /* I - HTTP connection */ const char *scheme, /* I - Auth scheme (NULL to clear it) */ const char *data) /* I - Auth data (NULL for none) */ { @@ -2367,10 +2497,10 @@ httpSetAuthString(http_t *http, /* I - Connection to server */ * Set the current authorization string... */ - int len = (int)strlen(scheme) + (data ? (int)strlen(data) + 1 : 0) + 1; + size_t len = strlen(scheme) + (data ? strlen(data) + 1 : 0) + 1; char *temp; - if (len > (int)sizeof(http->_authstring)) + if (len > sizeof(http->_authstring)) { if ((temp = malloc(len)) == NULL) len = sizeof(http->_authstring); @@ -2398,19 +2528,21 @@ httpSetAuthString(http_t *http, /* I - Connection to server */ * 'httpSetCredentials()' - Set the credentials associated with an encrypted * connection. * - * @since CUPS 1.5@ + * @since CUPS 1.5/macOS 10.7@ */ int /* O - Status of call (0 = success) */ -httpSetCredentials(http_t *http, /* I - Connection to server */ +httpSetCredentials(http_t *http, /* I - HTTP connection */ cups_array_t *credentials) /* I - Array of credentials */ { if (!http || cupsArrayCount(credentials) < 1) return (-1); +#ifdef HAVE_SSL _httpFreeCredentials(http->tls_credentials); - http->tls_credentials = _httpConvertCredentials(credentials); + http->tls_credentials = _httpCreateCredentials(credentials); +#endif /* HAVE_SSL */ return (http->tls_credentials ? 0 : -1); } @@ -2419,7 +2551,7 @@ httpSetCredentials(http_t *http, /* I - Connection to server */ /* * 'httpSetCookie()' - Set the cookie value(s). * - * @since CUPS 1.1.19/Mac OS X 10.3@ + * @since CUPS 1.1.19/macOS 10.3@ */ void @@ -2439,18 +2571,71 @@ httpSetCookie(http_t *http, /* I - Connection */ } +/* + * 'httpSetDefaultField()' - Set the default value of an HTTP header. + * + * Currently only @code HTTP_FIELD_ACCEPT_ENCODING@, @code HTTP_FIELD_SERVER@, + * and @code HTTP_FIELD_USER_AGENT@ can be set. + * + * @since CUPS 1.7/macOS 10.9@ + */ + +void +httpSetDefaultField(http_t *http, /* I - HTTP connection */ + http_field_t field, /* I - Field index */ + const char *value)/* I - Value */ +{ + DEBUG_printf(("httpSetDefaultField(http=%p, field=%d(%s), value=\"%s\")", (void *)http, field, http_fields[field], value)); + + if (!http) + return; + + switch (field) + { + case HTTP_FIELD_ACCEPT_ENCODING : + if (http->default_accept_encoding) + _cupsStrFree(http->default_accept_encoding); + + http->default_accept_encoding = value ? _cupsStrAlloc(value) : NULL; + break; + + case HTTP_FIELD_SERVER : + if (http->default_server) + _cupsStrFree(http->default_server); + + http->default_server = value ? _cupsStrAlloc(value) : NULL; + break; + + case HTTP_FIELD_USER_AGENT : + if (http->default_user_agent) + _cupsStrFree(http->default_user_agent); + + http->default_user_agent = value ? _cupsStrAlloc(value) : NULL; + break; + + default : + DEBUG_puts("1httpSetDefaultField: Ignored."); + break; + } +} + + /* * 'httpSetExpect()' - Set the Expect: header in a request. * - * Currently only HTTP_CONTINUE is supported for the "expect" argument. + * Currently only @code HTTP_STATUS_CONTINUE@ is supported for the "expect" + * argument. * - * @since CUPS 1.2/Mac OS X 10.5@ + * @since CUPS 1.2/macOS 10.5@ */ void -httpSetExpect(http_t *http, /* I - Connection to server */ - http_status_t expect) /* I - HTTP status to expect (HTTP_CONTINUE) */ +httpSetExpect(http_t *http, /* I - HTTP connection */ + http_status_t expect) /* I - HTTP status to expect + (@code HTTP_STATUS_CONTINUE@) */ { + DEBUG_printf(("httpSetExpect(http=%p, expect=%d)", (void *)http, expect)); + if (http) http->expect = expect; } @@ -2461,17 +2646,58 @@ httpSetExpect(http_t *http, /* I - Connection to server */ */ void -httpSetField(http_t *http, /* I - Connection to server */ +httpSetField(http_t *http, /* I - HTTP connection */ http_field_t field, /* I - Field index */ const char *value) /* I - Value */ { + DEBUG_printf(("httpSetField(http=%p, field=%d(%s), value=\"%s\")", (void *)http, field, http_fields[field], value)); + if (http == NULL || field < HTTP_FIELD_ACCEPT_LANGUAGE || - field > HTTP_FIELD_WWW_AUTHENTICATE || + field >= HTTP_FIELD_MAX || value == NULL) return; - strlcpy(http->fields[field], value, HTTP_MAX_VALUE); + switch (field) + { + case HTTP_FIELD_ACCEPT_ENCODING : + if (http->accept_encoding) + _cupsStrFree(http->accept_encoding); + + http->accept_encoding = _cupsStrAlloc(value); + break; + + case HTTP_FIELD_ALLOW : + if (http->allow) + _cupsStrFree(http->allow); + + http->allow = _cupsStrAlloc(value); + break; + + case HTTP_FIELD_SERVER : + if (http->server) + _cupsStrFree(http->server); + + http->server = _cupsStrAlloc(value); + break; + + case HTTP_FIELD_WWW_AUTHENTICATE : + /* CUPS STR #4503 - don't override WWW-Authenticate for unknown auth schemes */ + if (http->fields[HTTP_FIELD_WWW_AUTHENTICATE][0] && + _cups_strncasecmp(value, "Basic ", 6) && + _cups_strncasecmp(value, "Digest ", 7) && + _cups_strncasecmp(value, "Negotiate ", 10)) + { + DEBUG_printf(("1httpSetField: Ignoring unknown auth scheme in \"%s\".", value)); + return; + } + + /* Fall through to copy */ + + default : + strlcpy(http->fields[field], value, HTTP_MAX_VALUE); + break; + } if (field == HTTP_FIELD_AUTHORIZATION) { @@ -2523,25 +2749,52 @@ httpSetField(http_t *http, /* I - Connection to server */ } } } +#ifdef HAVE_LIBZ + else if (field == HTTP_FIELD_CONTENT_ENCODING && + http->data_encoding != HTTP_ENCODING_FIELDS) + { + DEBUG_puts("1httpSetField: Calling http_content_coding_start."); + http_content_coding_start(http, value); + } +#endif /* HAVE_LIBZ */ +} + + +/* + * 'httpSetKeepAlive()' - Set the current Keep-Alive state of a connection. + * + * @since CUPS 2.0/OS 10.10@ + */ + +void +httpSetKeepAlive( + http_t *http, /* I - HTTP connection */ + http_keepalive_t keep_alive) /* I - New Keep-Alive value */ +{ + if (http) + http->keep_alive = keep_alive; } /* * 'httpSetLength()' - Set the content-length and content-encoding. * - * @since CUPS 1.2/Mac OS X 10.5@ + * @since CUPS 1.2/macOS 10.5@ */ void -httpSetLength(http_t *http, /* I - Connection to server */ +httpSetLength(http_t *http, /* I - HTTP connection */ size_t length) /* I - Length (0 for chunked) */ { + DEBUG_printf(("httpSetLength(http=%p, length=" CUPS_LLFMT ")", (void *)http, CUPS_LLCAST length)); + if (!http) return; if (!length) { - strcpy(http->fields[HTTP_FIELD_TRANSFER_ENCODING], "chunked"); + strlcpy(http->fields[HTTP_FIELD_TRANSFER_ENCODING], "chunked", + HTTP_MAX_VALUE); http->fields[HTTP_FIELD_CONTENT_LENGTH][0] = '\0'; } else @@ -2554,46 +2807,58 @@ httpSetLength(http_t *http, /* I - Connection to server */ /* - * '_httpSetTimeout()' - Set read/write timeouts and an optional callback. + * 'httpSetTimeout()' - Set read/write timeouts and an optional callback. * * The optional timeout callback receives both the HTTP connection and a user - * data pointer and must return 1 to continue or 0 to error out. + * data pointer and must return 1 to continue or 0 to error (time) out. + * + * @since CUPS 1.5/macOS 10.7@ */ void -_httpSetTimeout( - http_t *http, /* I - Connection to server */ - double timeout, /* I - Number of seconds for timeout, +httpSetTimeout( + http_t *http, /* I - HTTP connection */ + double timeout, /* I - Number of seconds for timeout, must be greater than 0 */ - _http_timeout_cb_t cb, /* I - Callback function or NULL */ - void *user_data) /* I - User data pointer */ + http_timeout_cb_t cb, /* I - Callback function or NULL */ + void *user_data) /* I - User data pointer */ { if (!http || timeout <= 0.0) return; - http->timeout_cb = cb; - http->timeout_data = user_data; - http->timeout_value.tv_sec = (int)timeout; - http->timeout_value.tv_usec = (int)(timeout * 1000000) % 1000000; + http->timeout_cb = cb; + http->timeout_data = user_data; + http->timeout_value = timeout; if (http->fd >= 0) - { + http_set_timeout(http->fd, timeout); + + http_set_wait(http); +} + + +/* + * 'httpShutdown()' - Shutdown one side of an HTTP connection. + * + * @since CUPS 2.0/OS 10.10@ + */ + +void +httpShutdown(http_t *http) /* I - HTTP connection */ +{ + if (!http || http->fd < 0) + return; + +#ifdef HAVE_SSL + if (http->tls) + _httpTLSStop(http); +#endif /* HAVE_SSL */ + #ifdef WIN32 - DWORD timeout_value = http->timeout_value.tv_sec * 1000 + - http->timeout_value.tv_usec / 1000; - /* Timeout in milliseconds */ - - setsockopt(http->fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout_value, - sizeof(timeout_value)); - setsockopt(http->fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout_value, - sizeof(timeout_value)); + shutdown(http->fd, SD_RECEIVE); /* Microsoft-ism... */ #else - setsockopt(http->fd, SOL_SOCKET, SO_RCVTIMEO, &(http->timeout_value), - sizeof(http->timeout_value)); - setsockopt(http->fd, SOL_SOCKET, SO_SNDTIMEO, &(http->timeout_value), - sizeof(http->timeout_value)); + shutdown(http->fd, SHUT_RD); #endif /* WIN32 */ - } } @@ -2602,10 +2867,10 @@ _httpSetTimeout( */ int /* O - Status of call (0 = success) */ -httpTrace(http_t *http, /* I - Connection to server */ +httpTrace(http_t *http, /* I - HTTP connection */ const char *uri) /* I - URI for trace */ { - return (http_send(http, HTTP_TRACE, uri)); + return (http_send(http, HTTP_STATE_TRACE, uri)); } @@ -2617,7 +2882,7 @@ httpTrace(http_t *http, /* I - Connection to server */ */ int /* O - 1 to continue, 0 to stop */ -_httpUpdate(http_t *http, /* I - Connection to server */ +_httpUpdate(http_t *http, /* I - HTTP connection */ http_status_t *status) /* O - Current HTTP status */ { char line[32768], /* Line from connection... */ @@ -2626,8 +2891,7 @@ _httpUpdate(http_t *http, /* I - Connection to server */ int major, minor; /* HTTP version numbers */ - DEBUG_printf(("_httpUpdate(http=%p, status=%p), state=%s", http, status, - http_states[http->state])); + DEBUG_printf(("_httpUpdate(http=%p, status=%p), state=%s", (void *)http, (void *)status, httpStateString(http->state))); /* * Grab a single line from the connection... @@ -2635,7 +2899,7 @@ _httpUpdate(http_t *http, /* I - Connection to server */ if (!httpGets(line, sizeof(line), http)) { - *status = HTTP_ERROR; + *status = HTTP_STATUS_ERROR; return (0); } @@ -2647,62 +2911,76 @@ _httpUpdate(http_t *http, /* I - Connection to server */ * Blank line means the start of the data section (if any). Return * the result code, too... * - * If we get status 100 (HTTP_CONTINUE), then we *don't* change states. - * Instead, we just return HTTP_CONTINUE to the caller and keep on - * tryin'... + * If we get status 100 (HTTP_STATUS_CONTINUE), then we *don't* change + * states. Instead, we just return HTTP_STATUS_CONTINUE to the caller and + * keep on tryin'... */ - if (http->status == HTTP_CONTINUE) + if (http->status == HTTP_STATUS_CONTINUE) { *status = http->status; return (0); } - if (http->status < HTTP_BAD_REQUEST) + if (http->status < HTTP_STATUS_BAD_REQUEST) http->digest_tries = 0; #ifdef HAVE_SSL - if (http->status == HTTP_SWITCHING_PROTOCOLS && !http->tls) + if (http->status == HTTP_STATUS_SWITCHING_PROTOCOLS && !http->tls) { - if (http_setup_ssl(http) != 0) + if (_httpTLSStart(http) != 0) { -# ifdef WIN32 - closesocket(http->fd); -# else - close(http->fd); -# endif /* WIN32 */ + httpAddrClose(NULL, http->fd); - *status = http->status = HTTP_ERROR; + *status = http->status = HTTP_STATUS_ERROR; return (0); } - *status = HTTP_CONTINUE; + *status = HTTP_STATUS_CONTINUE; return (0); } #endif /* HAVE_SSL */ - httpGetLength2(http); + if (http_set_length(http) < 0) + { + DEBUG_puts("1_httpUpdate: Bad Content-Length."); + http->error = EINVAL; + http->status = *status = HTTP_STATUS_ERROR; + return (0); + } switch (http->state) { - case HTTP_GET : - case HTTP_POST : - case HTTP_POST_RECV : - case HTTP_PUT : + case HTTP_STATE_GET : + case HTTP_STATE_POST : + case HTTP_STATE_POST_RECV : + case HTTP_STATE_PUT : http->state ++; - case HTTP_POST_SEND : - case HTTP_HEAD : + + DEBUG_printf(("1_httpUpdate: Set state to %s.", + httpStateString(http->state))); + + case HTTP_STATE_POST_SEND : + case HTTP_STATE_HEAD : break; default : - http->state = HTTP_WAITING; + http->state = HTTP_STATE_WAITING; + + DEBUG_puts("1_httpUpdate: Reset state to HTTP_STATE_WAITING."); break; } +#ifdef HAVE_LIBZ + DEBUG_puts("1_httpUpdate: Calling http_content_coding_start."); + http_content_coding_start(http, + httpGetField(http, HTTP_FIELD_CONTENT_ENCODING)); +#endif /* HAVE_LIBZ */ + *status = http->status; return (0); } - else if (!strncmp(line, "HTTP/", 5)) + else if (!strncmp(line, "HTTP/", 5) && http->mode == _HTTP_MODE_CLIENT) { /* * Got the beginning of a response... @@ -2712,10 +2990,12 @@ _httpUpdate(http_t *http, /* I - Connection to server */ if (sscanf(line, "HTTP/%d.%d%d", &major, &minor, &intstatus) != 3) { - *status = http->status = HTTP_ERROR; + *status = http->status = HTTP_STATUS_ERROR; return (0); } + httpClearFields(http); + http->version = (http_version_t)(major * 100 + minor); *status = http->status = (http_status_t)intstatus; } @@ -2729,11 +3009,13 @@ _httpUpdate(http_t *http, /* I - Connection to server */ while (_cups_isspace(*value)) value ++; + DEBUG_printf(("1_httpUpdate: Header %s: %s", line, value)); + /* * Be tolerants of servers that send unknown attribute fields... */ - if (!strcasecmp(line, "expect")) + if (!_cups_strcasecmp(line, "expect")) { /* * "Expect: 100-continue" or similar... @@ -2741,7 +3023,7 @@ _httpUpdate(http_t *http, /* I - Connection to server */ http->expect = (http_status_t)atoi(value); } - else if (!strcasecmp(line, "cookie")) + else if (!_cups_strcasecmp(line, "cookie")) { /* * "Cookie: name=value[; name=value ...]" - replaces previous cookies... @@ -2749,7 +3031,7 @@ _httpUpdate(http_t *http, /* I - Connection to server */ httpSetCookie(http, value); } - else if ((field = http_field(line)) != HTTP_FIELD_UNKNOWN) + else if ((field = httpFieldValue(line)) != HTTP_FIELD_UNKNOWN) httpSetField(http, field, value); #ifdef DEBUG else @@ -2759,7 +3041,8 @@ _httpUpdate(http_t *http, /* I - Connection to server */ else { DEBUG_printf(("1_httpUpdate: Bad response line \"%s\"!", line)); - *status = http->status = HTTP_ERROR; + http->error = EINVAL; + http->status = *status = HTTP_STATUS_ERROR; return (0); } @@ -2772,13 +3055,12 @@ _httpUpdate(http_t *http, /* I - Connection to server */ */ http_status_t /* O - HTTP status */ -httpUpdate(http_t *http) /* I - Connection to server */ +httpUpdate(http_t *http) /* I - HTTP connection */ { http_status_t status; /* Request status */ - DEBUG_printf(("httpUpdate(http=%p), state=%s", http, - http_states[http->state])); + DEBUG_printf(("httpUpdate(http=%p), state=%s", (void *)http, httpStateString(http->state))); /* * Flush pending data, if any... @@ -2789,15 +3071,15 @@ httpUpdate(http_t *http) /* I - Connection to server */ DEBUG_puts("2httpUpdate: flushing buffer..."); if (httpFlushWrite(http) < 0) - return (HTTP_ERROR); + return (HTTP_STATUS_ERROR); } /* * If we haven't issued any commands, then there is nothing to "update"... */ - if (http->state == HTTP_WAITING) - return (HTTP_CONTINUE); + if (http->state == HTTP_STATE_WAITING) + return (HTTP_STATUS_CONTINUE); /* * Grab all of the lines we can from the connection... @@ -2809,7 +3091,7 @@ httpUpdate(http_t *http) /* I - Connection to server */ * See if there was an error... */ - if (http->error == EPIPE && http->status > HTTP_CONTINUE) + if (http->error == EPIPE && http->status > HTTP_STATUS_CONTINUE) { DEBUG_printf(("1httpUpdate: Returning status %d...", http->status)); return (http->status); @@ -2819,8 +3101,8 @@ httpUpdate(http_t *http) /* I - Connection to server */ { DEBUG_printf(("1httpUpdate: socket error %d - %s", http->error, strerror(http->error))); - http->status = HTTP_ERROR; - return (HTTP_ERROR); + http->status = HTTP_STATUS_ERROR; + return (HTTP_STATUS_ERROR); } /* @@ -2836,7 +3118,7 @@ httpUpdate(http_t *http) /* I - Connection to server */ */ int /* O - 1 if data is available, 0 otherwise */ -_httpWait(http_t *http, /* I - Connection to server */ +_httpWait(http_t *http, /* I - HTTP connection */ int msec, /* I - Milliseconds to wait */ int usessl) /* I - Use SSL context? */ { @@ -2849,7 +3131,7 @@ _httpWait(http_t *http, /* I - Connection to server */ int nfds; /* Result from select()/poll() */ - DEBUG_printf(("4_httpWait(http=%p, msec=%d, usessl=%d)", http, msec, usessl)); + DEBUG_printf(("4_httpWait(http=%p, msec=%d, usessl=%d)", (void *)http, msec, usessl)); if (http->fd < 0) { @@ -2862,32 +3144,10 @@ _httpWait(http_t *http, /* I - Connection to server */ */ #ifdef HAVE_SSL - if (http->tls && usessl) + if (http->tls && _httpTLSPending(http)) { -# ifdef HAVE_LIBSSL - if (SSL_pending(http->tls)) - { - DEBUG_puts("5_httpWait: Return 1 since there is pending SSL data."); - return (1); - } - -# elif defined(HAVE_GNUTLS) - if (gnutls_record_check_pending(http->tls)) - { - DEBUG_puts("5_httpWait: Return 1 since there is pending SSL data."); - return (1); - } - -# elif defined(HAVE_CDSASSL) - size_t bytes; /* Bytes that are available */ - - if (!SSLGetBufferedReadSize(http->tls, &bytes) && - bytes > 0) - { - DEBUG_puts("5_httpWait: Return 1 since there is pending SSL data."); - return (1); - } -# endif /* HAVE_LIBSSL */ + DEBUG_puts("5_httpWait: Return 1 since there is pending TLS data."); + return (1); } #endif /* HAVE_SSL */ @@ -2899,8 +3159,11 @@ _httpWait(http_t *http, /* I - Connection to server */ pfd.fd = http->fd; pfd.events = POLLIN; - while ((nfds = poll(&pfd, 1, msec)) < 0 && - (errno == EINTR || errno == EAGAIN)); + do + { + nfds = poll(&pfd, 1, msec); + } + while (nfds < 0 && (errno == EINTR || errno == EAGAIN)); #else do @@ -2940,22 +3203,35 @@ _httpWait(http_t *http, /* I - Connection to server */ /* * 'httpWait()' - Wait for data available on a connection. * - * @since CUPS 1.1.19/Mac OS X 10.3@ + * @since CUPS 1.1.19/macOS 10.3@ */ int /* O - 1 if data is available, 0 otherwise */ -httpWait(http_t *http, /* I - Connection to server */ +httpWait(http_t *http, /* I - HTTP connection */ int msec) /* I - Milliseconds to wait */ { /* * First see if there is data in the buffer... */ + DEBUG_printf(("2httpWait(http=%p, msec=%d)", (void *)http, msec)); + if (http == NULL) return (0); if (http->used) + { + DEBUG_puts("3httpWait: Returning 1 since there is buffered data ready."); + return (1); + } + +#ifdef HAVE_LIBZ + if (http->coding >= _HTTP_CODING_GUNZIP && http->stream.avail_in > 0) + { + DEBUG_puts("3httpWait: Returning 1 since there is buffered data ready."); return (1); + } +#endif /* HAVE_LIBZ */ /* * Flush pending data, if any... @@ -2963,6 +3239,8 @@ httpWait(http_t *http, /* I - Connection to server */ if (http->wused) { + DEBUG_puts("3httpWait: Flushing write buffer."); + if (httpFlushWrite(http) < 0) return (0); } @@ -2985,37 +3263,39 @@ httpWait(http_t *http, /* I - Connection to server */ */ int /* O - Number of bytes written */ -httpWrite(http_t *http, /* I - Connection to server */ +httpWrite(http_t *http, /* I - HTTP connection */ const char *buffer, /* I - Buffer for data */ int length) /* I - Number of bytes to write */ { - return ((int)httpWrite2(http, buffer, length)); + return ((int)httpWrite2(http, buffer, (size_t)length)); } /* * 'httpWrite2()' - Write data to a HTTP connection. * - * @since CUPS 1.2/Mac OS X 10.5@ + * @since CUPS 1.2/macOS 10.5@ */ ssize_t /* O - Number of bytes written */ -httpWrite2(http_t *http, /* I - Connection to server */ +httpWrite2(http_t *http, /* I - HTTP connection */ const char *buffer, /* I - Buffer for data */ size_t length) /* I - Number of bytes to write */ { ssize_t bytes; /* Bytes written */ - DEBUG_printf(("httpWrite2(http=%p, buffer=%p, length=" CUPS_LLFMT ")", http, - buffer, CUPS_LLCAST length)); + DEBUG_printf(("httpWrite2(http=%p, buffer=%p, length=" CUPS_LLFMT ")", (void *)http, (void *)buffer, CUPS_LLCAST length)); /* * Range check input... */ - if (http == NULL || buffer == NULL) + if (!http || !buffer) + { + DEBUG_puts("1httpWrite2: Returning -1 due to bad input."); return (-1); + } /* * Mark activity on the connection... @@ -3027,9 +3307,60 @@ httpWrite2(http_t *http, /* I - Connection to server */ * Buffer small writes for better performance... */ +#ifdef HAVE_LIBZ + if (http->coding == _HTTP_CODING_GZIP || http->coding == _HTTP_CODING_DEFLATE) + { + DEBUG_printf(("1httpWrite2: http->coding=%d", http->coding)); + + if (length == 0) + { + http_content_coding_finish(http); + bytes = 0; + } + else + { + size_t slen; /* Bytes to write */ + ssize_t sret; /* Bytes written */ + + http->stream.next_in = (Bytef *)buffer; + http->stream.avail_in = (uInt)length; + + while (deflate(&(http->stream), Z_NO_FLUSH) == Z_OK) + { + DEBUG_printf(("1httpWrite2: avail_out=%d", http->stream.avail_out)); + + if (http->stream.avail_out > 0) + continue; + + slen = _HTTP_MAX_SBUFFER - http->stream.avail_out; + + DEBUG_printf(("1httpWrite2: Writing intermediate chunk, len=%d", (int)slen)); + + if (slen > 0 && http->data_encoding == HTTP_ENCODING_CHUNKED) + sret = http_write_chunk(http, (char *)http->sbuffer, slen); + else if (slen > 0) + sret = http_write(http, (char *)http->sbuffer, slen); + else + sret = 0; + + if (sret < 0) + { + DEBUG_puts("1httpWrite2: Unable to write, returning -1."); + return (-1); + } + + http->stream.next_out = (Bytef *)http->sbuffer; + http->stream.avail_out = (uInt)_HTTP_MAX_SBUFFER; + } + + bytes = (ssize_t)length; + } + } + else +#endif /* HAVE_LIBZ */ if (length > 0) { - if (http->wused && (length + http->wused) > sizeof(http->wbuffer)) + if (http->wused && (length + (size_t)http->wused) > sizeof(http->wbuffer)) { DEBUG_printf(("2httpWrite2: Flushing buffer (wused=%d, length=" CUPS_LLFMT ")", http->wused, CUPS_LLCAST length)); @@ -3037,8 +3368,7 @@ httpWrite2(http_t *http, /* I - Connection to server */ httpFlushWrite(http); } - if ((length + http->wused) <= sizeof(http->wbuffer) && - length < sizeof(http->wbuffer)) + if ((length + (size_t)http->wused) <= sizeof(http->wbuffer) && length < sizeof(http->wbuffer)) { /* * Write to buffer... @@ -3060,16 +3390,16 @@ httpWrite2(http_t *http, /* I - Connection to server */ DEBUG_printf(("2httpWrite2: Writing " CUPS_LLFMT " bytes to socket...", CUPS_LLCAST length)); - if (http->data_encoding == HTTP_ENCODE_CHUNKED) - bytes = (ssize_t)http_write_chunk(http, buffer, (int)length); + if (http->data_encoding == HTTP_ENCODING_CHUNKED) + bytes = (ssize_t)http_write_chunk(http, buffer, length); else - bytes = (ssize_t)http_write(http, buffer, (int)length); + bytes = (ssize_t)http_write(http, buffer, length); DEBUG_printf(("2httpWrite2: Wrote " CUPS_LLFMT " bytes...", CUPS_LLCAST bytes)); } - if (http->data_encoding == HTTP_ENCODE_LENGTH) + if (http->data_encoding == HTTP_ENCODING_LENGTH) http->data_remaining -= bytes; } else @@ -3079,20 +3409,26 @@ httpWrite2(http_t *http, /* I - Connection to server */ * Handle end-of-request processing... */ - if ((http->data_encoding == HTTP_ENCODE_CHUNKED && length == 0) || - (http->data_encoding == HTTP_ENCODE_LENGTH && http->data_remaining == 0)) + if ((http->data_encoding == HTTP_ENCODING_CHUNKED && length == 0) || + (http->data_encoding == HTTP_ENCODING_LENGTH && http->data_remaining == 0)) { /* * Finished with the transfer; unless we are sending POST or PUT * data, go idle... */ - DEBUG_puts("2httpWrite: changing states..."); +#ifdef HAVE_LIBZ + if (http->coding == _HTTP_CODING_GZIP || http->coding == _HTTP_CODING_DEFLATE) + http_content_coding_finish(http); +#endif /* HAVE_LIBZ */ if (http->wused) - httpFlushWrite(http); + { + if (httpFlushWrite(http) < 0) + return (-1); + } - if (http->data_encoding == HTTP_ENCODE_CHUNKED) + if (http->data_encoding == HTTP_ENCODING_CHUNKED) { /* * Send a 0-length chunk at the end of the request... @@ -3104,223 +3440,540 @@ httpWrite2(http_t *http, /* I - Connection to server */ * Reset the data state... */ - http->data_encoding = HTTP_ENCODE_LENGTH; + http->data_encoding = HTTP_ENCODING_FIELDS; http->data_remaining = 0; } + + if (http->state == HTTP_STATE_POST_RECV) + http->state ++; + else if (http->state == HTTP_STATE_POST_SEND || + http->state == HTTP_STATE_GET_SEND) + http->state = HTTP_STATE_WAITING; + else + http->state = HTTP_STATE_STATUS; + + DEBUG_printf(("2httpWrite2: Changed state to %s.", + httpStateString(http->state))); } + DEBUG_printf(("1httpWrite2: Returning " CUPS_LLFMT ".", CUPS_LLCAST bytes)); + return (bytes); } -#if defined(HAVE_SSL) && defined(HAVE_CDSASSL) /* - * '_httpWriteCDSA()' - Write function for the CDSA library. + * 'httpWriteResponse()' - Write a HTTP response to a client connection. + * + * @since CUPS 1.7/macOS 10.9@ */ -OSStatus /* O - -1 on error, 0 on success */ -_httpWriteCDSA( - SSLConnectionRef connection, /* I - SSL/TLS connection */ - const void *data, /* I - Data buffer */ - size_t *dataLength) /* IO - Number of bytes */ +int /* O - 0 on success, -1 on error */ +httpWriteResponse(http_t *http, /* I - HTTP connection */ + http_status_t status) /* I - Status code */ { - OSStatus result; /* Return value */ - ssize_t bytes; /* Number of bytes read */ - http_t *http; /* HTTP connection */ + http_encoding_t old_encoding; /* Old data_encoding value */ + off_t old_remaining; /* Old data_remaining value */ - http = (http_t *)connection; + /* + * Range check input... + */ - do + DEBUG_printf(("httpWriteResponse(http=%p, status=%d)", (void *)http, status)); + + if (!http || status < HTTP_STATUS_CONTINUE) + { + DEBUG_puts("1httpWriteResponse: Bad input."); + return (-1); + } + + /* + * Set the various standard fields if they aren't already... + */ + + if (!http->fields[HTTP_FIELD_DATE][0]) + httpSetField(http, HTTP_FIELD_DATE, httpGetDateString(time(NULL))); + + if (status >= HTTP_STATUS_BAD_REQUEST && http->keep_alive) { - bytes = write(http->fd, data, *dataLength); + http->keep_alive = HTTP_KEEPALIVE_OFF; + httpSetField(http, HTTP_FIELD_KEEP_ALIVE, ""); } - while (bytes == -1 && (errno == EINTR || errno == EAGAIN)); - if (bytes == *dataLength) + if (http->version == HTTP_VERSION_1_1) { - result = 0; + if (!http->fields[HTTP_FIELD_CONNECTION][0]) + { + if (http->keep_alive) + httpSetField(http, HTTP_FIELD_CONNECTION, "Keep-Alive"); + else + httpSetField(http, HTTP_FIELD_CONNECTION, "close"); + } + + if (http->keep_alive && !http->fields[HTTP_FIELD_KEEP_ALIVE][0]) + httpSetField(http, HTTP_FIELD_KEEP_ALIVE, "timeout=10"); } - else if (bytes >= 0) + +#ifdef HAVE_SSL + if (status == HTTP_STATUS_UPGRADE_REQUIRED || + status == HTTP_STATUS_SWITCHING_PROTOCOLS) { - *dataLength = bytes; - result = errSSLWouldBlock; + if (!http->fields[HTTP_FIELD_CONNECTION][0]) + httpSetField(http, HTTP_FIELD_CONNECTION, "Upgrade"); + + if (!http->fields[HTTP_FIELD_UPGRADE][0]) + httpSetField(http, HTTP_FIELD_UPGRADE, "TLS/1.2,TLS/1.1,TLS/1.0"); + + if (!http->fields[HTTP_FIELD_CONTENT_LENGTH][0]) + httpSetField(http, HTTP_FIELD_CONTENT_LENGTH, "0"); } - else +#endif /* HAVE_SSL */ + + if (!http->server) + httpSetField(http, HTTP_FIELD_SERVER, + http->default_server ? http->default_server : CUPS_MINIMAL); + + /* + * Set the Accept-Encoding field if it isn't already... + */ + + if (!http->accept_encoding) + httpSetField(http, HTTP_FIELD_ACCEPT_ENCODING, + http->default_accept_encoding ? http->default_accept_encoding : +#ifdef HAVE_LIBZ + "gzip, deflate, identity"); +#else + "identity"); +#endif /* HAVE_LIBZ */ + + /* + * Send the response header... + */ + + old_encoding = http->data_encoding; + old_remaining = http->data_remaining; + http->data_encoding = HTTP_ENCODING_FIELDS; + + if (httpPrintf(http, "HTTP/%d.%d %d %s\r\n", http->version / 100, + http->version % 100, (int)status, httpStatus(status)) < 0) + { + http->status = HTTP_STATUS_ERROR; + return (-1); + } + + if (status != HTTP_STATUS_CONTINUE) + { + /* + * 100 Continue doesn't have the rest of the response headers... + */ + + int i; /* Looping var */ + const char *value; /* Field value */ + + for (i = 0; i < HTTP_FIELD_MAX; i ++) + { + if ((value = httpGetField(http, i)) != NULL && *value) + { + if (httpPrintf(http, "%s: %s\r\n", http_fields[i], value) < 1) + { + http->status = HTTP_STATUS_ERROR; + return (-1); + } + } + } + + if (http->cookie) + { + if (strchr(http->cookie, ';')) + { + if (httpPrintf(http, "Set-Cookie: %s\r\n", http->cookie) < 1) + { + http->status = HTTP_STATUS_ERROR; + return (-1); + } + } + else if (httpPrintf(http, "Set-Cookie: %s; path=/; httponly;%s\r\n", http->cookie, http->tls ? " secure;" : "") < 1) + { + http->status = HTTP_STATUS_ERROR; + return (-1); + } + } + + /* + * "Click-jacking" defense (STR #4492)... + */ + + if (httpPrintf(http, "X-Frame-Options: DENY\r\n" + "Content-Security-Policy: frame-ancestors 'none'\r\n") < 1) + { + http->status = HTTP_STATUS_ERROR; + return (-1); + } + } + + if (httpWrite2(http, "\r\n", 2) < 2) + { + http->status = HTTP_STATUS_ERROR; + return (-1); + } + + if (httpFlushWrite(http) < 0) { - *dataLength = 0; + http->status = HTTP_STATUS_ERROR; + return (-1); + } + + if (status == HTTP_STATUS_CONTINUE || + status == HTTP_STATUS_SWITCHING_PROTOCOLS) + { + /* + * Restore the old data_encoding and data_length values... + */ - if (errno == EAGAIN) - result = errSSLWouldBlock; + http->data_encoding = old_encoding; + http->data_remaining = old_remaining; + + if (old_remaining <= INT_MAX) + http->_data_remaining = (int)old_remaining; else - result = errSSLClosedAbort; + http->_data_remaining = INT_MAX; + } + else if (http->state == HTTP_STATE_OPTIONS || + http->state == HTTP_STATE_HEAD || + http->state == HTTP_STATE_PUT || + http->state == HTTP_STATE_TRACE || + http->state == HTTP_STATE_CONNECT || + http->state == HTTP_STATE_STATUS) + { + DEBUG_printf(("1httpWriteResponse: Resetting state to HTTP_STATE_WAITING, " + "was %s.", httpStateString(http->state))); + http->state = HTTP_STATE_WAITING; + } + else + { + /* + * Force data_encoding and data_length to be set according to the response + * headers... + */ + + http_set_length(http); + + if (http->data_encoding == HTTP_ENCODING_LENGTH && http->data_remaining == 0) + { + DEBUG_printf(("1httpWriteResponse: Resetting state to HTTP_STATE_WAITING, " + "was %s.", httpStateString(http->state))); + http->state = HTTP_STATE_WAITING; + return (0); + } + +#ifdef HAVE_LIBZ + /* + * Then start any content encoding... + */ + + DEBUG_puts("1httpWriteResponse: Calling http_content_coding_start."); + http_content_coding_start(http, + httpGetField(http, HTTP_FIELD_CONTENT_ENCODING)); +#endif /* HAVE_LIBZ */ + } - return (result); + return (0); } -#endif /* HAVE_SSL && HAVE_CDSASSL */ -#if defined(HAVE_SSL) && defined(HAVE_GNUTLS) +#ifdef HAVE_LIBZ /* - * '_httpWriteGNUTLS()' - Write function for the GNU TLS library. + * 'http_content_coding_finish()' - Finish doing any content encoding. */ -ssize_t /* O - Number of bytes written or -1 on error */ -_httpWriteGNUTLS( - gnutls_transport_ptr ptr, /* I - Connection to server */ - const void *data, /* I - Data buffer */ - size_t length) /* I - Number of bytes to write */ +static void +http_content_coding_finish( + http_t *http) /* I - HTTP connection */ { - return (send(((http_t *)ptr)->fd, data, length, 0)); + int zerr; /* Compression status */ + Byte dummy[1]; /* Dummy read buffer */ + size_t bytes; /* Number of bytes to write */ + + + DEBUG_printf(("http_content_coding_finish(http=%p)", (void *)http)); + DEBUG_printf(("1http_content_coding_finishing: http->coding=%d", http->coding)); + + switch (http->coding) + { + case _HTTP_CODING_DEFLATE : + case _HTTP_CODING_GZIP : + http->stream.next_in = dummy; + http->stream.avail_in = 0; + + do + { + zerr = deflate(&(http->stream), Z_FINISH); + bytes = _HTTP_MAX_SBUFFER - http->stream.avail_out; + + if (bytes > 0) + { + DEBUG_printf(("1http_content_coding_finish: Writing trailing chunk, len=%d", (int)bytes)); + + if (http->data_encoding == HTTP_ENCODING_CHUNKED) + http_write_chunk(http, (char *)http->sbuffer, bytes); + else + http_write(http, (char *)http->sbuffer, bytes); + } + + http->stream.next_out = (Bytef *)http->sbuffer; + http->stream.avail_out = (uInt)_HTTP_MAX_SBUFFER; + } + while (zerr == Z_OK); + + deflateEnd(&(http->stream)); + + free(http->sbuffer); + http->sbuffer = NULL; + + if (http->wused) + httpFlushWrite(http); + break; + + case _HTTP_CODING_INFLATE : + case _HTTP_CODING_GUNZIP : + inflateEnd(&(http->stream)); + free(http->sbuffer); + http->sbuffer = NULL; + break; + + default : + break; + } + + http->coding = _HTTP_CODING_IDENTITY; } -#endif /* HAVE_SSL && HAVE_GNUTLS */ -#if defined(HAVE_SSL) && defined(HAVE_LIBSSL) /* - * 'http_bio_ctrl()' - Control the HTTP connection. + * 'http_content_coding_start()' - Start doing content encoding. */ -static long /* O - Result/data */ -http_bio_ctrl(BIO *h, /* I - BIO data */ - int cmd, /* I - Control command */ - long arg1, /* I - First argument */ - void *arg2) /* I - Second argument */ +static void +http_content_coding_start( + http_t *http, /* I - HTTP connection */ + const char *value) /* I - Value of Content-Encoding */ { - switch (cmd) - { - default : - return (0); + int zerr; /* Error/status */ + _http_coding_t coding; /* Content coding value */ - case BIO_CTRL_RESET : - h->ptr = NULL; - return (0); - case BIO_C_SET_FILE_PTR : - h->ptr = arg2; - h->init = 1; - return (1); + DEBUG_printf(("http_content_coding_start(http=%p, value=\"%s\")", (void *)http, value)); + + if (http->coding != _HTTP_CODING_IDENTITY) + { + DEBUG_printf(("1http_content_coding_start: http->coding already %d.", + http->coding)); + return; + } + else if (!strcmp(value, "x-gzip") || !strcmp(value, "gzip")) + { + if (http->state == HTTP_STATE_GET_SEND || + http->state == HTTP_STATE_POST_SEND) + coding = http->mode == _HTTP_MODE_SERVER ? _HTTP_CODING_GZIP : + _HTTP_CODING_GUNZIP; + else if (http->state == HTTP_STATE_POST_RECV || + http->state == HTTP_STATE_PUT_RECV) + coding = http->mode == _HTTP_MODE_CLIENT ? _HTTP_CODING_GZIP : + _HTTP_CODING_GUNZIP; + else + { + DEBUG_puts("1http_content_coding_start: Not doing content coding."); + return; + } + } + else if (!strcmp(value, "x-deflate") || !strcmp(value, "deflate")) + { + if (http->state == HTTP_STATE_GET_SEND || + http->state == HTTP_STATE_POST_SEND) + coding = http->mode == _HTTP_MODE_SERVER ? _HTTP_CODING_DEFLATE : + _HTTP_CODING_INFLATE; + else if (http->state == HTTP_STATE_POST_RECV || + http->state == HTTP_STATE_PUT_RECV) + coding = http->mode == _HTTP_MODE_CLIENT ? _HTTP_CODING_DEFLATE : + _HTTP_CODING_INFLATE; + else + { + DEBUG_puts("1http_content_coding_start: Not doing content coding."); + return; + } + } + else + { + DEBUG_puts("1http_content_coding_start: Not doing content coding."); + return; + } - case BIO_C_GET_FILE_PTR : - if (arg2) - { - *((void **)arg2) = h->ptr; - return (1); - } - else - return (0); + memset(&(http->stream), 0, sizeof(http->stream)); - case BIO_CTRL_DUP : - case BIO_CTRL_FLUSH : - return (1); - } -} + switch (coding) + { + case _HTTP_CODING_DEFLATE : + case _HTTP_CODING_GZIP : + if (http->wused) + httpFlushWrite(http); + + if ((http->sbuffer = malloc(_HTTP_MAX_SBUFFER)) == NULL) + { + http->status = HTTP_STATUS_ERROR; + http->error = errno; + return; + } + /* + * Window size for compression is 11 bits - optimal based on PWG Raster + * sample files on pwg.org. -11 is raw deflate, 27 is gzip, per ZLIB + * documentation. + */ + + if ((zerr = deflateInit2(&(http->stream), Z_DEFAULT_COMPRESSION, + Z_DEFLATED, + coding == _HTTP_CODING_DEFLATE ? -11 : 27, 7, + Z_DEFAULT_STRATEGY)) < Z_OK) + { + http->status = HTTP_STATUS_ERROR; + http->error = zerr == Z_MEM_ERROR ? ENOMEM : EINVAL; + return; + } + + http->stream.next_out = (Bytef *)http->sbuffer; + http->stream.avail_out = (uInt)_HTTP_MAX_SBUFFER; + break; -/* - * 'http_bio_free()' - Free OpenSSL data. - */ + case _HTTP_CODING_INFLATE : + case _HTTP_CODING_GUNZIP : + if ((http->sbuffer = malloc(_HTTP_MAX_SBUFFER)) == NULL) + { + http->status = HTTP_STATUS_ERROR; + http->error = errno; + return; + } -static int /* O - 1 on success, 0 on failure */ -http_bio_free(BIO *h) /* I - BIO data */ -{ - if (!h) - return (0); + /* + * Window size for decompression is up to 15 bits (maximum supported). + * -15 is raw inflate, 31 is gunzip, per ZLIB documentation. + */ + + if ((zerr = inflateInit2(&(http->stream), + coding == _HTTP_CODING_INFLATE ? -15 : 31)) + < Z_OK) + { + free(http->sbuffer); + http->sbuffer = NULL; + http->status = HTTP_STATUS_ERROR; + http->error = zerr == Z_MEM_ERROR ? ENOMEM : EINVAL; + return; + } + + http->stream.avail_in = 0; + http->stream.next_in = http->sbuffer; + break; - if (h->shutdown) - { - h->init = 0; - h->flags = 0; + default : + break; } - return (1); + http->coding = coding; + + DEBUG_printf(("1http_content_coding_start: http->coding now %d.", + http->coding)); } +#endif /* HAVE_LIBZ */ /* - * 'http_bio_new()' - Initialize an OpenSSL BIO structure. + * 'http_create()' - Create an unconnected HTTP connection. */ -static int /* O - 1 on success, 0 on failure */ -http_bio_new(BIO *h) /* I - BIO data */ +static http_t * /* O - HTTP connection */ +http_create( + const char *host, /* I - Hostname */ + int port, /* I - Port number */ + http_addrlist_t *addrlist, /* I - Address list or NULL */ + int family, /* I - Address family or AF_UNSPEC */ + http_encryption_t encryption, /* I - Encryption to use */ + int blocking, /* I - 1 for blocking mode */ + _http_mode_t mode) /* I - _HTTP_MODE_CLIENT or _SERVER */ { - if (!h) - return (0); - - h->init = 0; - h->num = 0; - h->ptr = NULL; - h->flags = 0; + http_t *http; /* New HTTP connection */ + char service[255]; /* Service name */ + http_addrlist_t *myaddrlist = NULL; /* My address list */ - return (1); -} + DEBUG_printf(("4http_create(host=\"%s\", port=%d, addrlist=%p, family=%d, encryption=%d, blocking=%d, mode=%d)", host, port, (void *)addrlist, family, encryption, blocking, mode)); -/* - * 'http_bio_puts()' - Send a string for OpenSSL. - */ + if (!host && mode == _HTTP_MODE_CLIENT) + return (NULL); -static int /* O - Bytes written */ -http_bio_puts(BIO *h, /* I - BIO data */ - const char *str) /* I - String to write */ -{ -#ifdef WIN32 - return (send(((http_t *)h->ptr)->fd, str, (int)strlen(str), 0)); -#else - return (send(((http_t *)h->ptr)->fd, str, strlen(str), 0)); -#endif /* WIN32 */ -} + httpInitialize(); + /* + * Lookup the host... + */ -/* - * 'http_bio_read()' - Read data for OpenSSL. - */ + if (addrlist) + { + myaddrlist = httpAddrCopyList(addrlist); + } + else + { + snprintf(service, sizeof(service), "%d", port); -static int /* O - Bytes read */ -http_bio_read(BIO *h, /* I - BIO data */ - char *buf, /* I - Buffer */ - int size) /* I - Number of bytes to read */ -{ - http_t *http; /* HTTP connection */ + myaddrlist = httpAddrGetList(host, family, service); + } + if (!myaddrlist) + return (NULL); - http = (http_t *)h->ptr; + /* + * Allocate memory for the structure... + */ - if (!http->blocking) + if ((http = calloc(sizeof(http_t), 1)) == NULL) { - /* - * Make sure we have data before we read... - */ + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); + httpAddrFreeList(addrlist); + return (NULL); + } - if (!_httpWait(http, 10000, 0)) - { -#ifdef WIN32 - http->error = WSAETIMEDOUT; -#else - http->error = ETIMEDOUT; -#endif /* WIN32 */ + /* + * Initialize the HTTP data... + */ - return (-1); - } - } + http->mode = mode; + http->activity = time(NULL); + http->addrlist = myaddrlist; + http->blocking = blocking; + http->fd = -1; +#ifdef HAVE_GSSAPI + http->gssctx = GSS_C_NO_CONTEXT; + http->gssname = GSS_C_NO_NAME; +#endif /* HAVE_GSSAPI */ + http->status = HTTP_STATUS_CONTINUE; + http->version = HTTP_VERSION_1_1; - return (recv(http->fd, buf, size, 0)); -} + if (host) + strlcpy(http->hostname, host, sizeof(http->hostname)); + + if (port == 443) /* Always use encryption for https */ + http->encryption = HTTP_ENCRYPTION_ALWAYS; + else + http->encryption = encryption; + http_set_wait(http); -/* - * 'http_bio_write()' - Write data for OpenSSL. - */ + /* + * Return the new structure... + */ -static int /* O - Bytes written */ -http_bio_write(BIO *h, /* I - BIO data */ - const char *buf, /* I - Buffer to write */ - int num) /* I - Number of bytes to write */ -{ - return (send(((http_t *)h->ptr)->fd, buf, num, 0)); + return (http); } -#endif /* HAVE_SSL && HAVE_LIBSSL */ #ifdef DEBUG @@ -3343,7 +3996,7 @@ http_debug_hex(const char *prefix, /* I - Prefix for line */ if (_cups_debug_fd < 0 || _cups_debug_level < 6) return; - DEBUG_printf(("6%s: %d bytes:\n", prefix, bytes)); + DEBUG_printf(("6%s: %d bytes:", prefix, bytes)); snprintf(line, sizeof(line), "6%s: ", prefix); start = line + strlen(line); @@ -3351,16 +4004,16 @@ http_debug_hex(const char *prefix, /* I - Prefix for line */ for (i = 0; i < bytes; i += 16) { for (j = 0, ptr = start; j < 16 && (i + j) < bytes; j ++, ptr += 2) - sprintf(ptr, "%02X", buffer[i + j] & 255); + snprintf(ptr, 3, "%02X", buffer[i + j] & 255); while (j < 16) { - strcpy(ptr, " "); + memcpy(ptr, " ", 3); ptr += 2; j ++; } - strcpy(ptr, " "); + memcpy(ptr, " ", 3); ptr += 2; for (j = 0; j < 16 && (i + j) < bytes; j ++) @@ -3370,7 +4023,7 @@ http_debug_hex(const char *prefix, /* I - Prefix for line */ if (ch < ' ' || ch >= 127) ch = '.'; - *ptr++ = ch; + *ptr++ = (char)ch; } *ptr = '\0'; @@ -3381,139 +4034,240 @@ http_debug_hex(const char *prefix, /* I - Prefix for line */ /* - * 'http_field()' - Return the field index for a field name. + * 'http_read()' - Read a buffer from a HTTP connection. + * + * This function does the low-level read from the socket, retrying and timing + * out as needed. */ -static http_field_t /* O - Field index */ -http_field(const char *name) /* I - String name */ +static ssize_t /* O - Number of bytes read or -1 on error */ +http_read(http_t *http, /* I - HTTP connection */ + char *buffer, /* I - Buffer */ + size_t length) /* I - Maximum bytes to read */ { - int i; /* Looping var */ + ssize_t bytes; /* Bytes read */ - for (i = 0; i < HTTP_FIELD_MAX; i ++) - if (strcasecmp(name, http_fields[i]) == 0) - return ((http_field_t)i); + DEBUG_printf(("http_read(http=%p, buffer=%p, length=" CUPS_LLFMT ")", (void *)http, (void *)buffer, CUPS_LLCAST length)); - return (HTTP_FIELD_UNKNOWN); -} + if (!http->blocking) + { + while (!httpWait(http, http->wait_value)) + { + if (http->timeout_cb && (*http->timeout_cb)(http, http->timeout_data)) + continue; + + DEBUG_puts("2http_read: Timeout."); + return (0); + } + } + DEBUG_printf(("2http_read: Reading %d bytes into buffer.", (int)length)); + do + { #ifdef HAVE_SSL -/* - * 'http_read_ssl()' - Read from a SSL/TLS connection. - */ - -static int /* O - Bytes read */ -http_read_ssl(http_t *http, /* I - Connection to server */ - char *buf, /* I - Buffer to store data */ - int len) /* I - Length of buffer */ -{ -# if defined(HAVE_LIBSSL) - return (SSL_read((SSL *)(http->tls), buf, len)); + if (http->tls) + bytes = _httpTLSRead(http, buffer, (int)length); + else +#endif /* HAVE_SSL */ + bytes = recv(http->fd, buffer, length, 0); -# elif defined(HAVE_GNUTLS) - ssize_t result; /* Return value */ + if (bytes < 0) + { +#ifdef WIN32 + if (WSAGetLastError() != WSAEINTR) + { + http->error = WSAGetLastError(); + return (-1); + } + else if (WSAGetLastError() == WSAEWOULDBLOCK) + { + if (!http->timeout_cb || + !(*http->timeout_cb)(http, http->timeout_data)) + { + http->error = WSAEWOULDBLOCK; + return (-1); + } + } +#else + DEBUG_printf(("2http_read: %s", strerror(errno))); + if (errno == EWOULDBLOCK || errno == EAGAIN) + { + if (http->timeout_cb && !(*http->timeout_cb)(http, http->timeout_data)) + { + http->error = errno; + return (-1); + } + else if (!http->timeout_cb && errno != EAGAIN) + { + http->error = errno; + return (-1); + } + } + else if (errno != EINTR) + { + http->error = errno; + return (-1); + } +#endif /* WIN32 */ + } + } + while (bytes < 0); - result = gnutls_record_recv(http->tls, buf, len); + DEBUG_printf(("2http_read: Read " CUPS_LLFMT " bytes into buffer.", + CUPS_LLCAST bytes)); +#ifdef DEBUG + if (bytes > 0) + http_debug_hex("http_read", buffer, (int)bytes); +#endif /* DEBUG */ - if (result < 0 && !errno) + if (bytes < 0) { - /* - * Convert GNU TLS error to errno value... - */ +#ifdef WIN32 + if (WSAGetLastError() == WSAEINTR) + bytes = 0; + else + http->error = WSAGetLastError(); +#else + if (errno == EINTR || (errno == EAGAIN && !http->timeout_cb)) + bytes = 0; + else + http->error = errno; +#endif /* WIN32 */ + } + else if (bytes == 0) + { + http->error = EPIPE; + return (0); + } - switch (result) - { - case GNUTLS_E_INTERRUPTED : - errno = EINTR; - break; + return (bytes); +} - case GNUTLS_E_AGAIN : - errno = EAGAIN; - break; - default : - errno = EPIPE; - break; - } +/* + * 'http_read_buffered()' - Do a buffered read from a HTTP connection. + * + * This function reads data from the HTTP buffer or from the socket, as needed. + */ - result = -1; - } +static ssize_t /* O - Number of bytes read or -1 on error */ +http_read_buffered(http_t *http, /* I - HTTP connection */ + char *buffer, /* I - Buffer */ + size_t length) /* I - Maximum bytes to read */ +{ + ssize_t bytes; /* Bytes read */ - return ((int)result); -# elif defined(HAVE_CDSASSL) - int result; /* Return value */ - OSStatus error; /* Error info */ - size_t processed; /* Number of bytes processed */ + DEBUG_printf(("http_read_buffered(http=%p, buffer=%p, length=" CUPS_LLFMT ") used=%d", (void *)http, (void *)buffer, CUPS_LLCAST length, http->used)); + if (http->used > 0) + { + if (length > (size_t)http->used) + bytes = (ssize_t)http->used; + else + bytes = (ssize_t)length; - error = SSLRead(http->tls, buf, len, &processed); + DEBUG_printf(("2http_read: Grabbing %d bytes from input buffer.", + (int)bytes)); - switch (error) - { - case 0 : - result = (int)processed; - break; - case errSSLClosedGraceful : - result = 0; - break; - case errSSLWouldBlock : - if (processed) - result = (int)processed; - else - { - result = -1; - errno = EINTR; - } - break; - default : - errno = EPIPE; - result = -1; - break; + memcpy(buffer, http->buffer, (size_t)bytes); + http->used -= (int)bytes; + + if (http->used > 0) + memmove(http->buffer, http->buffer + bytes, (size_t)http->used); } + else + bytes = http_read(http, buffer, length); - return (result); -# elif defined(HAVE_SSPISSL) - return _sspiRead((_sspi_struct_t*) http->tls, buf, len); -# endif /* HAVE_LIBSSL */ + return (bytes); } -#endif /* HAVE_SSL */ -#ifdef HAVE_LIBSSL /* - * 'http_locking_cb()' - Lock/unlock a thread's mutex. + * 'http_read_chunk()' - Read a chunk from a HTTP connection. + * + * This function reads and validates the chunk length, then does a buffered read + * returning the number of bytes placed in the buffer. */ -static void -http_locking_cb(int mode, /* I - Lock mode */ - int type, /* I - Lock type */ - const char *file, /* I - Source file */ - int line) /* I - Line number */ +static ssize_t /* O - Number of bytes read or -1 on error */ +http_read_chunk(http_t *http, /* I - HTTP connection */ + char *buffer, /* I - Buffer */ + size_t length) /* I - Maximum bytes to read */ { - if (mode & CRYPTO_LOCK) - _cupsMutexLock(http_locks + type); - else - _cupsMutexUnlock(http_locks + type); + DEBUG_printf(("http_read_chunk(http=%p, buffer=%p, length=" CUPS_LLFMT ")", (void *)http, (void *)buffer, CUPS_LLCAST length)); + + if (http->data_remaining <= 0) + { + char len[32]; /* Length string */ + + if (!httpGets(len, sizeof(len), http)) + { + DEBUG_puts("1http_read_chunk: Could not get chunk length."); + return (0); + } + + if (!len[0]) + { + DEBUG_puts("1http_read_chunk: Blank chunk length, trying again..."); + if (!httpGets(len, sizeof(len), http)) + { + DEBUG_puts("1http_read_chunk: Could not get chunk length."); + return (0); + } + } + + http->data_remaining = strtoll(len, NULL, 16); + + if (http->data_remaining < 0) + { + DEBUG_printf(("1http_read_chunk: Negative chunk length \"%s\" (" + CUPS_LLFMT ")", len, CUPS_LLCAST http->data_remaining)); + return (0); + } + + DEBUG_printf(("2http_read_chunk: Got chunk length \"%s\" (" CUPS_LLFMT ")", + len, CUPS_LLCAST http->data_remaining)); + + if (http->data_remaining == 0) + { + /* + * 0-length chunk, grab trailing blank line... + */ + + httpGets(len, sizeof(len), http); + } + } + + DEBUG_printf(("2http_read_chunk: data_remaining=" CUPS_LLFMT, + CUPS_LLCAST http->data_remaining)); + + if (http->data_remaining <= 0) + return (0); + else if (length > (size_t)http->data_remaining) + length = (size_t)http->data_remaining; + + return (http_read_buffered(http, buffer, length)); } -#endif /* HAVE_LIBSSL */ /* * 'http_send()' - Send a request with all fields and the trailing blank line. */ -static int /* O - 0 on success, non-zero on error */ -http_send(http_t *http, /* I - Connection to server */ - http_state_t request, /* I - Request code */ - const char *uri) /* I - URI */ +static int /* O - 0 on success, non-zero on error */ +http_send(http_t *http, /* I - HTTP connection */ + http_state_t request, /* I - Request code */ + const char *uri) /* I - URI */ { - int i; /* Looping var */ - char buf[1024]; /* Encoded URI buffer */ - static const char * const codes[] = - { /* Request code strings */ + int i; /* Looping var */ + char buf[1024]; /* Encoded URI buffer */ + const char *value; /* Field value */ + static const char * const codes[] = /* Request code strings */ + { NULL, "OPTIONS", "GET", @@ -3526,12 +4280,13 @@ http_send(http_t *http, /* I - Connection to server */ NULL, "DELETE", "TRACE", - "CLOSE" + "CLOSE", + NULL, + NULL }; - DEBUG_printf(("7http_send(http=%p, request=HTTP_%s, uri=\"%s\")", - http, codes[request], uri)); + DEBUG_printf(("4http_send(http=%p, request=HTTP_%s, uri=\"%s\")", (void *)http, codes[request], uri)); if (http == NULL || uri == NULL) return (-1); @@ -3541,7 +4296,20 @@ http_send(http_t *http, /* I - Connection to server */ */ if (!http->fields[HTTP_FIELD_USER_AGENT][0]) - httpSetField(http, HTTP_FIELD_USER_AGENT, CUPS_MINIMAL); + { + if (http->default_user_agent) + httpSetField(http, HTTP_FIELD_USER_AGENT, http->default_user_agent); + else + httpSetField(http, HTTP_FIELD_USER_AGENT, cupsUserAgent()); + } + + /* + * Set the Accept-Encoding field if it isn't already... + */ + + if (!http->accept_encoding && http->default_accept_encoding) + httpSetField(http, HTTP_FIELD_ACCEPT_ENCODING, + http->default_accept_encoding); /* * Encode the URI as needed... @@ -3553,9 +4321,15 @@ http_send(http_t *http, /* I - Connection to server */ * See if we had an error the last time around; if so, reconnect... */ - if (http->status == HTTP_ERROR || http->status >= HTTP_BAD_REQUEST) - if (httpReconnect(http)) + if (http->fd < 0 || http->status == HTTP_STATUS_ERROR || + http->status >= HTTP_STATUS_BAD_REQUEST) + { + DEBUG_printf(("5http_send: Reconnecting, fd=%d, status=%d, tls_upgrade=%d", + http->fd, http->status, http->tls_upgrade)); + + if (httpReconnect2(http, 30000, NULL)) return (-1); + } /* * Flush any written data that is pending... @@ -3564,7 +4338,7 @@ http_send(http_t *http, /* I - Connection to server */ if (http->wused) { if (httpFlushWrite(http) < 0) - if (httpReconnect(http)) + if (httpReconnect2(http, 30000, NULL)) return (-1); } @@ -3573,37 +4347,44 @@ http_send(http_t *http, /* I - Connection to server */ */ http->state = request; - http->data_encoding = HTTP_ENCODE_FIELDS; + http->data_encoding = HTTP_ENCODING_FIELDS; - if (request == HTTP_POST || request == HTTP_PUT) + if (request == HTTP_STATE_POST || request == HTTP_STATE_PUT) http->state ++; - http->status = HTTP_CONTINUE; + http->status = HTTP_STATUS_CONTINUE; #ifdef HAVE_SSL - if (http->encryption == HTTP_ENCRYPT_REQUIRED && !http->tls) + if (http->encryption == HTTP_ENCRYPTION_REQUIRED && !http->tls) { httpSetField(http, HTTP_FIELD_CONNECTION, "Upgrade"); - httpSetField(http, HTTP_FIELD_UPGRADE, "TLS/1.0,SSL/2.0,SSL/3.0"); + httpSetField(http, HTTP_FIELD_UPGRADE, "TLS/1.2,TLS/1.1,TLS/1.0"); } #endif /* HAVE_SSL */ if (httpPrintf(http, "%s %s HTTP/1.1\r\n", codes[request], buf) < 1) { - http->status = HTTP_ERROR; + http->status = HTTP_STATUS_ERROR; return (-1); } for (i = 0; i < HTTP_FIELD_MAX; i ++) - if (http->fields[i][0] != '\0') + if ((value = httpGetField(http, i)) != NULL && *value) { - DEBUG_printf(("9http_send: %s: %s", http_fields[i], - httpGetField(http, i))); + DEBUG_printf(("5http_send: %s: %s", http_fields[i], value)); - if (httpPrintf(http, "%s: %s\r\n", http_fields[i], - httpGetField(http, i)) < 1) + if (i == HTTP_FIELD_HOST) { - http->status = HTTP_ERROR; + if (httpPrintf(http, "Host: %s:%d\r\n", value, + httpAddrPort(http->hostaddr)) < 1) + { + http->status = HTTP_STATUS_ERROR; + return (-1); + } + } + else if (httpPrintf(http, "%s: %s\r\n", http_fields[i], value) < 1) + { + http->status = HTTP_STATUS_ERROR; return (-1); } } @@ -3611,28 +4392,32 @@ http_send(http_t *http, /* I - Connection to server */ if (http->cookie) if (httpPrintf(http, "Cookie: $Version=0; %s\r\n", http->cookie) < 1) { - http->status = HTTP_ERROR; + http->status = HTTP_STATUS_ERROR; return (-1); } - if (http->expect == HTTP_CONTINUE && - (http->state == HTTP_POST_RECV || http->state == HTTP_PUT_RECV)) + DEBUG_printf(("5http_send: expect=%d, mode=%d, state=%d", http->expect, + http->mode, http->state)); + + if (http->expect == HTTP_STATUS_CONTINUE && http->mode == _HTTP_MODE_CLIENT && + (http->state == HTTP_STATE_POST_RECV || + http->state == HTTP_STATE_PUT_RECV)) if (httpPrintf(http, "Expect: 100-continue\r\n") < 1) { - http->status = HTTP_ERROR; + http->status = HTTP_STATUS_ERROR; return (-1); } if (httpPrintf(http, "\r\n") < 1) { - http->status = HTTP_ERROR; + http->status = HTTP_STATUS_ERROR; return (-1); } if (httpFlushWrite(http) < 0) return (-1); - httpGetLength2(http); + http_set_length(http); httpClearFields(http); /* @@ -3655,575 +4440,116 @@ http_send(http_t *http, /* I - Connection to server */ } -#ifdef HAVE_SSL -# if defined(HAVE_CDSASSL) && defined(HAVE_SECCERTIFICATECOPYDATA) /* - * 'http_set_credentials()' - Set the SSL/TLS credentials. + * 'http_set_length()' - Set the data_encoding and data_remaining values. */ -static int /* O - Status of connection */ -http_set_credentials(http_t *http) /* I - Connection to server */ +static off_t /* O - Remainder or -1 on error */ +http_set_length(http_t *http) /* I - Connection */ { - _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals */ - OSStatus error = 0; /* Error code */ - http_tls_credentials_t credentials = NULL; - /* TLS credentials */ + off_t remaining; /* Remainder */ - DEBUG_printf(("7http_set_credentials(%p)", http)); + DEBUG_printf(("http_set_length(http=%p) mode=%d state=%s", (void *)http, http->mode, httpStateString(http->state))); - /* - * Prefer connection specific credentials... - */ - - if ((credentials = http->tls_credentials) == NULL) - credentials = cg->tls_credentials; - -# if HAVE_SECPOLICYCREATESSL - /* - * Otherwise root around in the user's keychain to see if one can be found... - */ - - if (!credentials) + if ((remaining = httpGetLength2(http)) >= 0) { - CFDictionaryRef query; /* Query dictionary */ - CFTypeRef matches = NULL; /* Matching credentials */ - CFArrayRef dn_array = NULL;/* Distinguished names array */ - CFTypeRef keys[] = { kSecClass, - kSecMatchLimit, - kSecReturnRef }; - /* Keys for dictionary */ - CFTypeRef values[] = { kSecClassCertificate, - kSecMatchLimitOne, - kCFBooleanTrue }; - /* Values for dictionary */ - - /* - * Get the names associated with the server. - */ - - if ((error = SSLCopyDistinguishedNames(http->tls, &dn_array)) != noErr) - { - DEBUG_printf(("4http_set_credentials: SSLCopyDistinguishedNames, error=%d", - (int)error)); - return (error); - } - - /* - * Create a query which will return all identities that can sign and match - * the passed in policy. - */ - - query = CFDictionaryCreate(NULL, - (const void**)(&keys[0]), - (const void**)(&values[0]), - sizeof(keys) / sizeof(keys[0]), - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - if (query) + if (http->mode == _HTTP_MODE_SERVER && + http->state != HTTP_STATE_GET_SEND && + http->state != HTTP_STATE_PUT && + http->state != HTTP_STATE_POST && + http->state != HTTP_STATE_POST_SEND) { - error = SecItemCopyMatching(query, &matches); - DEBUG_printf(("4http_set_credentials: SecItemCopyMatching, error=%d", - (int)error)); - CFRelease(query); + DEBUG_puts("1http_set_length: Not setting data_encoding/remaining."); + return (remaining); } - if (matches) - CFRelease(matches); - - if (dn_array) - CFRelease(dn_array); - } -# endif /* HAVE_SECPOLICYCREATESSL */ - - if (credentials) - { - error = SSLSetCertificate(http->tls, credentials); - DEBUG_printf(("4http_set_credentials: SSLSetCertificate, error=%d", - (int)error)); - } - else - DEBUG_puts("4http_set_credentials: No credentials to set."); - - return (error); -} -# endif /* HAVE_CDSASSL && HAVE_SECCERTIFICATECOPYDATA */ - - -/* - * 'http_setup_ssl()' - Set up SSL/TLS support on a connection. - */ - -static int /* O - Status of connection */ -http_setup_ssl(http_t *http) /* I - Connection to server */ -{ - _cups_globals_t *cg = _cupsGlobals(); - /* Pointer to library globals */ - int any_root; /* Allow any root */ - -# ifdef HAVE_LIBSSL - SSL_CTX *context; /* Context for encryption */ - BIO *bio; /* BIO data */ -# elif defined(HAVE_GNUTLS) - gnutls_certificate_client_credentials *credentials; - /* TLS credentials */ -# elif defined(HAVE_CDSASSL) - OSStatus error; /* Error code */ - const char *message = NULL; /* Error message */ - char *hostname; /* Hostname */ -# ifdef HAVE_SECCERTIFICATECOPYDATA - cups_array_t *credentials; /* Credentials array */ - cups_array_t *names; /* CUPS distinguished names */ - CFArrayRef dn_array; /* CF distinguished names array */ - CFIndex count; /* Number of credentials */ - CFDataRef data; /* Certificate data */ - int i; /* Looping var */ - http_credential_t - *credential; /* Credential data */ -# endif /* HAVE_SECCERTIFICATECOPYDATA */ -# elif defined(HAVE_SSPISSL) - TCHAR username[256]; /* Username returned from GetUserName() */ - TCHAR commonName[256]; /* Common name for certificate */ - DWORD dwSize; /* 32 bit size */ -# endif /* HAVE_LIBSSL */ - - - DEBUG_printf(("7http_setup_ssl(http=%p)", http)); - - /* - * Always allow self-signed certificates for the local loopback address... - */ - - if (httpAddrLocalhost(http->hostaddr)) - any_root = 1; - else - any_root = cg->any_root; - -# ifdef HAVE_LIBSSL - context = SSL_CTX_new(SSLv23_client_method()); - - SSL_CTX_set_options(context, SSL_OP_NO_SSLv2); /* Only use SSLv3 or TLS */ - - bio = BIO_new(_httpBIOMethods()); - BIO_ctrl(bio, BIO_C_SET_FILE_PTR, 0, (char *)http); - - http->tls = SSL_new(context); - SSL_set_bio(http->tls_credentials, bio, bio); - - if (SSL_connect(http->tls) != 1) - { -# ifdef DEBUG - unsigned long error; /* Error code */ - - while ((error = ERR_get_error()) != 0) - DEBUG_printf(("8http_setup_ssl: %s", ERR_error_string(error, NULL))); -# endif /* DEBUG */ - - SSL_CTX_free(context); - SSL_free(http->tls); - http->tls = NULL; - -# ifdef WIN32 - http->error = WSAGetLastError(); -# else - http->error = errno; -# endif /* WIN32 */ - http->status = HTTP_ERROR; - - return (HTTP_ERROR); - } - -# elif defined(HAVE_GNUTLS) - credentials = (gnutls_certificate_client_credentials *) - malloc(sizeof(gnutls_certificate_client_credentials)); - if (credentials == NULL) - { - http->error = errno; - http->status = HTTP_ERROR; - - return (-1); - } - - gnutls_certificate_allocate_credentials(credentials); - - gnutls_init(&http->tls, GNUTLS_CLIENT); - gnutls_set_default_priority(http->tls); - gnutls_credentials_set(http->tls, GNUTLS_CRD_CERTIFICATE, *credentials); - gnutls_transport_set_ptr(http->tls, (gnutls_transport_ptr)http); - gnutls_transport_set_pull_function(http->tls, _httpReadGNUTLS); - gnutls_transport_set_push_function(http->tls, _httpWriteGNUTLS); - - if ((gnutls_handshake(http->tls)) != GNUTLS_E_SUCCESS) - { - http->error = errno; - http->status = HTTP_ERROR; - - gnutls_deinit(http->tls); - gnutls_certificate_free_credentials(*credentials); - free(credentials); - http->tls = NULL; - - return (-1); - } - - http->tls_credentials = credentials; - -# elif defined(HAVE_CDSASSL) - if ((error = SSLNewContext(false, &http->tls))) - { - http->error = error; - http->status = HTTP_ERROR; - - return (-1); - } - - error = SSLSetConnection(http->tls, http); - DEBUG_printf(("4http_setup_ssl: SSLSetConnection, error=%d", (int)error)); - - if (!error) - { - error = SSLSetIOFuncs(http->tls, _httpReadCDSA, _httpWriteCDSA); - DEBUG_printf(("4http_setup_ssl: SSLSetIOFuncs, error=%d", (int)error)); - } - - if (!error) - { - error = SSLSetProtocolVersionEnabled(http->tls, kSSLProtocol2, false); - DEBUG_printf(("4http_setup_ssl: SSLSetProtocolVersionEnabled, error=%d", - (int)error)); - } - - if (!error) - { - error = SSLSetAllowsAnyRoot(http->tls, any_root); - DEBUG_printf(("4http_setup_ssl: SSLSetAllowsAnyRoot(%d), error=%d", - any_root, (int)error)); - } - - if (!error) - { - error = SSLSetAllowsExpiredCerts(http->tls, cg->expired_certs); - DEBUG_printf(("4http_setup_ssl: SSLSetAllowsExpiredCerts(%d), error=%d", - cg->expired_certs, (int)error)); - } - - if (!error) - { - error = SSLSetAllowsExpiredRoots(http->tls, cg->expired_root); - DEBUG_printf(("4http_setup_ssl: SSLSetAllowsExpiredRoots(%d), error=%d", - cg->expired_root, (int)error)); - } - -# ifdef HAVE_SECCERTIFICATECOPYDATA - if (!error) - { - if (cg->client_cert_cb) + if (!_cups_strcasecmp(http->fields[HTTP_FIELD_TRANSFER_ENCODING], + "chunked")) { - error = SSLSetSessionOption(http->tls, - kSSLSessionOptionBreakOnCertRequested, true); - DEBUG_printf(("4http_setup_ssl: kSSLSessionOptionBreakOnCertRequested, " - "error=%d", (int)error)); + DEBUG_puts("1http_set_length: Setting data_encoding to " + "HTTP_ENCODING_CHUNKED."); + http->data_encoding = HTTP_ENCODING_CHUNKED; } else { - error = http_set_credentials(http); - DEBUG_printf(("4http_setup_ssl: http_set_credentials, error=%d", - (int)error)); - } - } - - /* - * If there's a server certificate callback installed let it evaluate the - * certificate(s) during the handshake... - */ - - if (!error && cg->server_cert_cb != NULL) - { - error = SSLSetEnableCertVerify(http->tls, false); - DEBUG_printf(("4http_setup_ssl: SSLSetEnableCertVerify, error=%d", - (int)error)); - - if (!error) - { - error = SSLSetSessionOption(http->tls, - kSSLSessionOptionBreakOnServerAuth, true); - DEBUG_printf(("4http_setup_ssl: kSSLSessionOptionBreakOnServerAuth, " - "error=%d", (int)error)); - } - } -# endif /* HAVE_SECCERTIFICATECOPYDATA */ - - if (!error) - { - hostname = httpAddrLocalhost(http->hostaddr) ? "localhost" : http->hostname; - error = SSLSetPeerDomainName(http->tls, hostname, strlen(hostname)); - - DEBUG_printf(("4http_setup_ssl: SSLSetPeerDomainName, error=%d", - (int)error)); - } - - if (!error) - { - int done = 0; /* Are we done yet? */ - - while (!error && !done) - { - error = SSLHandshake(http->tls); - - DEBUG_printf(("4_httpWait: SSLHandshake returned %d.", (int)error)); - - switch (error) - { - case noErr : - done = 1; - break; - - case errSSLWouldBlock : - usleep(1000); - break; - -# ifdef HAVE_SECCERTIFICATECOPYDATA - case errSSLServerAuthCompleted : - error = 0; - if (cg->server_cert_cb) - { - error = httpCopyCredentials(http, &credentials); - if (!error) - { - error = (cg->server_cert_cb)(http, http->tls, credentials, - cg->server_cert_data); - httpFreeCredentials(credentials); - } - - DEBUG_printf(("4_httpWait: Server certificate callback returned " - "%d.", (int)error)); - } - break; - - case errSSLClientCertRequested : - error = 0; - - if (cg->client_cert_cb) - { - names = NULL; - if (!(error = SSLCopyDistinguishedNames(http->tls, &dn_array)) && - dn_array) - { - if ((names = cupsArrayNew(NULL, NULL)) != NULL) - { - for (i = 0, count = CFArrayGetCount(dn_array); i < count; i++) - { - data = (CFDataRef)CFArrayGetValueAtIndex(dn_array, i); - - if ((credential = malloc(sizeof(*credential)))) - { - credential->datalen = CFDataGetLength(data); - if ((credential->data = malloc(credential->datalen))) - { - memcpy((void *)credential->data, CFDataGetBytePtr(data), - credential->datalen); - cupsArrayAdd(names, credential); - } - } - } - } - - CFRelease(dn_array); - } - - if (!error) - { - error = (cg->client_cert_cb)(http, http->tls, names, - cg->client_cert_data); - - DEBUG_printf(("4_httpWait: Client certificate callback " - "returned %d.", (int)error)); - } - - httpFreeCredentials(names); - } - break; -# endif /* HAVE_SECCERTIFICATECOPYDATA */ - - case errSSLUnknownRootCert : - message = _("Unable to establish a secure connection to host " - "(untrusted certificate)."); - break; - - case errSSLNoRootCert : - message = _("Unable to establish a secure connection to host " - "(self-signed certificate)."); - break; - - case errSSLCertExpired : - message = _("Unable to establish a secure connection to host " - "(expired certificate)."); - break; - - case errSSLCertNotYetValid : - message = _("Unable to establish a secure connection to host " - "(certificate not yet valid)."); - break; - - case errSSLHostNameMismatch : - message = _("Unable to establish a secure connection to host " - "(host name mismatch)."); - break; - - case errSSLXCertChainInvalid : - message = _("Unable to establish a secure connection to host " - "(certificate chain invalid)."); - break; - - case errSSLConnectionRefused : - message = _("Unable to establish a secure connection to host " - "(peer dropped connection before responding)."); - break; - - default : - break; - } + DEBUG_puts("1http_set_length: Setting data_encoding to " + "HTTP_ENCODING_LENGTH."); + http->data_encoding = HTTP_ENCODING_LENGTH; } - } - - if (error) - { - http->error = error; - http->status = HTTP_ERROR; - errno = ECONNREFUSED; - - SSLDisposeContext(http->tls); - http->tls = NULL; - /* - * If an error string wasn't set by the callbacks use a generic one... - */ - - if (!message) -#ifdef HAVE_CSSMERRORSTRING - message = cssmErrorString(error); -#else - message = _("Unable to establish a secure connection to host."); -#endif /* HAVE_CSSMERRORSTRING */ - - _cupsSetError(IPP_PKI_ERROR, message, 1); - - return (-1); - } - -# elif defined(HAVE_SSPISSL) - http->tls = _sspiAlloc(); - - if (!http->tls) - return (-1); - - http->tls->sock = http->fd; - dwSize = sizeof(username) / sizeof(TCHAR); - GetUserName(username, &dwSize); - _sntprintf_s(commonName, sizeof(commonName) / sizeof(TCHAR), - sizeof(commonName) / sizeof(TCHAR), TEXT("CN=%s"), username); - - if (!_sspiGetCredentials(http->tls_credentials, L"ClientContainer", - commonName, FALSE)) - { - _sspiFree(http->tls_credentials); - http->tls_credentials = NULL; - return (-1); - } - - _sspiSetAllowsAnyRoot(http->tls_credentials, TRUE); - _sspiSetAllowsExpiredCerts(http->tls_credentials, TRUE); + DEBUG_printf(("1http_set_length: Setting data_remaining to " CUPS_LLFMT ".", + CUPS_LLCAST remaining)); + http->data_remaining = remaining; - if (!_sspiConnect(http->tls_credentials, http->hostname)) - { - _sspiFree(http->tls_credentials); - http->tls_credentials = NULL; - return (-1); + if (remaining <= INT_MAX) + http->_data_remaining = (int)remaining; + else + http->_data_remaining = INT_MAX; } -# endif /* HAVE_CDSASSL */ - return (0); + return (remaining); } -#endif /* HAVE_SSL */ - -#ifdef HAVE_SSL /* - * 'http_shutdown_ssl()' - Shut down SSL/TLS on a connection. + * 'http_set_timeout()' - Set the socket timeout values. */ static void -http_shutdown_ssl(http_t *http) /* I - Connection to server */ +http_set_timeout(int fd, /* I - File descriptor */ + double timeout) /* I - Timeout in seconds */ { -# ifdef HAVE_LIBSSL - SSL_CTX *context; /* Context for encryption */ - - context = SSL_get_SSL_CTX(http->tls_credentials); - - SSL_shutdown(http->tls_credentials); - SSL_CTX_free(context); - SSL_free(http->tls_credentials); - -# elif defined(HAVE_GNUTLS) - gnutls_certificate_client_credentials *credentials; - /* TLS credentials */ - - credentials = (gnutls_certificate_client_credentials *)(http->tls_credentials); - - gnutls_bye(http->tls, GNUTLS_SHUT_RDWR); - gnutls_deinit(http->tls); - gnutls_certificate_free_credentials(*credentials); - free(credentials); - -# elif defined(HAVE_CDSASSL) - while (SSLClose(http->tls) == errSSLWouldBlock) - usleep(1000); +#ifdef WIN32 + DWORD tv = (DWORD)(timeout * 1000); + /* Timeout in milliseconds */ - SSLDisposeContext(http->tls); + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CUPS_SOCAST &tv, sizeof(tv)); + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, CUPS_SOCAST &tv, sizeof(tv)); - if (http->tls_credentials) - CFRelease(http->tls_credentials); +#else + struct timeval tv; /* Timeout in secs and usecs */ -# elif defined(HAVE_SSPISSL) - _sspiFree(http->tls_credentials); -# endif /* HAVE_LIBSSL */ + tv.tv_sec = (int)timeout; + tv.tv_usec = (int)(1000000 * fmod(timeout, 1.0)); - http->tls = NULL; - http->tls_credentials = NULL; + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CUPS_SOCAST &tv, sizeof(tv)); + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, CUPS_SOCAST &tv, sizeof(tv)); +#endif /* WIN32 */ } -#endif /* HAVE_SSL */ -#ifdef HAVE_LIBSSL /* - * 'http_threadid_cb()' - Return the current thread ID. + * 'http_set_wait()' - Set the default wait value for reads. */ -static unsigned long /* O - Thread ID */ -http_threadid_cb(void) +static void +http_set_wait(http_t *http) /* I - HTTP connection */ { -# ifdef HAVE_PTHREAD_H - return ((unsigned long)pthread_self()); -# else - return (0); -# endif /* HAVE_PTHREAD_H */ + if (http->blocking) + { + http->wait_value = (int)(http->timeout_value * 1000); + + if (http->wait_value <= 0) + http->wait_value = 60000; + } + else + http->wait_value = 10000; } -#endif /* HAVE_LIBSSL */ #ifdef HAVE_SSL /* - * 'http_upgrade()' - Force upgrade to TLS encryption. + * 'http_tls_upgrade()' - Force upgrade to TLS encryption. */ static int /* O - Status of connection */ -http_upgrade(http_t *http) /* I - Connection to server */ +http_tls_upgrade(http_t *http) /* I - HTTP connection */ { int ret; /* Return value */ http_t myhttp; /* Local copy of HTTP data */ - DEBUG_printf(("7http_upgrade(%p)", http)); + DEBUG_printf(("7http_tls_upgrade(%p)", (void *)http)); /* * Flush the connection to make sure any previous "Upgrade" message @@ -4244,11 +4570,12 @@ http_upgrade(http_t *http) /* I - Connection to server */ * encryption on the link... */ + http->tls_upgrade = 1; http->field_authorization = NULL; /* Don't free the auth string */ httpClearFields(http); httpSetField(http, HTTP_FIELD_CONNECTION, "upgrade"); - httpSetField(http, HTTP_FIELD_UPGRADE, "TLS/1.2, TLS/1.1, TLS/1.0, SSL/3.0"); + httpSetField(http, HTTP_FIELD_UPGRADE, "TLS/1.2,TLS/1.1,TLS/1.0"); if ((ret = httpOptions(http, "*")) == 0) { @@ -4256,7 +4583,7 @@ http_upgrade(http_t *http) /* I - Connection to server */ * Wait for the secure connection... */ - while (httpUpdate(http) == HTTP_CONTINUE); + while (httpUpdate(http) == HTTP_STATUS_CONTINUE); } /* @@ -4270,6 +4597,7 @@ http_upgrade(http_t *http) /* I - Connection to server */ http->expect = myhttp.expect; http->field_authorization = myhttp.field_authorization; http->digest_tries = myhttp.digest_tries; + http->tls_upgrade = 0; /* * See if we actually went secure... @@ -4281,13 +4609,10 @@ http_upgrade(http_t *http) /* I - Connection to server */ * Server does not support HTTP upgrade... */ - DEBUG_puts("8http_upgrade: Server does not support HTTP upgrade!"); + DEBUG_puts("8http_tls_upgrade: Server does not support HTTP upgrade!"); -# ifdef WIN32 - closesocket(http->fd); -# else - close(http->fd); -# endif + _cupsSetError(IPP_STATUS_ERROR_CUPS_PKI, _("Encryption is not supported."), 1); + httpAddrClose(NULL, http->fd); http->fd = -1; @@ -4303,29 +4628,90 @@ http_upgrade(http_t *http) /* I - Connection to server */ * 'http_write()' - Write a buffer to a HTTP connection. */ -static int /* O - Number of bytes written */ -http_write(http_t *http, /* I - Connection to server */ +static ssize_t /* O - Number of bytes written */ +http_write(http_t *http, /* I - HTTP connection */ const char *buffer, /* I - Buffer for data */ - int length) /* I - Number of bytes to write */ + size_t length) /* I - Number of bytes to write */ { - int tbytes, /* Total bytes sent */ - bytes; /* Bytes sent */ + ssize_t tbytes, /* Total bytes sent */ + bytes; /* Bytes sent */ - DEBUG_printf(("2http_write(http=%p, buffer=%p, length=%d)", http, buffer, - length)); + DEBUG_printf(("2http_write(http=%p, buffer=%p, length=" CUPS_LLFMT ")", (void *)http, (void *)buffer, CUPS_LLCAST length)); http->error = 0; tbytes = 0; while (length > 0) { + DEBUG_printf(("3http_write: About to write %d bytes.", (int)length)); + + if (http->timeout_cb) + { +#ifdef HAVE_POLL + struct pollfd pfd; /* Polled file descriptor */ +#else + fd_set output_set; /* Output ready for write? */ + struct timeval timeout; /* Timeout value */ +#endif /* HAVE_POLL */ + int nfds; /* Result from select()/poll() */ + + do + { +#ifdef HAVE_POLL + pfd.fd = http->fd; + pfd.events = POLLOUT; + + while ((nfds = poll(&pfd, 1, http->wait_value)) < 0 && + (errno == EINTR || errno == EAGAIN)) + /* do nothing */; + +#else + do + { + FD_ZERO(&output_set); + FD_SET(http->fd, &output_set); + + timeout.tv_sec = http->wait_value / 1000; + timeout.tv_usec = 1000 * (http->wait_value % 1000); + + nfds = select(http->fd + 1, NULL, &output_set, NULL, &timeout); + } +# ifdef WIN32 + while (nfds < 0 && (WSAGetLastError() == WSAEINTR || + WSAGetLastError() == WSAEWOULDBLOCK)); +# else + while (nfds < 0 && (errno == EINTR || errno == EAGAIN)); +# endif /* WIN32 */ +#endif /* HAVE_POLL */ + + if (nfds < 0) + { + http->error = errno; + return (-1); + } + else if (nfds == 0 && !(*http->timeout_cb)(http, http->timeout_data)) + { +#ifdef WIN32 + http->error = WSAEWOULDBLOCK; +#else + http->error = EWOULDBLOCK; +#endif /* WIN32 */ + return (-1); + } + } + while (nfds <= 0); + } + #ifdef HAVE_SSL if (http->tls) - bytes = http_write_ssl(http, buffer, length); + bytes = _httpTLSWrite(http, buffer, (int)length); else #endif /* HAVE_SSL */ bytes = send(http->fd, buffer, length, 0); + DEBUG_printf(("3http_write: Write of " CUPS_LLFMT " bytes returned " + CUPS_LLFMT ".", CUPS_LLCAST length, CUPS_LLCAST bytes)); + if (bytes < 0) { #ifdef WIN32 @@ -4372,14 +4758,14 @@ http_write(http_t *http, /* I - Connection to server */ buffer += bytes; tbytes += bytes; - length -= bytes; + length -= (size_t)bytes; } #ifdef DEBUG - http_debug_hex("http_write", buffer - tbytes, tbytes); + http_debug_hex("http_write", buffer - tbytes, (int)tbytes); #endif /* DEBUG */ - DEBUG_printf(("3http_write: Returning %d.", tbytes)); + DEBUG_printf(("3http_write: Returning " CUPS_LLFMT ".", CUPS_LLCAST tbytes)); return (tbytes); } @@ -4389,130 +4775,39 @@ http_write(http_t *http, /* I - Connection to server */ * 'http_write_chunk()' - Write a chunked buffer. */ -static int /* O - Number bytes written */ -http_write_chunk(http_t *http, /* I - Connection to server */ +static ssize_t /* O - Number bytes written */ +http_write_chunk(http_t *http, /* I - HTTP connection */ const char *buffer, /* I - Buffer to write */ - int length) /* I - Length of buffer */ + size_t length) /* I - Length of buffer */ { - char header[255]; /* Chunk header */ - int bytes; /* Bytes written */ + char header[16]; /* Chunk header */ + ssize_t bytes; /* Bytes written */ - DEBUG_printf(("7http_write_chunk(http=%p, buffer=%p, length=%d)", - http, buffer, length)); + DEBUG_printf(("7http_write_chunk(http=%p, buffer=%p, length=" CUPS_LLFMT ")", (void *)http, (void *)buffer, CUPS_LLCAST length)); /* * Write the chunk header, data, and trailer. */ - sprintf(header, "%x\r\n", length); - if (http_write(http, header, (int)strlen(header)) < 0) + snprintf(header, sizeof(header), "%x\r\n", (unsigned)length); + if (http_write(http, header, strlen(header)) < 0) { - DEBUG_puts("8http_write_chunk: http_write of length failed!"); + DEBUG_puts("8http_write_chunk: http_write of length failed."); return (-1); } if ((bytes = http_write(http, buffer, length)) < 0) { - DEBUG_puts("8http_write_chunk: http_write of buffer failed!"); + DEBUG_puts("8http_write_chunk: http_write of buffer failed."); return (-1); } if (http_write(http, "\r\n", 2) < 0) { - DEBUG_puts("8http_write_chunk: http_write of CR LF failed!"); + DEBUG_puts("8http_write_chunk: http_write of CR LF failed."); return (-1); } return (bytes); } - - -#ifdef HAVE_SSL -/* - * 'http_write_ssl()' - Write to a SSL/TLS connection. - */ - -static int /* O - Bytes written */ -http_write_ssl(http_t *http, /* I - Connection to server */ - const char *buf, /* I - Buffer holding data */ - int len) /* I - Length of buffer */ -{ - ssize_t result; /* Return value */ - - - DEBUG_printf(("2http_write_ssl(http=%p, buf=%p, len=%d)", http, buf, len)); - -# if defined(HAVE_LIBSSL) - result = SSL_write((SSL *)(http->tls), buf, len); - -# elif defined(HAVE_GNUTLS) - result = gnutls_record_send(http->tls, buf, len); - - if (result < 0 && !errno) - { - /* - * Convert GNU TLS error to errno value... - */ - - switch (result) - { - case GNUTLS_E_INTERRUPTED : - errno = EINTR; - break; - - case GNUTLS_E_AGAIN : - errno = EAGAIN; - break; - - default : - errno = EPIPE; - break; - } - - result = -1; - } - -# elif defined(HAVE_CDSASSL) - OSStatus error; /* Error info */ - size_t processed; /* Number of bytes processed */ - - - error = SSLWrite(http->tls, buf, len, &processed); - - switch (error) - { - case 0 : - result = (int)processed; - break; - case errSSLClosedGraceful : - result = 0; - break; - case errSSLWouldBlock : - if (processed) - result = (int)processed; - else - { - result = -1; - errno = EINTR; - } - break; - default : - errno = EPIPE; - result = -1; - break; - } -# elif defined(HAVE_SSPISSL) - return _sspiWrite((_sspi_struct_t*) http->tls, (void*) buf, len); -# endif /* HAVE_LIBSSL */ - - DEBUG_printf(("3http_write_ssl: Returning %d.", (int)result)); - - return ((int)result); -} -#endif /* HAVE_SSL */ - - -/* - * End of "$Id: http.c 7850 2008-08-20 00:07:25Z mike $". - */