X-Git-Url: http://git.ipfire.org/?p=thirdparty%2Fcups.git;a=blobdiff_plain;f=cups%2Fhttp.c;h=3db98fca2199eedf0beec728d55ea0223db9a8b5;hp=834f61c4c799d63b1830ac1769668017e5716b58;hb=87e9839293534bf13dc3d1bf4fc285914770cd12;hpb=0268488e2f31bbec0c6b02ff7b7594b3191e6177 diff --git a/cups/http.c b/cups/http.c index 834f61c4c..3db98fca2 100644 --- a/cups/http.c +++ b/cups/http.c @@ -1,117 +1,21 @@ /* - * "$Id: http.c 7850 2008-08-20 00:07:25Z mike $" + * "$Id$" * - * HTTP routines for CUPS. + * HTTP routines for CUPS. * - * Copyright 2007-2011 by Apple Inc. - * Copyright 1997-2007 by Easy Software Products, all rights reserved. + * Copyright 2007-2013 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 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. */ /* @@ -120,6 +24,7 @@ #include "cups-private.h" #include +#include #ifdef WIN32 # include #else @@ -128,57 +33,48 @@ # 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); #ifdef HAVE_SSL static int http_read_ssl(http_t *http, char *buf, int len); -# ifdef HAVE_CDSASSL static int http_set_credentials(http_t *http); -# endif /* HAVE_CDSASSL */ +#endif /* HAVE_SSL */ +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_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 */ #endif /* HAVE_SSL */ @@ -214,27 +110,11 @@ 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) @@ -265,12 +145,101 @@ static BIO_METHOD http_bio_methods = #endif /* HAVE_SSL && HAVE_LIBSSL */ +/* + * 'httpAcceptConnection()' - Accept a new HTTP client connection from the + * specified listening socket. + * + * @since CUPS 1.7/OS X 10.9@ + */ + +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 */ + + + /* + * 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); +} + + /* * 'httpAddCredential()' - Allocates and adds a single credential to an array. * * Use @code cupsArrayNew(NULL, NULL)@ to create a credentials array. * - * @since CUPS 1.5@ + * @since CUPS 1.5/OS X 10.7@ */ int /* O - 0 on success, -1 on error */ @@ -318,11 +287,14 @@ _httpBIOMethods(void) */ 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); + } } @@ -331,7 +303,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)); } @@ -340,11 +312,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/OS X 10.3@ */ void -httpClearCookie(http_t *http) /* I - Connection to server */ +httpClearCookie(http_t *http) /* I - HTTP connection */ { if (!http) return; @@ -362,15 +334,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)", 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) { @@ -378,6 +356,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; } } @@ -388,7 +384,7 @@ 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 */ @@ -444,7 +440,7 @@ httpClose(http_t *http) /* I - Connection to server */ /* * 'httpConnect()' - Connect to a HTTP server. * - * This function is deprecated - use @link httpConnectEncrypt@ instead. + * This function is deprecated - use @link httpConnect2@ instead. * * @deprecated@ */ @@ -453,70 +449,101 @@ 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/OS X 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, + addrlist, family, encryption, blocking, msec, cancel)); /* * Create the HTTP structure... */ - if ((http = _httpCreate(host, port, 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); } +/* + * 'httpConnectEncrypt()' - Connect to a HTTP server using encryption. + * + * This function is now deprecated. Please use the @link httpConnect2@ function + * instead. + * + * @deprecated@ + */ + +http_t * /* O - New HTTP connection */ +httpConnectEncrypt( + const char *host, /* I - Host to connect to */ + int port, /* I - Port number */ + http_encryption_t encryption) /* I - Type of encryption to use */ +{ + DEBUG_printf(("httpConnectEncrypt(host=\"%s\", port=%d, encryption=%d)", + host, port, encryption)); + + return (httpConnect2(host, port, NULL, AF_UNSPEC, encryption, 1, 30000, + NULL)); +} + + /* * 'httpCopyCredentials()' - Copy the credentials associated with an encrypted * connection. * - * @since CUPS 1.5@ + * @since CUPS 1.5/OS X 10.7@ */ int /* O - Status of call (0 = success) */ httpCopyCredentials( - http_t *http, /* I - Connection to server */ + http_t *http, /* I - HTTP connection */ cups_array_t **credentials) /* O - Array of credentials */ { # ifdef HAVE_LIBSSL # elif defined(HAVE_GNUTLS) # elif defined(HAVE_CDSASSL) OSStatus error; /* Error code */ + SecTrustRef peerTrust; /* Peer trust reference */ CFIndex count; /* Number of credentials */ - CFArrayRef peerCerts; /* Peer certificates */ SecCertificateRef secCert; /* Certificate reference */ CFDataRef data; /* Certificate data */ int i; /* Looping var */ @@ -537,13 +564,15 @@ httpCopyCredentials( return (-1); # elif defined(HAVE_CDSASSL) - if (!(error = SSLCopyPeerCertificates(http->tls, &peerCerts)) && peerCerts) + if (!(error = SSLCopyPeerTrust(http->tls, &peerTrust)) && peerTrust) { if ((*credentials = cupsArrayNew(NULL, NULL)) != NULL) { - for (i = 0, count = CFArrayGetCount(peerCerts); i < count; i++) + count = SecTrustGetCertificateCount(peerTrust); + + for (i = 0; i < count; i ++) { - secCert = (SecCertificateRef)CFArrayGetValueAtIndex(peerCerts, i); + secCert = SecTrustGetCertificateAtIndex(peerTrust, i); if ((data = SecCertificateCopyData(secCert))) { httpAddCredential(*credentials, CFDataGetBytePtr(data), @@ -553,23 +582,26 @@ httpCopyCredentials( } } - CFRelease(peerCerts); + CFRelease(peerTrust); } return (error); # elif defined(HAVE_SSPISSL) return (-1); + +# else + return (-1); # endif /* HAVE_LIBSSL */ } /* - * '_httpConvertCredentials()' - Convert credentials to the internal format. + * '_httpCreateCredentials()' - Create credentials in the internal format. */ http_tls_credentials_t /* O - Internal credentials */ -_httpConvertCredentials( +_httpCreateCredentials( cups_array_t *credentials) /* I - Array of credentials */ { if (!credentials) @@ -597,7 +629,7 @@ _httpConvertCredentials( credential; credential = (http_credential_t *)cupsArrayNext(credentials)) { - if ((data = CFDataCreate(kCFAllocatorDefault, credential->data, + if ((data = CFDataCreate(kCFAllocatorDefault, credential->data, credential->datalen))) { if ((secCert = SecCertificateCreateWithData(kCFAllocatorDefault, data)) @@ -622,87 +654,15 @@ _httpConvertCredentials( } -/* - * '_httpCreate()' - Create an unconnected HTTP connection. - */ - -http_t * /* O - HTTP connection */ -_httpCreate( - const char *host, /* I - Hostname */ - int port, /* I - Port number */ - http_encryption_t encryption, /* I - Encryption to use */ - int family) /* I - Address family or AF_UNSPEC */ -{ - http_t *http; /* New HTTP connection */ - http_addrlist_t *addrlist; /* Host address data */ - char service[255]; /* Service name */ - - - DEBUG_printf(("4_httpCreate(host=\"%s\", port=%d, encryption=%d)", - host, port, encryption)); - - if (!host) - return (NULL); - - httpInitialize(); - - /* - * Lookup the host... - */ - - sprintf(service, "%d", port); - - 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); -} - - /* * 'httpDelete()' - Send a DELETE request to the server. */ 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)); } @@ -711,18 +671,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); #endif /* HAVE_SSL */ -#ifdef WIN32 - closesocket(http->fd); -#else - close(http->fd); -#endif /* WIN32 */ + httpAddrClose(NULL, http->fd); http->fd = -1; } @@ -733,7 +689,7 @@ _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)); @@ -744,15 +700,15 @@ httpEncryption(http_t *http, /* I - Connection to server */ 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) + 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_upgrade(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); @@ -765,7 +721,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); @@ -774,12 +730,31 @@ 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 */ @@ -787,7 +762,14 @@ httpFlush(http_t *http) /* I - Connection to server */ DEBUG_printf(("httpFlush(http=%p), state=%s", http, - http_states[http->state])); + 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()... @@ -810,24 +792,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); #endif /* HAVE_SSL */ -#ifdef WIN32 - closesocket(http->fd); -#else - close(http->fd); -#endif /* WIN32 */ + httpAddrClose(NULL, http->fd); http->fd = -1; } @@ -837,16 +823,17 @@ 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/OS X 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 */ - DEBUG_printf(("httpFlushWrite(http=%p)", http)); + DEBUG_printf(("httpFlushWrite(http=%p) data_encoding=%d", http, + http ? http->data_encoding : -1)); if (!http || !http->wused) { @@ -855,14 +842,14 @@ httpFlushWrite(http_t *http) /* I - Connection to server */ return (0); } - if (http->data_encoding == HTTP_ENCODE_CHUNKED) + if (http->data_encoding == HTTP_ENCODING_CHUNKED) bytes = http_write_chunk(http, http->wbuffer, http->wused); else bytes = http_write(http, http->wbuffer, http->wused); http->wused = 0; - DEBUG_printf(("1httpFlushWrite: Returning %d.", bytes)); + DEBUG_printf(("1httpFlushWrite: Returning %d, errno=%d.", bytes, errno)); return (bytes); } @@ -924,10 +911,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@ + */ + +time_t /* O - Time of last read or write */ +httpGetActivity(http_t *http) /* I - HTTP connection */ +{ + return (http ? http->activity : 0); } @@ -939,11 +941,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/OS X 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); @@ -955,20 +957,108 @@ 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/OS X 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/OS X 10.9@ + */ + +const char * /* O - Content-Coding value or + @code NULL@ for the identity + coding. */ +httpGetContentEncoding(http_t *http) /* I - Connection to client/server */ +{ +#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/OS X 10.3@ */ const char * /* O - Cookie data or NULL */ @@ -979,40 +1069,103 @@ httpGetCookie(http_t *http) /* I - HTTP connecion */ /* - * 'httpGetFd()' - Get the file descriptor associated with a connection. + * 'httpGetEncryption()' - Get the current encryption mode of a connection. * - * @since CUPS 1.2/Mac OS X 10.5@ + * 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@ */ -int /* O - File descriptor or -1 if none */ -httpGetFd(http_t *http) /* I - Connection to server */ +http_encryption_t /* O - Current encryption mode */ +httpGetEncryption(http_t *http) /* I - HTTP connection */ { - return (http ? http->fd : -1); + return (http ? http->encryption : HTTP_ENCRYPTION_IF_REQUESTED); } /* - * 'httpGetField()' - Get a field value from a request/response. + * '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/OS X 10.9@ */ -const char * /* O - Field value */ -httpGetField(http_t *http, /* I - Connection to server */ - http_field_t field) /* I - Field to get */ +http_status_t /* O - Expect: status, if any */ +httpGetExpect(http_t *http) /* I - Connection to client */ { - if (!http || field <= HTTP_FIELD_UNKNOWN || field >= HTTP_FIELD_MAX) - return (NULL); - else if (field == HTTP_FIELD_AUTHORIZATION && - http->field_authorization) - { - /* - * Special case for WWW-Authenticate: as its contents can be - * longer than HTTP_MAX_VALUE... - */ - - return (http->field_authorization); - } + if (!http) + return (HTTP_STATUS_ERROR); else - return (http->fields[field]); + return (http->expect); +} + + +/* + * 'httpGetFd()' - Get the file descriptor associated with a connection. + * + * @since CUPS 1.2/OS X 10.5@ + */ + +int /* O - File descriptor or -1 if none */ +httpGetFd(http_t *http) /* I - HTTP connection */ +{ + return (http ? http->fd : -1); +} + + +/* + * 'httpGetField()' - Get a field value from a request/response. + */ + +const char * /* O - Field value */ +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); + + switch (field) + { + case HTTP_FIELD_ACCEPT_ENCODING : + return (http->accept_encoding); + + case HTTP_FIELD_ALLOW : + return (http->allow); + + case HTTP_FIELD_SERVER : + return (http->server); + + 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]); + } +} + + +/* + * 'httpGetKeepAlive()' - Get the current Keep-Alive state of the connection. + * + * @since CUPS 2.0@ + */ + +http_keepalive_t /* O - Keep-Alive state */ +httpGetKeepAlive(http_t *http) /* I - HTTP connection */ +{ + return (http ? http->keep_alive : HTTP_KEEPALIVE_OFF); } @@ -1027,7 +1180,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. @@ -1051,224 +1204,129 @@ 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/OS X 10.5@ */ off_t /* O - Content length */ -httpGetLength2(http_t *http) /* I - Connection to server */ +httpGetLength2(http_t *http) /* I - HTTP connection */ { + off_t remaining; /* Remaining length */ + + DEBUG_printf(("2httpGetLength2(http=%p), state=%s", http, - http_states[http->state])); + 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@ */ -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 ? 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@ */ -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 (http->used); +#ifdef HAVE_SSL + else if (http->tls) + { + size_t ready; /* Ready bytes */ + +# ifdef HAVE_LIBSSL + if ((ready = SSL_pending((SSL *)(http->tls))) > 0) + return (ready); +# elif defined(HAVE_GNUTLS) + if ((ready = gnutls_record_check_pending(http->tls)) > 0) + return (ready); +# elif defined(HAVE_CDSASSL) + if (!SSLGetBufferedReadSize(http->tls, &ready) && ready > 0) + return (ready); +# endif /* HAVE_LIBSSL */ + } +#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. + * + * The @link httpIsChunked@ function can be used to determine whether the + * message body is chunked or fixed-length. * - * @since CUPS 1.2/Mac OS X 10.5@ + * @since CUPS 2.0@ */ -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 ? http->data_remaining : 0); } @@ -1279,7 +1337,7 @@ 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 */ @@ -1291,7 +1349,7 @@ httpGets(char *line, /* I - Line to read into */ DEBUG_printf(("2httpGets(line=%p, length=%d, http=%p)", line, length, http)); - if (http == NULL || line == NULL) + if (!http || !line || length <= 1) return (NULL); /* @@ -1321,8 +1379,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; @@ -1332,16 +1393,10 @@ 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, + HTTP_MAX_BUFFER - http->used); - DEBUG_printf(("4httpGets: read %d bytes...", bytes)); + DEBUG_printf(("4httpGets: read %d bytes.", bytes)); if (bytes < 0) { @@ -1450,59 +1505,242 @@ 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); } /* - * 'httpInitialize()' - Initialize the HTTP interface library and set the - * default HTTP proxy (if any). + * 'httpGetStatus()' - Get the status of the last HTTP request. + * + * @since CUPS 1.2/OS X 10.5@ */ -void -httpInitialize(void) +http_status_t /* O - HTTP status */ +httpGetStatus(http_t *http) /* I - HTTP connection */ { - static int initialized = 0; /* Have we been called before? */ -#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(); - if (initialized) - { - _cupsGlobalUnlock(); - return; - } + return (http ? http->status : HTTP_STATUS_ERROR); +} -#ifdef WIN32 - WSAStartup(MAKEWORD(2,2), &winsockdata); -#elif !defined(SO_NOSIGPIPE) - /* - * Ignore SIGPIPE signals... - */ +/* + * 'httpGetSubField()' - Get a sub-field value. + * + * @deprecated@ + */ -# ifdef HAVE_SIGSET - sigset(SIGPIPE, SIG_IGN); +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)); +} -# elif defined(HAVE_SIGACTION) - struct sigaction action; /* POSIX sigaction data */ +/* + * 'httpGetSubField2()' - Get a sub-field value. + * + * @since CUPS 1.2/OS X 10.5@ + */ - memset(&action, 0, sizeof(action)); +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)", 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); +} + + +/* + * '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\")", http, uri)); + return (http_send(http, HTTP_STATE_HEAD, uri)); +} + + +/* + * 'httpInitialize()' - Initialize the HTTP interface library and set the + * default HTTP proxy (if any). + */ + +void +httpInitialize(void) +{ + static int initialized = 0; /* Have we been called before? */ +#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(); + if (initialized) + { + _cupsGlobalUnlock(); + return; + } + +#ifdef WIN32 + WSAStartup(MAKEWORD(2,2), &winsockdata); + +#elif !defined(SO_NOSIGPIPE) + /* + * Ignore SIGPIPE signals... + */ + +# ifdef HAVE_SIGSET + sigset(SIGPIPE, SIG_IGN); + +# elif defined(HAVE_SIGACTION) + struct sigaction action; /* POSIX sigaction data */ + + + memset(&action, 0, sizeof(action)); action.sa_handler = SIG_IGN; sigaction(SIGPIPE, &action, NULL); @@ -1512,14 +1750,6 @@ httpInitialize(void) #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... */ @@ -1534,19 +1764,6 @@ httpInitialize(void) 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 */ - - 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...) @@ -1565,38 +1782,71 @@ httpInitialize(void) } +/* + * '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@ + */ + +int /* O - 1 if chunked, 0 if not */ +httpIsChunked(http_t *http) /* I - HTTP connection */ +{ + return (http ? http->data_encoding == HTTP_ENCODING_CHUNKED : 0); +} + + +/* + * 'httpIsEncrypted()' - Report whether a connection is encrypted. + * + * This function returns non-zero if the connection is currently encrypted. + * + * @since CUPS 2.0@ + */ + +int /* O - 1 if encrypted, 0 if not */ +httpIsEncrypted(http_t *http) /* I - HTTP connection */ +{ + return (http ? http->tls != NULL : 0); +} + + /* * 'httpOptions()' - Send an OPTIONS request to the server. */ 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/OS X 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 ")", + DEBUG_printf(("httpPeek(http=%p, buffer=%p, length=" CUPS_LLFMT ")", http, buffer, CUPS_LLCAST length)); if (http == NULL || buffer == NULL) @@ -1608,108 +1858,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_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->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->blocking) + { + while (!httpWait(http, http->wait_value)) + { + if (http->timeout_cb && (*http->timeout_cb)(http, http->timeout_data)) + continue; + + return (0); + } + } if (http->data_remaining > sizeof(http->buffer)) - bytes = sizeof(http->buffer); + buflen = sizeof(http->buffer); else - bytes = http->data_remaining; + buflen = http->data_remaining; -#ifdef HAVE_SSL - if (http->tls) - bytes = http_read_ssl(http, http->buffer, bytes); - else -#endif /* HAVE_SSL */ - { - DEBUG_printf(("2_httpPeek: reading %d bytes from socket into buffer...", - (int)bytes)); + DEBUG_printf(("2httpPeek: Reading %d bytes into buffer.", (int)buflen)); + bytes = http_read(http, http->buffer, buflen); - bytes = recv(http->fd, http->buffer, bytes, 0); + 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 */ - DEBUG_printf(("2_httpPeek: read %d bytes from socket into buffer...", - (int)bytes)); + http->used = bytes; } + } - if (bytes > 0) - http->used = bytes; - else if (bytes < 0) +#ifdef HAVE_LIBZ + if (http->coding) + { +# 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) { -#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 */ + 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->dbuffer) + memmove(http->dbuffer, http->stream.next_in, http->stream.avail_in); + + http->stream.next_in = http->dbuffer; + + if (buflen > http->data_remaining) + buflen = http->data_remaining; + + if (buflen > http->used) + buflen = http->used; + + DEBUG_printf(("1httpPeek: Copying %d more bytes of data into " + "decompression buffer.", (int)buflen)); + + memcpy(http->dbuffer + http->stream.avail_in, http->buffer, buflen); + http->stream.avail_in += buflen; + http->used -= buflen; + http->data_remaining -= buflen; + + if (http->used > 0) + memmove(http->buffer, http->buffer + buflen, http->used); } - else + + DEBUG_printf(("2httpPeek: length=%d, avail_in=%d", (int)length, + (int)http->stream.avail_in)); + + if (inflateCopy(&stream, &(http->stream)) != Z_OK) { - http->error = EPIPE; - return (0); + DEBUG_puts("2httpPeek: Unable to copy decompressor stream."); + http->error = ENOMEM; + return (-1); } - } + stream.next_out = (Bytef *)buffer; + stream.avail_out = length; + + zerr = inflate(&stream, Z_SYNC_FLUSH); + inflateEnd(&stream); + + if (zerr < Z_OK) + { + DEBUG_printf(("2httpPeek: zerr=%d", zerr)); +#ifdef DEBUG + http_debug_hex("2httpPeek", (char *)http->dbuffer, + http->stream.avail_in); +#endif /* DEBUG */ + + http->error = EIO; + return (-1); + } + + bytes = 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) @@ -1717,7 +2050,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); @@ -1745,23 +2078,23 @@ _httpPeek(http_t *http, /* I - Connection to server */ return (0); } -#ifdef DEBUG - http_debug_hex("_httpPeek", buffer, (int)bytes); -#endif /* DEBUG */ - return (bytes); } +/* For OS X 10.8 and earlier */ +ssize_t _httpPeek(http_t *http, char *buffer, size_t length) +{ return (httpPeek(http, buffer, length)); } + /* * 'httpPost()' - Send a POST request to the 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)); } @@ -1772,7 +2105,7 @@ 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 */ { @@ -1787,9 +2120,9 @@ httpPrintf(http_t *http, /* I - Connection to server */ bytes = vsnprintf(buf, sizeof(buf), format, ap); va_end(ap); - DEBUG_printf(("3httpPrintf: %s", buf)); + DEBUG_printf(("3httpPrintf: (%d bytes) %s", bytes, buf)); - if (http->data_encoding == HTTP_ENCODE_FIELDS) + if (http->data_encoding == HTTP_ENCODING_FIELDS) return (httpWrite2(http, buf, bytes)); else { @@ -1811,11 +2144,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)); + return (http_send(http, HTTP_STATE_PUT, uri)); } @@ -1829,7 +2162,7 @@ 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 */ { @@ -1840,20 +2173,29 @@ httpRead(http_t *http, /* I - Connection to server */ /* * 'httpRead2()' - Read data from a HTTP connection. * - * @since CUPS 1.2/Mac OS X 10.5@ + * @since CUPS 1.2/OS X 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, + http, 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, + http, buffer, CUPS_LLCAST length, + http->data_encoding, CUPS_LLCAST http->data_remaining)); +#endif /* HAVE_LIBZ */ if (http == NULL || buffer == NULL) return (-1); @@ -1864,239 +2206,184 @@ httpRead2(http_t *http, /* I - Connection to server */ 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) +#ifdef HAVE_LIBZ + if (http->coding) { - /* - * Buffer small reads for better performance... - */ - - if (!http->blocking && !httpWait(http, 10000)) - return (0); - - if (http->data_remaining > sizeof(http->buffer)) - bytes = sizeof(http->buffer); - else - bytes = http->data_remaining; - -#ifdef HAVE_SSL - if (http->tls) - bytes = http_read_ssl(http, http->buffer, bytes); - else -#endif /* HAVE_SSL */ + 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 = 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->dbuffer, + http->stream.avail_in); +#endif /* DEBUG */ + + http->error = EIO; return (-1); } + + bytes = 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 - http->stream.avail_in; + /* Additional bytes for buffer */ + + if (buflen > 0) + { + if (http->stream.avail_in > 0 && + http->stream.next_in > http->dbuffer) + memmove(http->dbuffer, http->stream.next_in, http->stream.avail_in); + + http->stream.next_in = http->dbuffer; + + 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 = http->data_remaining; + + bytes = http_read_buffered(http, + (char *)http->dbuffer + + http->stream.avail_in, buflen); + } + else if (http->data_encoding == HTTP_ENCODING_CHUNKED) + bytes = http_read_chunk(http, + (char *)http->dbuffer + + http->stream.avail_in, 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 += 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 ((bytes = http_read_buffered(http, buffer, length)) > 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 (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->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_content_coding_finish(http); +#endif /* HAVE_LIBZ */ - if (http->data_remaining <= INT_MAX) - http->_data_remaining = (int)http->data_remaining; - else - http->_data_remaining = INT_MAX; - } - else if (bytes < 0) - { -#ifdef WIN32 - if (WSAGetLastError() == WSAEINTR) - bytes = 0; - else - http->error = WSAGetLastError(); -#else - if (errno == EINTR || (errno == EAGAIN && !http->timeout_cb)) - bytes = 0; + 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->error = errno; -#endif /* WIN32 */ - } - else - { - http->error = EPIPE; - return (0); - } - - if (http->data_remaining == 0) - { - if (http->data_encoding == HTTP_ENCODE_CHUNKED) - httpGets(len, sizeof(len), http); + http->state = HTTP_STATE_STATUS; - if (http->data_encoding != HTTP_ENCODE_CHUNKED) - { - if (http->state == HTTP_POST_RECV) - http->state ++; - else - http->state = HTTP_WAITING; - } + DEBUG_printf(("1httpRead2: End of content, set state to %s.", + httpStateString(http->state))); } -#ifdef DEBUG - http_debug_hex("httpRead2", buffer, (int)bytes); -#endif /* DEBUG */ - return (bytes); } @@ -2125,8 +2412,11 @@ _httpReadCDSA( * Make sure we have data before we read... */ - if (!_httpWait(http, 10000, 0)) + while (!_httpWait(http, http->wait_value, 0)) { + if (http->timeout_cb && (*http->timeout_cb)(http, http->timeout_data)) + continue; + http->error = ETIMEDOUT; return (-1); } @@ -2171,13 +2461,16 @@ _httpReadCDSA( ssize_t /* O - Number of bytes read or -1 on error */ _httpReadGNUTLS( - gnutls_transport_ptr ptr, /* I - Connection to server */ + gnutls_transport_ptr ptr, /* I - HTTP connection */ void *data, /* I - Buffer */ size_t length) /* I - Number of bytes to read */ { http_t *http; /* HTTP connection */ + ssize_t bytes; /* Bytes read */ + DEBUG_printf(("6_httpReadGNUTLS(ptr=%p, data=%p, length=%d)", ptr, data, (int)length)); + http = (http_t *)ptr; if (!http->blocking) @@ -2186,74 +2479,279 @@ _httpReadGNUTLS( * Make sure we have data before we read... */ - if (!_httpWait(http, 10000, 0)) + while (!_httpWait(http, http->wait_value, 0)) { + if (http->timeout_cb && (*http->timeout_cb)(http, http->timeout_data)) + continue; + http->error = ETIMEDOUT; return (-1); } } - return (recv(http->fd, data, length, 0)); + bytes = recv(http->fd, data, length, 0); + DEBUG_printf(("6_httpReadGNUTLS: bytes=%d", (int)bytes)); + return (bytes); } #endif /* HAVE_SSL && HAVE_GNUTLS */ /* - * 'httpReconnect()' - Reconnect to a HTTP server. + * 'httpReadRequest()' - Read a HTTP request from a connection. + * + * @since CUPS 1.7/OS X 10.9@ */ -int /* O - 0 on success, non-zero on failure */ -httpReconnect(http_t *http) /* I - Connection to server */ +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 */ { - http_addrlist_t *addr; /* Connected address */ -#ifdef DEBUG - http_addrlist_t *current; /* Current address */ - char temp[256]; /* Temporary address string */ -#endif /* DEBUG */ + char line[4096], /* HTTP request line */ + *req_method, /* HTTP request method */ + *req_uri, /* HTTP request URI */ + *req_version; /* HTTP request version number string */ - DEBUG_printf(("httpReconnect(http=%p)", http)); + /* + * Range check input... + */ - if (!http) - return (-1); + DEBUG_printf(("httpReadRequest(http=%p, uri=%p, urilen=" CUPS_LLFMT ")", + http, uri, CUPS_LLCAST urilen)); -#ifdef HAVE_SSL - if (http->tls) + if (uri) + *uri = '\0'; + + if (!http || !uri || urilen < 1) { - DEBUG_puts("2httpReconnect: Shutting down SSL/TLS..."); - http_shutdown_ssl(http); + DEBUG_puts("1httpReadRequest: Bad arguments, returning HTTP_STATE_ERROR."); + return (HTTP_STATE_ERROR); + } + else if (http->state != HTTP_STATE_WAITING) + { + DEBUG_printf(("1httpReadRequest: Bad state %s, returning HTTP_STATE_ERROR.", + httpStateString(http->state))); + return (HTTP_STATE_ERROR); } -#endif /* HAVE_SSL */ /* - * Close any previously open socket... + * Reset state... */ - if (http->fd >= 0) - { - DEBUG_printf(("2httpReconnect: Closing socket %d...", http->fd)); - -#ifdef WIN32 - closesocket(http->fd); -#else - close(http->fd); -#endif /* WIN32 */ + httpClearFields(http); - http->fd = -1; - } + 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; /* - * Connect to the server... + * Read a line from the socket... */ -#ifdef DEBUG - for (current = http->addrlist; current; current = current->next) - DEBUG_printf(("2httpReconnect: Address %s:%d", - httpAddrString(&(current->addr), temp, sizeof(temp)), - _httpAddrPort(&(current->addr)))); -#endif /* DEBUG */ + if (!httpGets(line, sizeof(line), http)) + { + DEBUG_puts("1httpReadRequest: Unable to read, returning HTTP_STATE_ERROR"); + return (HTTP_STATE_ERROR); + } - if ((addr = httpAddrConnect(http->addrlist, &(http->fd))) == NULL) + if (!line[0]) + { + DEBUG_puts("1httpReadRequest: Blank line, returning HTTP_STATE_WAITING"); + return (HTTP_STATE_WAITING); + } + + DEBUG_printf(("1httpReadRequest: %s", line)); + + /* + * Parse it... + */ + + req_method = line; + req_uri = line; + + while (*req_uri && !isspace(*req_uri & 255)) + req_uri ++; + + 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'; + + while (*req_uri && isspace(*req_uri & 255)) + req_uri ++; + + req_version = req_uri; + + 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); + } + + *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 + { + DEBUG_printf(("1httpReadRequest: Unknown method \"%s\".", req_method)); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unknown request method."), 1); + return (HTTP_STATE_UNKNOWN_METHOD); + } + + DEBUG_printf(("1httpReadRequest: Set state to %s.", + httpStateString(http->state))); + + if (!strcmp(req_version, "HTTP/1.0")) + { + http->version = HTTP_VERSION_1_0; + http->keep_alive = HTTP_KEEPALIVE_OFF; + } + else if (!strcmp(req_version, "HTTP/1.1")) + { + http->version = HTTP_VERSION_1_1; + http->keep_alive = HTTP_KEEPALIVE_ON; + } + else + { + DEBUG_printf(("1httpReadRequest: Unknown version \"%s\".", req_version)); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unknown request version."), 1); + return (HTTP_STATE_UNKNOWN_VERSION); + } + + DEBUG_printf(("1httpReadRequest: URI is \"%s\".", req_uri)); + strlcpy(uri, req_uri, urilen); + + return (http->state); +} + + +/* + * 'httpReconnect()' - Reconnect to a HTTP server. + * + * This function is deprecated. Please use the @link httpReconnect2@ function + * instead. + * + * @deprecated@ + */ + +int /* O - 0 on success, non-zero on failure */ +httpReconnect(http_t *http) /* I - HTTP connection */ +{ + DEBUG_printf(("httpReconnect(http=%p)", http)); + + return (httpReconnect2(http, 30000, NULL)); +} + + +/* + * 'httpReconnect2()' - Reconnect to a HTTP server with timeout and optional + * cancel. + */ + +int /* O - 0 on success, non-zero on failure */ +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 + http_addrlist_t *current; /* Current address */ + char temp[256]; /* Temporary address string */ +#endif /* DEBUG */ + + + DEBUG_printf(("httpReconnect2(http=%p, msec=%d, cancel=%p)", http, msec, + cancel)); + + if (!http) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); + return (-1); + } + +#ifdef HAVE_SSL + if (http->tls) + { + DEBUG_puts("2httpReconnect2: Shutting down SSL/TLS..."); + http_shutdown_ssl(http); + } +#endif /* HAVE_SSL */ + + /* + * Close any previously open socket... + */ + + if (http->fd >= 0) + { + DEBUG_printf(("2httpReconnect2: Closing socket %d...", http->fd)); + + 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(("2httpReconnect2: Address %s:%d", + httpAddrString(&(current->addr), temp, sizeof(temp)), + httpAddrPort(&(current->addr)))); +#endif /* DEBUG */ + + if ((addr = httpAddrConnect2(http->addrlist, &(http->fd), msec, + cancel)) == NULL) { /* * Unable to connect... @@ -2264,30 +2762,24 @@ 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) - { - 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)); - } + 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. @@ -2295,22 +2787,18 @@ httpReconnect(http_t *http) /* I - Connection to server */ if (http_setup_ssl(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) + else if (http->encryption == HTTP_ENCRYPTION_REQUIRED && !http->tls_upgrade) return (http_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); } @@ -2324,11 +2812,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/OS X 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) */ { @@ -2381,11 +2869,11 @@ 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/OS X 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) @@ -2393,7 +2881,7 @@ httpSetCredentials(http_t *http, /* I - Connection to server */ _httpFreeCredentials(http->tls_credentials); - http->tls_credentials = _httpConvertCredentials(credentials); + http->tls_credentials = _httpCreateCredentials(credentials); return (http->tls_credentials ? 0 : -1); } @@ -2402,7 +2890,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/OS X 10.3@ */ void @@ -2422,18 +2910,72 @@ 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/OS X 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\")", + 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/OS X 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)", http, expect)); + if (http) http->expect = expect; } @@ -2444,17 +2986,46 @@ 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\")", 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; + + default : + strlcpy(http->fields[field], value, HTTP_MAX_VALUE); + break; + } if (field == HTTP_FIELD_AUTHORIZATION) { @@ -2506,25 +3077,53 @@ 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@ + */ + +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/OS X 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 ")", 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 @@ -2537,35 +3136,52 @@ 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/OS X 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) - { - 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)); - } + http_set_timeout(http->fd, timeout); + + http_set_wait(http); +} + + +/* + * 'httpShutdown()' - Shutdown one side of an HTTP connection. + * + * @since CUPS 2.0@ + */ + +void +httpShutdown(http_t *http) /* I - HTTP connection */ +{ + if (!http || http->fd < 0) + return; + + if (http->tls) + http_shutdown_ssl(http); + + shutdown(http->fd, SHUT_RD); } @@ -2574,175 +3190,233 @@ _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)); } /* - * 'httpUpdate()' - Update the current HTTP state for incoming data. + * '_httpUpdate()' - Update the current HTTP status for incoming data. + * + * Note: Unlike httpUpdate(), this function does not flush pending write data + * and only retrieves a single status line from the HTTP connection. */ -http_status_t /* O - HTTP status */ -httpUpdate(http_t *http) /* I - Connection to server */ +int /* O - 1 to continue, 0 to stop */ +_httpUpdate(http_t *http, /* I - HTTP connection */ + http_status_t *status) /* O - Current HTTP status */ { char line[32768], /* Line from connection... */ *value; /* Pointer to value on line */ http_field_t field; /* Field index */ - int major, minor, /* HTTP version numbers */ - status; /* Request status */ + int major, minor; /* HTTP version numbers */ - DEBUG_printf(("httpUpdate(http=%p), state=%s", http, - http_states[http->state])); + DEBUG_printf(("_httpUpdate(http=%p, status=%p), state=%s", http, status, + httpStateString(http->state))); /* - * Flush pending data, if any... + * Grab a single line from the connection... */ - if (http->wused) + if (!httpGets(line, sizeof(line), http)) { - DEBUG_puts("2httpUpdate: flushing buffer..."); - - if (httpFlushWrite(http) < 0) - return (HTTP_ERROR); + *status = HTTP_STATUS_ERROR; + return (0); } - /* - * If we haven't issued any commands, then there is nothing to "update"... - */ - - if (http->state == HTTP_WAITING) - return (HTTP_CONTINUE); - - /* - * Grab all of the lines we can from the connection... - */ + DEBUG_printf(("2_httpUpdate: Got \"%s\"", line)); - while (httpGets(line, sizeof(line), http) != NULL) + if (line[0] == '\0') { - DEBUG_printf(("2httpUpdate: Got \"%s\"", line)); + /* + * Blank line means the start of the data section (if any). Return + * the result code, too... + * + * 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 (line[0] == '\0') + if (http->status == HTTP_STATUS_CONTINUE) { - /* - * 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 (http->status == HTTP_CONTINUE) - return (http->status); + *status = http->status; + return (0); + } - if (http->status < HTTP_BAD_REQUEST) - http->digest_tries = 0; + 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 (http_setup_ssl(http) != 0) - { -# ifdef WIN32 - closesocket(http->fd); -# else - close(http->fd); -# endif /* WIN32 */ - - return (HTTP_ERROR); - } + httpAddrClose(NULL, http->fd); - return (HTTP_CONTINUE); + *status = http->status = HTTP_STATUS_ERROR; + return (0); } + + *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 : - http->state ++; - case HTTP_POST_SEND : - case HTTP_HEAD : - break; + switch (http->state) + { + case HTTP_STATE_GET : + case HTTP_STATE_POST : + case HTTP_STATE_POST_RECV : + case HTTP_STATE_PUT : + http->state ++; - default : - http->state = HTTP_WAITING; - break; - } + DEBUG_printf(("1_httpUpdate: Set state to %s.", + httpStateString(http->state))); - return (http->status); - } - else if (!strncmp(line, "HTTP/", 5)) - { - /* - * Got the beginning of a response... - */ + case HTTP_STATE_POST_SEND : + case HTTP_STATE_HEAD : + break; - if (sscanf(line, "HTTP/%d.%d%d", &major, &minor, &status) != 3) - return (HTTP_ERROR); + default : + http->state = HTTP_STATE_WAITING; - http->version = (http_version_t)(major * 100 + minor); - http->status = (http_status_t)status; + DEBUG_puts("1_httpUpdate: Reset state to HTTP_STATE_WAITING."); + break; } - else if ((value = strchr(line, ':')) != NULL) - { - /* - * Got a value... - */ - *value++ = '\0'; - while (_cups_isspace(*value)) - value ++; +#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 */ - /* - * Be tolerants of servers that send unknown attribute fields... - */ + *status = http->status; + return (0); + } + else if (!strncmp(line, "HTTP/", 5)) + { + /* + * Got the beginning of a response... + */ - if (!strcasecmp(line, "expect")) - { - /* - * "Expect: 100-continue" or similar... - */ + int intstatus; /* Status value as an integer */ - http->expect = (http_status_t)atoi(value); - } - else if (!strcasecmp(line, "cookie")) - { - /* - * "Cookie: name=value[; name=value ...]" - replaces previous cookies... - */ + if (sscanf(line, "HTTP/%d.%d%d", &major, &minor, &intstatus) != 3) + { + *status = http->status = HTTP_STATUS_ERROR; + return (0); + } - httpSetCookie(http, value); - } - else if ((field = http_field(line)) == HTTP_FIELD_UNKNOWN) - { - DEBUG_printf(("1httpUpdate: unknown field %s seen!", line)); - continue; - } - else - httpSetField(http, field, value); + httpClearFields(http); + + http->version = (http_version_t)(major * 100 + minor); + *status = http->status = (http_status_t)intstatus; + } + else if ((value = strchr(line, ':')) != NULL) + { + /* + * Got a value... + */ + + *value++ = '\0'; + while (_cups_isspace(*value)) + value ++; + + DEBUG_printf(("1_httpUpdate: Header %s: %s", line, value)); + + /* + * Be tolerants of servers that send unknown attribute fields... + */ + + if (!_cups_strcasecmp(line, "expect")) + { + /* + * "Expect: 100-continue" or similar... + */ + + http->expect = (http_status_t)atoi(value); } - else + else if (!_cups_strcasecmp(line, "cookie")) { - DEBUG_printf(("1httpUpdate: Bad response line \"%s\"!", line)); - http->status = HTTP_ERROR; - return (HTTP_ERROR); + /* + * "Cookie: name=value[; name=value ...]" - replaces previous cookies... + */ + + httpSetCookie(http, value); } + else if ((field = httpFieldValue(line)) != HTTP_FIELD_UNKNOWN) + httpSetField(http, field, value); +#ifdef DEBUG + else + DEBUG_printf(("1_httpUpdate: unknown field %s seen!", line)); +#endif /* DEBUG */ + } + else + { + DEBUG_printf(("1_httpUpdate: Bad response line \"%s\"!", line)); + http->error = EINVAL; + http->status = *status = HTTP_STATUS_ERROR; + return (0); + } + + return (1); +} + + +/* + * 'httpUpdate()' - Update the current HTTP state for incoming data. + */ + +http_status_t /* O - HTTP status */ +httpUpdate(http_t *http) /* I - HTTP connection */ +{ + http_status_t status; /* Request status */ + + + DEBUG_printf(("httpUpdate(http=%p), state=%s", http, + httpStateString(http->state))); + + /* + * Flush pending data, if any... + */ + + if (http->wused) + { + DEBUG_puts("2httpUpdate: flushing buffer..."); + + if (httpFlushWrite(http) < 0) + return (HTTP_STATUS_ERROR); } + /* + * If we haven't issued any commands, then there is nothing to "update"... + */ + + if (http->state == HTTP_STATE_WAITING) + return (HTTP_STATUS_CONTINUE); + + /* + * Grab all of the lines we can from the connection... + */ + + while (_httpUpdate(http, &status)); + /* * 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); @@ -2752,15 +3426,15 @@ 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); } /* - * If we haven't already returned, then there is nothing new... + * Return the current status... */ - return (HTTP_CONTINUE); + return (status); } @@ -2769,7 +3443,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? */ { @@ -2832,8 +3506,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 @@ -2873,22 +3550,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/OS X 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)", 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... @@ -2896,6 +3586,8 @@ httpWait(http_t *http, /* I - Connection to server */ if (http->wused) { + DEBUG_puts("3httpWait: Flushing write buffer."); + if (httpFlushWrite(http) < 0) return (0); } @@ -2918,7 +3610,7 @@ 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 */ { @@ -2929,11 +3621,11 @@ httpWrite(http_t *http, /* I - Connection to server */ /* * 'httpWrite2()' - Write data to a HTTP connection. * - * @since CUPS 1.2/Mac OS X 10.5@ + * @since CUPS 1.2/OS X 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 */ { @@ -2947,8 +3639,11 @@ httpWrite2(http_t *http, /* I - Connection to server */ * 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... @@ -2960,6 +3655,46 @@ httpWrite2(http_t *http, /* I - Connection to server */ * Buffer small writes for better performance... */ +#ifdef HAVE_LIBZ + if (http->coding) + { + DEBUG_printf(("1httpWrite2: http->coding=%d", http->coding)); + + if (length == 0) + { + http_content_coding_finish(http); + bytes = 0; + } + else + { + http->stream.next_in = (Bytef *)buffer; + http->stream.avail_in = length; + http->stream.next_out = (Bytef *)http->wbuffer + http->wused; + http->stream.avail_out = sizeof(http->wbuffer) - http->wused; + + while (deflate(&(http->stream), Z_NO_FLUSH) == Z_OK) + { + http->wused = sizeof(http->wbuffer) - http->stream.avail_out; + + if (http->stream.avail_out == 0) + { + if (httpFlushWrite(http) < 0) + { + DEBUG_puts("1httpWrite2: Unable to flush, returning -1."); + return (-1); + } + + http->stream.next_out = (Bytef *)http->wbuffer; + http->stream.avail_out = sizeof(http->wbuffer); + } + } + + http->wused = sizeof(http->wbuffer) - http->stream.avail_out; + bytes = length; + } + } + else +#endif /* HAVE_LIBZ */ if (length > 0) { if (http->wused && (length + http->wused) > sizeof(http->wbuffer)) @@ -2993,7 +3728,7 @@ 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) + if (http->data_encoding == HTTP_ENCODING_CHUNKED) bytes = (ssize_t)http_write_chunk(http, buffer, (int)length); else bytes = (ssize_t)http_write(http, buffer, (int)length); @@ -3002,7 +3737,7 @@ httpWrite2(http_t *http, /* I - Connection to server */ CUPS_LLCAST bytes)); } - if (http->data_encoding == HTTP_ENCODE_LENGTH) + if (http->data_encoding == HTTP_ENCODING_LENGTH) http->data_remaining -= bytes; } else @@ -3012,20 +3747,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_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... @@ -3037,18 +3778,23 @@ 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_POST_RECV) + if (http->state == HTTP_STATE_POST_RECV) http->state ++; - else if (http->state == HTTP_PUT_RECV) - http->state = HTTP_STATUS; + else if (http->state == HTTP_STATE_POST_SEND) + http->state = HTTP_STATE_WAITING; else - http->state = HTTP_WAITING; + 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); } @@ -3108,15 +3854,221 @@ _httpWriteCDSA( ssize_t /* O - Number of bytes written or -1 on error */ _httpWriteGNUTLS( - gnutls_transport_ptr ptr, /* I - Connection to server */ + gnutls_transport_ptr ptr, /* I - HTTP connection */ const void *data, /* I - Data buffer */ size_t length) /* I - Number of bytes to write */ { - return (send(((http_t *)ptr)->fd, data, length, 0)); + ssize_t bytes; /* Bytes written */ + + + DEBUG_printf(("6_httpWriteGNUTLS(ptr=%p, data=%p, length=%d)", ptr, data, + (int)length)); +#ifdef DEBUG + http_debug_hex("_httpWriteGNUTLS", data, (int)length); +#endif /* DEBUG */ + + bytes = send(((http_t *)ptr)->fd, data, length, 0); + DEBUG_printf(("_httpWriteGNUTLS: bytes=%d", (int)bytes)); + + return (bytes); } #endif /* HAVE_SSL && HAVE_GNUTLS */ +/* + * 'httpWriteResponse()' - Write a HTTP response to a client connection. + * + * @since CUPS 1.7/OS X 10.9@ + */ + +int /* O - 0 on success, -1 on error */ +httpWriteResponse(http_t *http, /* I - HTTP connection */ + http_status_t status) /* I - Status code */ +{ + http_encoding_t old_encoding; /* Old data_encoding value */ + off_t old_remaining; /* Old data_remaining value */ + + + /* + * Range check input... + */ + + DEBUG_printf(("httpWriteResponse(http=%p, status=%d)", 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) + { + http->keep_alive = HTTP_KEEPALIVE_OFF; + httpSetField(http, HTTP_FIELD_KEEP_ALIVE, ""); + } + + if (http->version == HTTP_VERSION_1_1) + { + 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"); + } + +#ifdef HAVE_SSL + if (status == HTTP_STATUS_UPGRADE_REQUIRED || + status == HTTP_STATUS_SWITCHING_PROTOCOLS) + { + 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"); + } +#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 (httpPrintf(http, "Set-Cookie: %s path=/ httponly%s\r\n", + http->cookie, http->tls ? " secure" : "") < 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) + { + 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... + */ + + http->data_encoding = old_encoding; + http->data_remaining = old_remaining; + + if (old_remaining <= INT_MAX) + http->_data_remaining = (int)old_remaining; + else + 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); + +#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 (0); +} + + #if defined(HAVE_SSL) && defined(HAVE_LIBSSL) /* * 'http_bio_ctrl()' - Control the HTTP connection. @@ -3233,8 +4185,11 @@ http_bio_read(BIO *h, /* I - BIO data */ * Make sure we have data before we read... */ - if (!_httpWait(http, 10000, 0)) + while (!_httpWait(http, http->wait_value, 0)) { + if (http->timeout_cb && (*http->timeout_cb)(http, http->timeout_data)) + continue; + #ifdef WIN32 http->error = WSAETIMEDOUT; #else @@ -3263,6 +4218,280 @@ http_bio_write(BIO *h, /* I - BIO data */ #endif /* HAVE_SSL && HAVE_LIBSSL */ +#ifdef HAVE_LIBZ +/* + * 'http_content_coding_finish()' - Finish doing any content encoding. + */ + +static void +http_content_coding_finish( + http_t *http) /* I - HTTP connection */ +{ + int zerr; /* Compression status */ + + + switch (http->coding) + { + case _HTTP_CODING_DEFLATE : + case _HTTP_CODING_GZIP : + do + { + http->stream.next_out = (Bytef *)http->wbuffer + http->wused; + http->stream.avail_out = sizeof(http->wbuffer) - http->wused; + + zerr = deflate(&(http->stream), Z_FINISH); + + http->wused = sizeof(http->wbuffer) - http->stream.avail_out; + if (http->wused == sizeof(http->wbuffer)) + httpFlushWrite(http); + } + while (zerr == Z_OK); + + deflateEnd(&(http->stream)); + + if (http->wused) + httpFlushWrite(http); + break; + + case _HTTP_CODING_INFLATE : + case _HTTP_CODING_GUNZIP : + inflateEnd(&(http->stream)); + free(http->dbuffer); + http->dbuffer = NULL; + break; + + default : + break; + } + + http->coding = _HTTP_CODING_IDENTITY; +} + + +/* + * 'http_content_coding_start()' - Start doing content encoding. + */ + +static void +http_content_coding_start( + http_t *http, /* I - HTTP connection */ + const char *value) /* I - Value of Content-Encoding */ +{ + int zerr; /* Error/status */ + _http_coding_t coding; /* Content coding value */ + + + DEBUG_printf(("http_content_coding_start(http=%p, value=\"%s\")", 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; + } + + memset(&(http->stream), 0, sizeof(http->stream)); + + switch (coding) + { + case _HTTP_CODING_DEFLATE : + case _HTTP_CODING_GZIP : + if (http->wused) + httpFlushWrite(http); + + /* + * 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; + } + break; + + case _HTTP_CODING_INFLATE : + case _HTTP_CODING_GUNZIP : + if ((http->dbuffer = malloc(HTTP_MAX_BUFFER)) == NULL) + { + http->status = HTTP_STATUS_ERROR; + http->error = errno; + return; + } + + /* + * 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->dbuffer); + http->dbuffer = 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->dbuffer; + break; + + default : + break; + } + + http->coding = coding; + + DEBUG_printf(("1http_content_coding_start: http->coding now %d.", + http->coding)); +} +#endif /* HAVE_LIBZ */ + + +/* + * 'http_create()' - Create an unconnected HTTP connection. + */ + +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 */ +{ + http_t *http; /* New HTTP connection */ + char service[255]; /* Service name */ + http_addrlist_t *myaddrlist = NULL; /* My address list */ + + + DEBUG_printf(("4http_create(host=\"%s\", port=%d, addrlist=%p, family=%d, " + "encryption=%d, blocking=%d, mode=%d)", host, port, addrlist, + family, encryption, blocking, mode)); + + if (!host && mode == _HTTP_MODE_CLIENT) + return (NULL); + + httpInitialize(); + + /* + * Lookup the host... + */ + + if (addrlist) + { + myaddrlist = httpAddrCopyList(addrlist); + } + else + { + snprintf(service, sizeof(service), "%d", port); + + myaddrlist = httpAddrGetList(host, family, service); + } + + if (!myaddrlist) + return (NULL); + + /* + * Allocate memory for the structure... + */ + + if ((http = calloc(sizeof(http_t), 1)) == NULL) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); + httpAddrFreeList(addrlist); + return (NULL); + } + + /* + * Initialize the HTTP data... + */ + + 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; + + 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); + + /* + * Return the new structure... + */ + + return (http); +} + +/* For OS X 10.8 and earlier */ +http_t *_httpCreate(const char *host, int port, http_addrlist_t *addrlist, + http_encryption_t encryption, int family) +{ return (http_create(host, port, addrlist, family, encryption, 1, + _HTTP_MODE_CLIENT)); } + + #ifdef DEBUG /* * 'http_debug_hex()' - Do a hex dump of a buffer. @@ -3283,7 +4512,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); @@ -3295,12 +4524,12 @@ http_debug_hex(const char *prefix, /* I - Prefix for line */ 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 ++) @@ -3321,20 +4550,227 @@ 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 */ + + + DEBUG_printf(("http_read(http=%p, buffer=%p, length=" CUPS_LLFMT ")", http, + buffer, CUPS_LLCAST length)); + + 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 + if (http->tls) + bytes = http_read_ssl(http, buffer, length); + else +#endif /* HAVE_SSL */ + bytes = recv(http->fd, buffer, length, 0); + + 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); + + 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 (bytes < 0) + { +#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); + } + + return (bytes); +} + + +/* + * '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. + */ + +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 */ + + + DEBUG_printf(("http_read_buffered(http=%p, buffer=%p, length=" CUPS_LLFMT + ") used=%d", + http, buffer, CUPS_LLCAST length, http->used)); + + if (http->used > 0) + { + if (length > (size_t)http->used) + bytes = (size_t)http->used; + else + bytes = length; + + DEBUG_printf(("2http_read: Grabbing %d bytes from input buffer.", + (int)bytes)); + + memcpy(buffer, http->buffer, bytes); + http->used -= (int)bytes; + + if (http->used > 0) + memmove(http->buffer, http->buffer + bytes, http->used); + } + else + bytes = http_read(http, buffer, length); + + return (bytes); +} + + +/* + * '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 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 */ +{ + DEBUG_printf(("http_read_chunk(http=%p, buffer=%p, length=" CUPS_LLFMT ")", + http, 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); + } + } - for (i = 0; i < HTTP_FIELD_MAX; i ++) - if (strcasecmp(name, http_fields[i]) == 0) - return ((http_field_t)i); + DEBUG_printf(("2http_read_chunk: data_remaining=" CUPS_LLFMT, + CUPS_LLCAST http->data_remaining)); - return (HTTP_FIELD_UNKNOWN); + 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)); } @@ -3344,7 +4780,7 @@ http_field(const char *name) /* I - String name */ */ static int /* O - Bytes read */ -http_read_ssl(http_t *http, /* I - Connection to server */ +http_read_ssl(http_t *http, /* I - HTTP connection */ char *buf, /* I - Buffer to store data */ int len) /* I - Length of buffer */ { @@ -3390,31 +4826,38 @@ http_read_ssl(http_t *http, /* I - Connection to server */ error = SSLRead(http->tls, buf, len, &processed); - + DEBUG_printf(("6http_read_ssl: error=%d, processed=%d", (int)error, + (int)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; + errno = EINTR; } break; + + case errSSLClosedGraceful : default : - errno = EPIPE; - result = -1; + if (processed) + result = (int)processed; + else + { + result = -1; + errno = EPIPE; + } break; } return (result); + # elif defined(HAVE_SSPISSL) return _sspiRead((_sspi_struct_t*) http->tls, buf, len); # endif /* HAVE_LIBSSL */ @@ -3422,38 +4865,20 @@ http_read_ssl(http_t *http, /* I - Connection to server */ #endif /* HAVE_SSL */ -#ifdef HAVE_LIBSSL -/* - * 'http_locking_cb()' - Lock/unlock a thread's mutex. - */ - -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 */ -{ - if (mode & CRYPTO_LOCK) - _cupsMutexLock(http_locks + type); - else - _cupsMutexUnlock(http_locks + type); -} -#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", @@ -3466,11 +4891,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\")", + DEBUG_printf(("4http_send(http=%p, request=HTTP_%s, uri=\"%s\")", http, codes[request], uri)); if (http == NULL || uri == NULL) @@ -3481,7 +4908,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... @@ -3493,9 +4933,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... @@ -3504,7 +4950,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); } @@ -3513,37 +4959,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) + { + 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_ERROR; + http->status = HTTP_STATUS_ERROR; return (-1); } } @@ -3551,28 +5004,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); /* @@ -3596,13 +5053,13 @@ http_send(http_t *http, /* I - Connection to server */ #ifdef HAVE_SSL -# ifdef HAVE_CDSASSL +# if defined(HAVE_CDSASSL) /* * 'http_set_credentials()' - Set the SSL/TLS credentials. */ static int /* O - Status of connection */ -http_set_credentials(http_t *http) /* I - Connection to server */ +http_set_credentials(http_t *http) /* I - HTTP connection */ { _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals */ OSStatus error = 0; /* Error code */ @@ -3619,123 +5076,178 @@ http_set_credentials(http_t *http) /* I - Connection to server */ 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 (credentials) { - 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 */ + 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."); - /* - * Get the names associated with the server. - */ + return (error); +} +# endif /* HAVE_CDSASSL */ +#endif /* HAVE_SSL */ + + +/* + * 'http_set_length()' - Set the data_encoding and data_remaining values. + */ + +static off_t /* O - Remainder or -1 on error */ +http_set_length(http_t *http) /* I - Connection */ +{ + off_t remaining; /* Remainder */ - if ((error = SSLCopyDistinguishedNames(http->tls, &dn_array)) != noErr) + + DEBUG_printf(("http_set_length(http=%p) mode=%d state=%s", http, http->mode, + httpStateString(http->state))); + + if ((remaining = httpGetLength2(http)) >= 0) + { + 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) { - DEBUG_printf(("4http_set_credentials: SSLCopyDistinguishedNames, error=%d", - (int)error)); - return (error); + DEBUG_puts("1http_set_length: Not setting data_encoding/remaining."); + return (remaining); } - /* - * 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 (!_cups_strcasecmp(http->fields[HTTP_FIELD_TRANSFER_ENCODING], + "chunked")) + { + DEBUG_puts("1http_set_length: Setting data_encoding to " + "HTTP_ENCODING_CHUNKED."); + http->data_encoding = HTTP_ENCODING_CHUNKED; + } + else { - error = SecItemCopyMatching(query, &matches); - DEBUG_printf(("4http_set_credentials: SecItemCopyMatching, error=%d", - (int)error)); - CFRelease(query); + DEBUG_puts("1http_set_length: Setting data_encoding to " + "HTTP_ENCODING_LENGTH."); + http->data_encoding = HTTP_ENCODING_LENGTH; } - if (matches) - CFRelease(matches); + DEBUG_printf(("1http_set_length: Setting data_remaining to " CUPS_LLFMT ".", + CUPS_LLCAST remaining)); + http->data_remaining = remaining; - if (dn_array) - CFRelease(dn_array); + if (remaining <= INT_MAX) + http->_data_remaining = remaining; + else + http->_data_remaining = INT_MAX; } -# endif /* HAVE_SECPOLICYCREATESSL */ - if (credentials) + return (remaining); +} + +/* + * 'http_set_timeout()' - Set the socket timeout values. + */ + +static void +http_set_timeout(int fd, /* I - File descriptor */ + double timeout) /* I - Timeout in seconds */ +{ +#ifdef WIN32 + DWORD tv = (DWORD)(timeout * 1000); + /* Timeout in milliseconds */ + + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CUPS_SOCAST &tv, sizeof(tv)); + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, CUPS_SOCAST &tv, sizeof(tv)); + +#else + struct timeval tv; /* Timeout in secs and usecs */ + + tv.tv_sec = (int)timeout; + tv.tv_usec = (int)(1000000 * fmod(timeout, 1.0)); + + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, CUPS_SOCAST &tv, sizeof(tv)); + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, CUPS_SOCAST &tv, sizeof(tv)); +#endif /* WIN32 */ +} + + +/* + * 'http_set_wait()' - Set the default wait value for reads. + */ + +static void +http_set_wait(http_t *http) /* I - HTTP connection */ +{ + if (http->blocking) { - error = SSLSetCertificate(http->tls, credentials); - DEBUG_printf(("4http_set_credentials: SSLSetCertificate, error=%d", - (int)error)); + http->wait_value = (int)(http->timeout_value * 1000); + + if (http->wait_value <= 0) + http->wait_value = 60000; } else - DEBUG_puts("4http_set_credentials: No credentials to set."); - - return (error); + http->wait_value = 10000; } -# endif /* HAVE_CDSASSL */ +#ifdef HAVE_SSL /* * '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 */ +static int /* O - 0 on success, -1 on failure */ +http_setup_ssl(http_t *http) /* I - HTTP connection */ { - _cups_globals_t *cg = _cupsGlobals(); - /* Pointer to library globals */ - int any_root; /* Allow any root */ + char hostname[256], /* Hostname */ + *hostptr; /* Pointer into hostname */ # ifdef HAVE_LIBSSL - SSL_CTX *context; /* Context for encryption */ - BIO *bio; /* BIO data */ + SSL_CTX *context; /* Context for encryption */ + BIO *bio; /* BIO data */ + const char *message = NULL;/* Error message */ # elif defined(HAVE_GNUTLS) + int status; /* Status of handshake */ gnutls_certificate_client_credentials *credentials; /* TLS credentials */ # elif defined(HAVE_CDSASSL) - OSStatus error; /* Error code */ - const char *message = NULL; /* Error message */ - cups_array_t *credentials; /* Credentials array */ - char *hostname; /* Hostname */ - 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 */ + _cups_globals_t *cg = _cupsGlobals(); + /* Pointer to library globals */ + OSStatus error; /* Error code */ + const char *message = NULL;/* Error message */ + 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 */ # elif defined(HAVE_SSPISSL) - TCHAR username[256]; /* Username returned from GetUserName() */ - TCHAR commonName[256]; /* Common name for certificate */ - DWORD dwSize; /* 32 bit size */ + 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... + * Get the hostname to use for SSL... */ if (httpAddrLocalhost(http->hostaddr)) - any_root = 1; + { + strlcpy(hostname, "localhost", sizeof(hostname)); + } else - any_root = cg->any_root; + { + /* + * Otherwise make sure the hostname we have does not end in a trailing dot. + */ + + strlcpy(hostname, http->hostname, sizeof(hostname)); + if ((hostptr = hostname + strlen(hostname) - 1) >= hostname && + *hostptr == '.') + *hostptr = '\0'; + } # ifdef HAVE_LIBSSL context = SSL_CTX_new(SSLv23_client_method()); @@ -3746,16 +5258,21 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ BIO_ctrl(bio, BIO_C_SET_FILE_PTR, 0, (char *)http); http->tls = SSL_new(context); - SSL_set_bio(http->tls_credentials, bio, bio); + SSL_set_bio(http->tls, bio, bio); + +# ifdef HAVE_SSL_SET_TLSEXT_HOST_NAME + SSL_set_tlsext_host_name(http->tls, hostname); +# endif /* HAVE_SSL_SET_TLSEXT_HOST_NAME */ 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 */ + { + message = ERR_error_string(error, NULL); + DEBUG_printf(("8http_setup_ssl: %s", message)); + } SSL_CTX_free(context); SSL_free(http->tls); @@ -3766,9 +5283,14 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ # else http->error = errno; # endif /* WIN32 */ - http->status = HTTP_ERROR; + http->status = HTTP_STATUS_ERROR; + + if (!message) + message = _("Unable to establish a secure connection to host."); + + _cupsSetError(IPP_STATUS_ERROR_CUPS_PKI, message, 1); - return (HTTP_ERROR); + return (-1); } # elif defined(HAVE_GNUTLS) @@ -3776,8 +5298,11 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ malloc(sizeof(gnutls_certificate_client_credentials)); if (credentials == NULL) { - http->error = errno; - http->status = HTTP_ERROR; + DEBUG_printf(("8http_setup_ssl: Unable to allocate credentials: %s", + strerror(errno))); + http->error = errno; + http->status = HTTP_STATUS_ERROR; + _cupsSetHTTPError(HTTP_STATUS_ERROR); return (-1); } @@ -3786,31 +5311,44 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ gnutls_init(&http->tls, GNUTLS_CLIENT); gnutls_set_default_priority(http->tls); + gnutls_server_name_set(http->tls, GNUTLS_NAME_DNS, hostname, + strlen(hostname)); 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) + while ((status = gnutls_handshake(http->tls)) != GNUTLS_E_SUCCESS) { - http->error = errno; - http->status = HTTP_ERROR; + DEBUG_printf(("8http_setup_ssl: gnutls_handshake returned %d (%s)", + status, gnutls_strerror(status))); - gnutls_deinit(http->tls); - gnutls_certificate_free_credentials(*credentials); - free(credentials); - http->tls = NULL; + if (gnutls_error_is_fatal(status)) + { + http->error = EIO; + http->status = HTTP_STATUS_ERROR; - return (-1); + _cupsSetError(IPP_STATUS_ERROR_CUPS_PKI, gnutls_strerror(status), 0); + + 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))) + if ((http->tls = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, + kSSLStreamType)) == NULL) { - http->error = error; - http->status = HTTP_ERROR; + DEBUG_puts("4http_setup_ssl: SSLCreateContext failed."); + http->error = errno = ENOMEM; + http->status = HTTP_STATUS_ERROR; + _cupsSetHTTPError(HTTP_STATUS_ERROR); return (-1); } @@ -3826,32 +5364,12 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ if (!error) { - error = SSLSetProtocolVersionEnabled(http->tls, kSSLProtocol2, false); - DEBUG_printf(("4http_setup_ssl: SSLSetProtocolVersionEnabled, error=%d", + error = SSLSetSessionOption(http->tls, kSSLSessionOptionBreakOnServerAuth, + true); + DEBUG_printf(("4http_setup_ssl: SSLSetSessionOption, 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)); - } - if (!error) { if (cg->client_cert_cb) @@ -3870,29 +5388,13 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ } /* - * If there's a server certificate callback installed let it evaluate the - * certificate(s) during the handshake... + * Let the server know which hostname/domain we are trying to connect to + * in case it wants to serve up a certificate with a matching common name. */ - 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)); - } - } - if (!error) { - hostname = httpAddrLocalhost(http->hostaddr) ? "localhost" : http->hostname; - error = SSLSetPeerDomainName(http->tls, hostname, strlen(hostname)); + error = SSLSetPeerDomainName(http->tls, hostname, strlen(hostname)); DEBUG_printf(("4http_setup_ssl: SSLSetPeerDomainName, error=%d", (int)error)); @@ -3906,7 +5408,7 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ { error = SSLHandshake(http->tls); - DEBUG_printf(("4_httpWait: SSLHandshake returned %d.", (int)error)); + DEBUG_printf(("4http_setup_ssl: SSLHandshake returned %d.", (int)error)); switch (error) { @@ -3915,7 +5417,8 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ break; case errSSLWouldBlock : - usleep(1000); + error = noErr; /* Force a retry */ + usleep(1000); /* in 1 millisecond */ break; case errSSLServerAuthCompleted : @@ -3930,8 +5433,8 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ httpFreeCredentials(credentials); } - DEBUG_printf(("4_httpWait: Server certificate callback returned " - "%d.", (int)error)); + DEBUG_printf(("4http_setup_ssl: Server certificate callback " + "returned %d.", (int)error)); } break; @@ -3941,7 +5444,7 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ if (cg->client_cert_cb) { names = NULL; - if (!(error = SSLCopyDistinguishedNames(http->tls, &dn_array)) && + if (!(error = SSLCopyDistinguishedNames(http->tls, &dn_array)) && dn_array) { if ((names = cupsArrayNew(NULL, NULL)) != NULL) @@ -3950,15 +5453,17 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ { data = (CFDataRef)CFArrayGetValueAtIndex(dn_array, i); - if ((credential = malloc(sizeof(*credential)))) + if ((credential = malloc(sizeof(*credential))) != NULL) { credential->datalen = CFDataGetLength(data); if ((credential->data = malloc(credential->datalen))) { - memcpy((void *)credential->data, CFDataGetBytePtr(data), + memcpy((void *)credential->data, CFDataGetBytePtr(data), credential->datalen); cupsArrayAdd(names, credential); } + else + free(credential); } } } @@ -3968,10 +5473,10 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ if (!error) { - error = (cg->client_cert_cb)(http, http->tls, names, + error = (cg->client_cert_cb)(http, http->tls, names, cg->client_cert_data); - DEBUG_printf(("4_httpWait: Client certificate callback " + DEBUG_printf(("4http_setup_ssl: Client certificate callback " "returned %d.", (int)error)); } @@ -4023,10 +5528,10 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ if (error) { http->error = error; - http->status = HTTP_ERROR; + http->status = HTTP_STATUS_ERROR; errno = ECONNREFUSED; - SSLDisposeContext(http->tls); + CFRelease(http->tls); http->tls = NULL; /* @@ -4040,7 +5545,7 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ message = _("Unable to establish a secure connection to host."); #endif /* HAVE_CSSMERRORSTRING */ - _cupsSetError(IPP_PKI_ERROR, message, 1); + _cupsSetError(IPP_STATUS_ERROR_CUPS_PKI, message, 1); return (-1); } @@ -4049,7 +5554,10 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ http->tls = _sspiAlloc(); if (!http->tls) + { + _cupsSetHTTPError(HTTP_STATUS_ERROR); return (-1); + } http->tls->sock = http->fd; dwSize = sizeof(username) / sizeof(TCHAR); @@ -4062,41 +5570,53 @@ http_setup_ssl(http_t *http) /* I - Connection to server */ { _sspiFree(http->tls_credentials); http->tls_credentials = NULL; + + http->error = EIO; + http->status = HTTP_STATUS_ERROR; + + _cupsSetError(IPP_STATUS_ERROR_CUPS_PKI, + _("Unable to establish a secure connection to host."), 1); + return (-1); } _sspiSetAllowsAnyRoot(http->tls_credentials, TRUE); _sspiSetAllowsExpiredCerts(http->tls_credentials, TRUE); - if (!_sspiConnect(http->tls_credentials, http->hostname)) + if (!_sspiConnect(http->tls_credentials, hostname)) { _sspiFree(http->tls_credentials); http->tls_credentials = NULL; + + http->error = EIO; + http->status = HTTP_STATUS_ERROR; + + _cupsSetError(IPP_STATUS_ERROR_CUPS_PKI, + _("Unable to establish a secure connection to host."), 1); + return (-1); } # endif /* HAVE_CDSASSL */ return (0); } -#endif /* HAVE_SSL */ -#ifdef HAVE_SSL /* * 'http_shutdown_ssl()' - Shut down SSL/TLS on a connection. */ static void -http_shutdown_ssl(http_t *http) /* I - Connection to server */ +http_shutdown_ssl(http_t *http) /* I - HTTP connection */ { # ifdef HAVE_LIBSSL SSL_CTX *context; /* Context for encryption */ - context = SSL_get_SSL_CTX(http->tls_credentials); + context = SSL_get_SSL_CTX(http->tls); - SSL_shutdown(http->tls_credentials); + SSL_shutdown(http->tls); SSL_CTX_free(context); - SSL_free(http->tls_credentials); + SSL_free(http->tls); # elif defined(HAVE_GNUTLS) gnutls_certificate_client_credentials *credentials; @@ -4113,7 +5633,7 @@ http_shutdown_ssl(http_t *http) /* I - Connection to server */ while (SSLClose(http->tls) == errSSLWouldBlock) usleep(1000); - SSLDisposeContext(http->tls); + CFRelease(http->tls); if (http->tls_credentials) CFRelease(http->tls_credentials); @@ -4128,30 +5648,13 @@ http_shutdown_ssl(http_t *http) /* I - Connection to server */ #endif /* HAVE_SSL */ -#ifdef HAVE_LIBSSL -/* - * 'http_threadid_cb()' - Return the current thread ID. - */ - -static unsigned long /* O - Thread ID */ -http_threadid_cb(void) -{ -# ifdef HAVE_PTHREAD_H - return ((unsigned long)pthread_self()); -# else - return (0); -# endif /* HAVE_PTHREAD_H */ -} -#endif /* HAVE_LIBSSL */ - - #ifdef HAVE_SSL /* * 'http_upgrade()' - Force upgrade to TLS encryption. */ static int /* O - Status of connection */ -http_upgrade(http_t *http) /* I - Connection to server */ +http_upgrade(http_t *http) /* I - HTTP connection */ { int ret; /* Return value */ http_t myhttp; /* Local copy of HTTP data */ @@ -4178,11 +5681,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) { @@ -4190,7 +5694,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); } /* @@ -4204,6 +5708,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... @@ -4217,11 +5722,7 @@ http_upgrade(http_t *http) /* I - Connection to server */ DEBUG_puts("8http_upgrade: Server does not support HTTP upgrade!"); -# ifdef WIN32 - closesocket(http->fd); -# else - close(http->fd); -# endif + httpAddrClose(NULL, http->fd); http->fd = -1; @@ -4237,22 +5738,81 @@ 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 ")", http, + 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); @@ -4260,6 +5820,9 @@ http_write(http_t *http, /* I - Connection to server */ #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 @@ -4313,7 +5876,7 @@ http_write(http_t *http, /* I - Connection to server */ http_debug_hex("http_write", buffer - tbytes, tbytes); #endif /* DEBUG */ - DEBUG_printf(("3http_write: Returning %d.", tbytes)); + DEBUG_printf(("3http_write: Returning " CUPS_LLFMT ".", CUPS_LLCAST tbytes)); return (tbytes); } @@ -4323,38 +5886,38 @@ 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 ")", + http, 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); } @@ -4368,7 +5931,7 @@ http_write_chunk(http_t *http, /* I - Connection to server */ */ static int /* O - Bytes written */ -http_write_ssl(http_t *http, /* I - Connection to server */ +http_write_ssl(http_t *http, /* I - HTTP connection */ const char *buf, /* I - Buffer holding data */ int len) /* I - Length of buffer */ { @@ -4419,9 +5982,7 @@ http_write_ssl(http_t *http, /* I - Connection to server */ case 0 : result = (int)processed; break; - case errSSLClosedGraceful : - result = 0; - break; + case errSSLWouldBlock : if (processed) result = (int)processed; @@ -4431,13 +5992,20 @@ http_write_ssl(http_t *http, /* I - Connection to server */ errno = EINTR; } break; + + case errSSLClosedGraceful : default : - errno = EPIPE; - result = -1; + if (processed) + result = (int)processed; + else + { + result = -1; + errno = EPIPE; + } break; } # elif defined(HAVE_SSPISSL) - return _sspiWrite((_sspi_struct_t*) http->tls, (void*) buf, len); + return _sspiWrite((_sspi_struct_t *)http->tls, (void *)buf, len); # endif /* HAVE_LIBSSL */ DEBUG_printf(("3http_write_ssl: Returning %d.", (int)result)); @@ -4448,5 +6016,5 @@ http_write_ssl(http_t *http, /* I - Connection to server */ /* - * End of "$Id: http.c 7850 2008-08-20 00:07:25Z mike $". + * End of "$Id$". */