X-Git-Url: http://git.ipfire.org/?p=thirdparty%2Fcups.git;a=blobdiff_plain;f=cups%2Frequest.c;h=39cbe6ccdf8f796bc356cd871c67a6e7b47fb44f;hp=14741e95c801d011fb17e20b65ead2f102e86c3e;hb=57b7b66b58a66426494ec13ffb18f730afeab8b5;hpb=2e4ff8afcbae91304495e2c90b4965420422e363 diff --git a/cups/request.c b/cups/request.c index 14741e95c..39cbe6ccd 100644 --- a/cups/request.c +++ b/cups/request.c @@ -1,36 +1,23 @@ /* - * "$Id: request.c 6879 2007-08-29 20:26:50Z mike $" + * IPP utilities for CUPS. * - * IPP utilities for the Common UNIX Printing System (CUPS). + * Copyright 2007-2014 by Apple Inc. + * Copyright 1997-2007 by Easy Software Products. * - * Copyright 2007 by Apple Inc. - * Copyright 1997-2007 by Easy Software Products. + * 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 + * 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: - * - * cupsDoFileRequest() - Do an IPP request with a file. - * cupsDoIORequest() - Do an IPP request with file descriptors. - * cupsDoRequest() - Do an IPP request. - * _cupsSetError() - Set the last IPP status code and status-message. - * _cupsSetHTTPError() - Set the last error using the HTTP status. + * This file is subject to the Apple OS-Developed Software exception. */ /* * Include necessary headers... */ -#include "globals.h" -#include "debug.h" -#include -#include +#include "cups-private.h" #include #include #if defined(WIN32) || defined(__EMX__) @@ -41,26 +28,31 @@ #ifndef O_BINARY # define O_BINARY 0 #endif /* O_BINARY */ +#ifndef MSG_DONTWAIT +# define MSG_DONTWAIT 0 +#endif /* !MSG_DONTWAIT */ /* * 'cupsDoFileRequest()' - Do an IPP request with a file. * - * This function sends the IPP request to the specified server, retrying - * and authenticating as necessary. The request is freed with ippDelete() - * after receiving a valid IPP response. + * This function sends the IPP request and attached file to the specified + * server, retrying and authenticating as necessary. The request is freed with + * @link ippDelete@. */ ipp_t * /* O - Response data */ -cupsDoFileRequest(http_t *http, /* I - HTTP connection to server */ +cupsDoFileRequest(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */ ipp_t *request, /* I - IPP request */ const char *resource, /* I - HTTP resource for POST */ - const char *filename) /* I - File to send or NULL for none */ + const char *filename) /* I - File to send or @code NULL@ for none */ { ipp_t *response; /* IPP response data */ int infile; /* Input file */ + DEBUG_printf(("cupsDoFileRequest(http=%p, request=%p(%s), resource=\"%s\", filename=\"%s\")", (void *)http, (void *)request, request ? ippOpString(request->request.op.operation_id) : "?", resource, filename)); + if (filename) { if ((infile = open(filename, O_RDONLY | O_BINARY)) < 0) @@ -69,8 +61,8 @@ cupsDoFileRequest(http_t *http, /* I - HTTP connection to server */ * Can't get file information! */ - _cupsSetError(errno == ENOENT ? IPP_NOT_FOUND : IPP_NOT_AUTHORIZED, - strerror(errno)); + _cupsSetError(errno == ENOENT ? IPP_STATUS_ERROR_NOT_FOUND : IPP_STATUS_ERROR_NOT_AUTHORIZED, + NULL, 0); ippDelete(request); @@ -92,51 +84,61 @@ cupsDoFileRequest(http_t *http, /* I - HTTP connection to server */ /* * 'cupsDoIORequest()' - Do an IPP request with file descriptors. * - * This function sends the IPP request to the specified server, retrying - * and authenticating as necessary. The request is freed with ippDelete() - * after receiving a valid IPP response. + * This function sends the IPP request with the optional input file "infile" to + * the specified server, retrying and authenticating as necessary. The request + * is freed with @link ippDelete@. * - * If "infile" is a valid file descriptor, cupsDoIORequest() copies + * If "infile" is a valid file descriptor, @code cupsDoIORequest@ copies * all of the data from the file after the IPP request message. * - * If "outfile" is a valid file descriptor, cupsDoIORequest() copies + * If "outfile" is a valid file descriptor, @code cupsDoIORequest@ copies * all of the data after the IPP response message to the file. * - * @since CUPS 1.3@ + * @since CUPS 1.3/macOS 10.5@ */ ipp_t * /* O - Response data */ -cupsDoIORequest(http_t *http, /* I - HTTP connection to server */ +cupsDoIORequest(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */ ipp_t *request, /* I - IPP request */ const char *resource, /* I - HTTP resource for POST */ int infile, /* I - File to read from or -1 for none */ int outfile) /* I - File to write to or -1 for none */ { - ipp_t *response; /* IPP response data */ - size_t length; /* Content-Length value */ + ipp_t *response = NULL; /* IPP response data */ + size_t length = 0; /* Content-Length value */ http_status_t status; /* Status of HTTP request */ - int got_status; /* Did we get the status? */ - ipp_state_t state; /* State of IPP processing */ struct stat fileinfo; /* File information */ - int bytes; /* Number of bytes read/written */ + ssize_t bytes; /* Number of bytes read/written */ char buffer[32768]; /* Output buffer */ - http_status_t expect; /* Expect: header to use */ - DEBUG_printf(("cupsDoFileRequest(%p, %p, \'%s\', \'%s\')\n", - http, request, resource ? resource : "(null)", - filename ? filename : "(null)")); + DEBUG_printf(("cupsDoIORequest(http=%p, request=%p(%s), resource=\"%s\", infile=%d, outfile=%d)", (void *)http, (void *)request, request ? ippOpString(request->request.op.operation_id) : "?", resource, infile, outfile)); + + /* + * Range check input... + */ - if (http == NULL || request == NULL || resource == NULL) + if (!request || !resource) { - if (request != NULL) - ippDelete(request); + ippDelete(request); - _cupsSetError(IPP_INTERNAL_ERROR, NULL); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); return (NULL); } + /* + * Get the default connection as needed... + */ + + if (!http) + if ((http = _cupsConnect()) == NULL) + { + ippDelete(request); + + return (NULL); + } + /* * See if we have a file to send... */ @@ -149,8 +151,8 @@ cupsDoIORequest(http_t *http, /* I - HTTP connection to server */ * Can't get file information! */ - _cupsSetError(errno == ENOENT ? IPP_NOT_FOUND : IPP_NOT_AUTHORIZED, - strerror(errno)); + _cupsSetError(errno == EBADF ? IPP_STATUS_ERROR_NOT_FOUND : IPP_STATUS_ERROR_NOT_AUTHORIZED, + NULL, 0); ippDelete(request); @@ -169,282 +171,285 @@ cupsDoIORequest(http_t *http, /* I - HTTP connection to server */ ippDelete(request); - _cupsSetError(IPP_NOT_POSSIBLE, strerror(EISDIR)); + _cupsSetError(IPP_STATUS_ERROR_NOT_POSSIBLE, strerror(EISDIR), 0); return (NULL); } + +#ifndef WIN32 + if (!S_ISREG(fileinfo.st_mode)) + length = 0; /* Chunk when piping */ + else +#endif /* !WIN32 */ + length = ippLength(request) + (size_t)fileinfo.st_size; } + else + length = ippLength(request); + + DEBUG_printf(("2cupsDoIORequest: Request length=%ld, total length=%ld", + (long)ippLength(request), (long)length)); -#ifdef HAVE_SSL /* - * See if we have an auth-info attribute and are communicating over - * a non-local link. If so, encrypt the link so that we can pass - * the authentication information securely... + * Clear any "Local" authentication data since it is probably stale... */ - if (ippFindAttribute(request, "auth-info", IPP_TAG_TEXT) && - !httpAddrLocalhost(http->hostaddr) && !http->tls && - httpEncryption(http, HTTP_ENCRYPT_REQUIRED)) - return (NULL); -#endif /* HAVE_SSL */ + if (http->authstring && !strncmp(http->authstring, "Local ", 6)) + httpSetAuthString(http, NULL, NULL); /* * Loop until we can send the request without authorization problems. */ - response = NULL; - status = HTTP_ERROR; - expect = HTTP_CONTINUE; - while (response == NULL) { - DEBUG_puts("cupsDoFileRequest: setup..."); + DEBUG_puts("2cupsDoIORequest: setup..."); /* - * Setup the HTTP variables needed... + * Send the request... */ - length = ippLength(request); - if (infile >= 0) - { -#ifndef WIN32 - if (!S_ISREG(fileinfo.st_mode)) - length = 0; /* Chunk when piping */ - else -#endif /* !WIN32 */ - length += fileinfo.st_size; - } + status = cupsSendRequest(http, request, resource, length); - httpClearFields(http); - httpSetLength(http, length); - httpSetField(http, HTTP_FIELD_CONTENT_TYPE, "application/ipp"); - httpSetField(http, HTTP_FIELD_AUTHORIZATION, http->authstring); - httpSetExpect(http, expect); + DEBUG_printf(("2cupsDoIORequest: status=%d", status)); - DEBUG_printf(("cupsDoFileRequest: authstring=\"%s\"\n", http->authstring)); + if (status == HTTP_STATUS_CONTINUE && request->state == IPP_STATE_DATA && infile >= 0) + { + DEBUG_puts("2cupsDoIORequest: file write..."); - /* - * Try the request... - */ + /* + * Send the file with the request... + */ - DEBUG_puts("cupsDoFileRequest: post..."); +#ifndef WIN32 + if (S_ISREG(fileinfo.st_mode)) +#endif /* WIN32 */ + lseek(infile, 0, SEEK_SET); - if (httpPost(http, resource)) - { - if (httpReconnect(http)) + while ((bytes = read(infile, buffer, sizeof(buffer))) > 0) { - status = HTTP_ERROR; - break; + if ((status = cupsWriteRequestData(http, buffer, (size_t)bytes)) + != HTTP_STATUS_CONTINUE) + break; } - else - continue; } /* - * Send the IPP data... + * Get the server's response... */ - DEBUG_puts("cupsDoFileRequest: ipp write..."); - - request->state = IPP_IDLE; - status = HTTP_CONTINUE; - got_status = 0; + if (status <= HTTP_STATUS_CONTINUE || status == HTTP_STATUS_OK) + { + response = cupsGetResponse(http, resource); + status = httpGetStatus(http); + } - while ((state = ippWrite(http, request)) != IPP_DATA) - if (state == IPP_ERROR) - break; - else if (httpCheck(http)) - { - got_status = 1; + DEBUG_printf(("2cupsDoIORequest: status=%d", status)); - if ((status = httpUpdate(http)) != HTTP_CONTINUE) - break; - } + if (status == HTTP_STATUS_ERROR || + (status >= HTTP_STATUS_BAD_REQUEST && status != HTTP_STATUS_UNAUTHORIZED && + status != HTTP_STATUS_UPGRADE_REQUIRED)) + { + _cupsSetHTTPError(status); + break; + } - if (!got_status) + if (response && outfile >= 0) { /* - * Wait up to 1 second to get the 100-continue response... + * Write trailing data to file... */ - if (httpWait(http, 1000)) - status = httpUpdate(http); + while ((bytes = httpRead2(http, buffer, sizeof(buffer))) > 0) + if (write(outfile, buffer, (size_t)bytes) < bytes) + break; } - else if (httpCheck(http)) - status = httpUpdate(http); - if (status == HTTP_CONTINUE && state == IPP_DATA && infile >= 0) + if (http->state != HTTP_STATE_WAITING) { - DEBUG_puts("cupsDoFileRequest: file write..."); - /* - * Send the file... + * Flush any remaining data... */ -#ifndef WIN32 - if (S_ISREG(fileinfo.st_mode)) -#endif /* WIN32 */ - lseek(infile, 0, SEEK_SET); + httpFlush(http); + } + } - while ((bytes = (int)read(infile, buffer, sizeof(buffer))) > 0) - { - if (httpCheck(http)) - { - if ((status = httpUpdate(http)) != HTTP_CONTINUE) - break; - } + /* + * Delete the original request and return the response... + */ - if (httpWrite2(http, buffer, bytes) < bytes) - break; - } - } + ippDelete(request); - /* - * Get the server's return status... - */ + return (response); +} - DEBUG_puts("cupsDoFileRequest: update..."); - while (status == HTTP_CONTINUE) - status = httpUpdate(http); +/* + * 'cupsDoRequest()' - Do an IPP request. + * + * This function sends the IPP request to the specified server, retrying + * and authenticating as necessary. The request is freed with @link ippDelete@. + */ - DEBUG_printf(("cupsDoFileRequest: status = %d\n", status)); +ipp_t * /* O - Response data */ +cupsDoRequest(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */ + ipp_t *request, /* I - IPP request */ + const char *resource) /* I - HTTP resource for POST */ +{ + DEBUG_printf(("cupsDoRequest(http=%p, request=%p(%s), resource=\"%s\")", (void *)http, (void *)request, request ? ippOpString(request->request.op.operation_id) : "?", resource)); - if (status == HTTP_UNAUTHORIZED) - { - DEBUG_puts("cupsDoFileRequest: unauthorized..."); + return (cupsDoIORequest(http, request, resource, -1, -1)); +} - /* - * Flush any error message... - */ - httpFlush(http); +/* + * 'cupsGetResponse()' - Get a response to an IPP request. + * + * Use this function to get the response for an IPP request sent using + * @link cupsSendRequest@. For requests that return additional data, use + * @link cupsReadResponseData@ after getting a successful response, + * otherwise call @link httpFlush@ to complete the response processing. + * + * @since CUPS 1.4/macOS 10.6@ + */ - /* - * See if we can do authentication... - */ +ipp_t * /* O - Response or @code NULL@ on HTTP error */ +cupsGetResponse(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */ + const char *resource) /* I - HTTP resource for POST */ +{ + http_status_t status; /* HTTP status */ + ipp_state_t state; /* IPP read state */ + ipp_t *response = NULL; /* IPP response */ - if (cupsDoAuthentication(http, "POST", resource)) - break; - if (httpReconnect(http)) - { - status = HTTP_ERROR; - break; - } + DEBUG_printf(("cupsGetResponse(http=%p, resource=\"%s\")", (void *)http, resource)); + DEBUG_printf(("1cupsGetResponse: http->state=%d", http ? http->state : HTTP_STATE_ERROR)); - continue; - } - else if (status == HTTP_ERROR) - { - DEBUG_printf(("cupsDoFileRequest: http->error=%d (%s)\n", http->error, - strerror(http->error))); + /* + * Connect to the default server as needed... + */ -#ifdef WIN32 - if (http->error != WSAENETDOWN && http->error != WSAENETUNREACH && - http->error != WSAETIMEDOUT) -#else - if (http->error != ENETDOWN && http->error != ENETUNREACH && - http->error != ETIMEDOUT) -#endif /* WIN32 */ - continue; - else - break; - } -#ifdef HAVE_SSL - else if (status == HTTP_UPGRADE_REQUIRED) + if (!http) + { + _cups_globals_t *cg = _cupsGlobals(); + /* Pointer to library globals */ + + if ((http = cg->http) == NULL) { - /* Flush any error message... */ - httpFlush(http); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No active connection."), 1); + DEBUG_puts("1cupsGetResponse: No active connection - returning NULL."); + return (NULL); + } + } - /* Reconnect... */ - if (httpReconnect(http)) - { - status = HTTP_ERROR; - break; - } + if (http->state != HTTP_STATE_POST_RECV && http->state != HTTP_STATE_POST_SEND) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No request sent."), 1); + DEBUG_puts("1cupsGetResponse: Not in POST state - returning NULL."); + return (NULL); + } - /* Upgrade with encryption... */ - httpEncryption(http, HTTP_ENCRYPT_REQUIRED); + /* + * Check for an unfinished chunked request... + */ - /* Try again, this time with encryption enabled... */ - continue; - } -#endif /* HAVE_SSL */ - else if (status == HTTP_EXPECTATION_FAILED) - { - /* - * Don't try using the Expect: header the next time around... - */ + if (http->data_encoding == HTTP_ENCODING_CHUNKED) + { + /* + * Send a 0-length chunk to finish off the request... + */ - expect = (http_status_t)0; - } - else if (status != HTTP_OK) - { - DEBUG_printf(("cupsDoFileRequest: error %d...\n", status)); + DEBUG_puts("2cupsGetResponse: Finishing chunked POST..."); - /* - * Flush any error message... - */ + if (httpWrite2(http, "", 0) < 0) + return (NULL); + } - httpFlush(http); - break; - } - else + /* + * Wait for a response from the server... + */ + + DEBUG_printf(("2cupsGetResponse: Update loop, http->status=%d...", + http->status)); + + do + { + status = httpUpdate(http); + } + while (status == HTTP_STATUS_CONTINUE); + + DEBUG_printf(("2cupsGetResponse: status=%d", status)); + + if (status == HTTP_STATUS_OK) + { + /* + * Get the IPP response... + */ + + response = ippNew(); + + while ((state = ippRead(http, response)) != IPP_STATE_DATA) + if (state == IPP_STATE_ERROR) + break; + + if (state == IPP_STATE_ERROR) { /* - * Read the response... + * Flush remaining data and delete the response... */ - DEBUG_puts("cupsDoFileRequest: response..."); + DEBUG_puts("1cupsGetResponse: IPP read error!"); - response = ippNew(); + httpFlush(http); - while ((state = ippRead(http, response)) != IPP_DATA) - if (state == IPP_ERROR) - break; + ippDelete(response); + response = NULL; - if (state == IPP_ERROR) - { - /* - * Delete the response... - */ + http->status = status = HTTP_STATUS_ERROR; + http->error = EINVAL; + } + } + else if (status != HTTP_STATUS_ERROR) + { + /* + * Flush any error message... + */ - DEBUG_puts("IPP read error!"); - ippDelete(response); - response = NULL; + httpFlush(http); - _cupsSetError(IPP_SERVICE_UNAVAILABLE, strerror(errno)); + /* + * Then handle encryption and authentication... + */ - break; - } - else if (outfile >= 0) - { - /* - * Write trailing data to file... - */ + if (status == HTTP_STATUS_UNAUTHORIZED) + { + /* + * See if we can do authentication... + */ - while ((bytes = (int)httpRead2(http, buffer, sizeof(buffer))) > 0) - if (write(outfile, buffer, bytes) < bytes) - break; - } + DEBUG_puts("2cupsGetResponse: Need authorization..."); + + if (!cupsDoAuthentication(http, "POST", resource)) + httpReconnect2(http, 30000, NULL); else - { - /* - * Flush any remaining data... - */ + http->status = status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED; + } - httpFlush(http); - } +#ifdef HAVE_SSL + else if (status == HTTP_STATUS_UPGRADE_REQUIRED) + { + /* + * Force a reconnect with encryption... + */ + + DEBUG_puts("2cupsGetResponse: Need encryption..."); + + if (!httpReconnect2(http, 30000, NULL)) + httpEncryption(http, HTTP_ENCRYPTION_REQUIRED); } +#endif /* HAVE_SSL */ } - /* - * Delete the original request and return the response... - */ - - ippDelete(request); - if (response) { ipp_attribute_t *attr; /* status-message attribute */ @@ -452,62 +457,661 @@ cupsDoIORequest(http_t *http, /* I - HTTP connection to server */ attr = ippFindAttribute(response, "status-message", IPP_TAG_TEXT); + DEBUG_printf(("1cupsGetResponse: status-code=%s, status-message=\"%s\"", + ippErrorString(response->request.status.status_code), + attr ? attr->values[0].string.text : "")); + _cupsSetError(response->request.status.status_code, - attr ? attr->values[0].string.text : - ippErrorString(response->request.status.status_code)); + attr ? attr->values[0].string.text : + ippErrorString(response->request.status.status_code), 0); } - else if (status != HTTP_OK) - _cupsSetHTTPError(status); return (response); } /* - * 'cupsDoRequest()' - Do an IPP request. - * - * This function sends the IPP request to the specified server, retrying - * and authenticating as necessary. The request is freed with ippDelete() - * after receiving a valid IPP response. + * 'cupsLastError()' - Return the last IPP status code received on the current + * thread. */ -ipp_t * /* O - Response data */ -cupsDoRequest(http_t *http, /* I - HTTP connection to server */ - ipp_t *request, /* I - IPP request */ - const char *resource) /* I - HTTP resource for POST */ +ipp_status_t /* O - IPP status code from last request */ +cupsLastError(void) { - return (cupsDoFileRequest(http, request, resource, NULL)); + return (_cupsGlobals()->last_error); } /* - * '_cupsSetError()' - Set the last IPP status code and status-message. + * 'cupsLastErrorString()' - Return the last IPP status-message received on the + * current thread. + * + * @since CUPS 1.2/macOS 10.5@ */ -void -_cupsSetError(ipp_status_t status, /* I - IPP status code */ - const char *message) /* I - status-message value */ +const char * /* O - status-message text from last request */ +cupsLastErrorString(void) { - _cups_globals_t *cg; /* Global data */ + return (_cupsGlobals()->last_status_message); +} - cg = _cupsGlobals(); - cg->last_error = status; +/* + * '_cupsNextDelay()' - Return the next retry delay value. + * + * This function currently returns the Fibonacci sequence 1 1 2 3 5 8. + * + * Pass 0 for the current delay value to initialize the sequence. + */ - if (cg->last_status_message) - { - free(cg->last_status_message); +int /* O - Next delay value */ +_cupsNextDelay(int current, /* I - Current delay value or 0 */ + int *previous) /* IO - Previous delay value */ +{ + int next; /* Next delay value */ - cg->last_status_message = NULL; + + if (current > 0) + { + next = (current + *previous) % 12; + *previous = next < current ? 0 : current; + } + else + { + next = 1; + *previous = 0; } - if (message) - cg->last_status_message = strdup(message); + return (next); } /* - * '_cupsSetHTTPError()' - Set the last error using the HTTP status. + * 'cupsReadResponseData()' - Read additional data after the IPP response. + * + * This function is used after @link cupsGetResponse@ to read the PPD or document + * files from @code CUPS_GET_PPD@ and @code CUPS_GET_DOCUMENT@ requests, + * respectively. + * + * @since CUPS 1.4/macOS 10.6@ + */ + +ssize_t /* O - Bytes read, 0 on EOF, -1 on error */ +cupsReadResponseData( + http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */ + char *buffer, /* I - Buffer to use */ + size_t length) /* I - Number of bytes to read */ +{ + /* + * Get the default connection as needed... + */ + + DEBUG_printf(("cupsReadResponseData(http=%p, buffer=%p, length=" CUPS_LLFMT ")", (void *)http, (void *)buffer, CUPS_LLCAST length)); + + if (!http) + { + _cups_globals_t *cg = _cupsGlobals(); + /* Pointer to library globals */ + + if ((http = cg->http) == NULL) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No active connection"), 1); + return (-1); + } + } + + /* + * Then read from the HTTP connection... + */ + + return (httpRead2(http, buffer, length)); +} + + +/* + * 'cupsSendRequest()' - Send an IPP request. + * + * Use @link cupsWriteRequestData@ to write any additional data (document, PPD + * file, etc.) for the request, @link cupsGetResponse@ to get the IPP response, + * and @link cupsReadResponseData@ to read any additional data following the + * response. Only one request can be sent/queued at a time per @code http_t@ + * connection. + * + * Returns the initial HTTP status code, which will be @code HTTP_STATUS_CONTINUE@ + * on a successful send of the request. + * + * Note: Unlike @link cupsDoFileRequest@, @link cupsDoIORequest@, and + * @link cupsDoRequest@, the request is NOT freed with @link ippDelete@. + * + * @since CUPS 1.4/macOS 10.6@ + */ + +http_status_t /* O - Initial HTTP status */ +cupsSendRequest(http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */ + ipp_t *request, /* I - IPP request */ + const char *resource, /* I - Resource path */ + size_t length) /* I - Length of data to follow or @code CUPS_LENGTH_VARIABLE@ */ +{ + http_status_t status; /* Status of HTTP request */ + int got_status; /* Did we get the status? */ + ipp_state_t state; /* State of IPP processing */ + http_status_t expect; /* Expect: header to use */ + + + DEBUG_printf(("cupsSendRequest(http=%p, request=%p(%s), resource=\"%s\", length=" CUPS_LLFMT ")", (void *)http, (void *)request, request ? ippOpString(request->request.op.operation_id) : "?", resource, CUPS_LLCAST length)); + + /* + * Range check input... + */ + + if (!request || !resource) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0); + + return (HTTP_STATUS_ERROR); + } + + /* + * Get the default connection as needed... + */ + + if (!http) + if ((http = _cupsConnect()) == NULL) + return (HTTP_STATUS_SERVICE_UNAVAILABLE); + + /* + * If the prior request was not flushed out, do so now... + */ + + if (http->state == HTTP_STATE_GET_SEND || + http->state == HTTP_STATE_POST_SEND) + { + DEBUG_puts("2cupsSendRequest: Flush prior response."); + httpFlush(http); + } + else if (http->state != HTTP_STATE_WAITING) + { + DEBUG_printf(("1cupsSendRequest: Unknown HTTP state (%d), " + "reconnecting.", http->state)); + if (httpReconnect2(http, 30000, NULL)) + return (HTTP_STATUS_ERROR); + } + +#ifdef HAVE_SSL + /* + * See if we have an auth-info attribute and are communicating over + * a non-local link. If so, encrypt the link so that we can pass + * the authentication information securely... + */ + + if (ippFindAttribute(request, "auth-info", IPP_TAG_TEXT) && + !httpAddrLocalhost(http->hostaddr) && !http->tls && + httpEncryption(http, HTTP_ENCRYPTION_REQUIRED)) + { + DEBUG_puts("1cupsSendRequest: Unable to encrypt connection."); + return (HTTP_STATUS_SERVICE_UNAVAILABLE); + } +#endif /* HAVE_SSL */ + + /* + * Reconnect if the last response had a "Connection: close"... + */ + + if (!_cups_strcasecmp(http->fields[HTTP_FIELD_CONNECTION], "close")) + { + DEBUG_puts("2cupsSendRequest: Connection: close"); + httpClearFields(http); + if (httpReconnect2(http, 30000, NULL)) + { + DEBUG_puts("1cupsSendRequest: Unable to reconnect."); + return (HTTP_STATUS_SERVICE_UNAVAILABLE); + } + } + + /* + * Loop until we can send the request without authorization problems. + */ + + expect = HTTP_STATUS_CONTINUE; + + for (;;) + { + DEBUG_puts("2cupsSendRequest: Setup..."); + + /* + * Setup the HTTP variables needed... + */ + + httpClearFields(http); + httpSetExpect(http, expect); + httpSetField(http, HTTP_FIELD_CONTENT_TYPE, "application/ipp"); + httpSetLength(http, length); + +#ifdef HAVE_GSSAPI + if (http->authstring && !strncmp(http->authstring, "Negotiate", 9)) + { + /* + * Do not use cached Kerberos credentials since they will look like a + * "replay" attack... + */ + + _cupsSetNegotiateAuthString(http, "POST", resource); + } +#endif /* HAVE_GSSAPI */ + + httpSetField(http, HTTP_FIELD_AUTHORIZATION, http->authstring); + + DEBUG_printf(("2cupsSendRequest: authstring=\"%s\"", http->authstring)); + + /* + * Try the request... + */ + + DEBUG_puts("2cupsSendRequest: Sending HTTP POST..."); + + if (httpPost(http, resource)) + { + DEBUG_puts("2cupsSendRequest: POST failed, reconnecting."); + if (httpReconnect2(http, 30000, NULL)) + { + DEBUG_puts("1cupsSendRequest: Unable to reconnect."); + return (HTTP_STATUS_SERVICE_UNAVAILABLE); + } + else + continue; + } + + /* + * Send the IPP data... + */ + + DEBUG_puts("2cupsSendRequest: Writing IPP request..."); + + request->state = IPP_STATE_IDLE; + status = HTTP_STATUS_CONTINUE; + got_status = 0; + + while ((state = ippWrite(http, request)) != IPP_STATE_DATA) + { + if (httpCheck(http)) + { + got_status = 1; + + _httpUpdate(http, &status); + if (status >= HTTP_STATUS_MULTIPLE_CHOICES) + break; + } + else if (state == IPP_STATE_ERROR) + break; + } + + if (state == IPP_STATE_ERROR) + { + /* + * We weren't able to send the IPP request. But did we already get a HTTP + * error status? + */ + + if (!got_status || status < HTTP_STATUS_MULTIPLE_CHOICES) + { + /* + * No, something else went wrong. + */ + + DEBUG_puts("1cupsSendRequest: Unable to send IPP request."); + + http->status = HTTP_STATUS_ERROR; + http->state = HTTP_STATE_WAITING; + + return (HTTP_STATUS_ERROR); + } + } + + /* + * Wait up to 1 second to get the 100-continue response as needed... + */ + + if (!got_status) + { + if (expect == HTTP_STATUS_CONTINUE) + { + DEBUG_puts("2cupsSendRequest: Waiting for 100-continue..."); + + if (httpWait(http, 1000)) + _httpUpdate(http, &status); + } + else if (httpCheck(http)) + _httpUpdate(http, &status); + } + + DEBUG_printf(("2cupsSendRequest: status=%d", status)); + + /* + * Process the current HTTP status... + */ + + if (status >= HTTP_STATUS_MULTIPLE_CHOICES) + { + int temp_status; /* Temporary status */ + + _cupsSetHTTPError(status); + + do + { + temp_status = httpUpdate(http); + } + while (temp_status != HTTP_STATUS_ERROR && + http->state == HTTP_STATE_POST_RECV); + + httpFlush(http); + } + + switch (status) + { + case HTTP_STATUS_CONTINUE : + case HTTP_STATUS_OK : + case HTTP_STATUS_ERROR : + DEBUG_printf(("1cupsSendRequest: Returning %d.", status)); + return (status); + + case HTTP_STATUS_UNAUTHORIZED : + if (cupsDoAuthentication(http, "POST", resource)) + { + DEBUG_puts("1cupsSendRequest: Returning HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED."); + return (HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED); + } + + DEBUG_puts("2cupsSendRequest: Reconnecting after HTTP_STATUS_UNAUTHORIZED."); + + if (httpReconnect2(http, 30000, NULL)) + { + DEBUG_puts("1cupsSendRequest: Unable to reconnect."); + return (HTTP_STATUS_SERVICE_UNAVAILABLE); + } + break; + +#ifdef HAVE_SSL + case HTTP_STATUS_UPGRADE_REQUIRED : + /* + * Flush any error message, reconnect, and then upgrade with + * encryption... + */ + + DEBUG_puts("2cupsSendRequest: Reconnecting after " + "HTTP_STATUS_UPGRADE_REQUIRED."); + + if (httpReconnect2(http, 30000, NULL)) + { + DEBUG_puts("1cupsSendRequest: Unable to reconnect."); + return (HTTP_STATUS_SERVICE_UNAVAILABLE); + } + + DEBUG_puts("2cupsSendRequest: Upgrading to TLS."); + if (httpEncryption(http, HTTP_ENCRYPTION_REQUIRED)) + { + DEBUG_puts("1cupsSendRequest: Unable to encrypt connection."); + return (HTTP_STATUS_SERVICE_UNAVAILABLE); + } + break; +#endif /* HAVE_SSL */ + + case HTTP_STATUS_EXPECTATION_FAILED : + /* + * Don't try using the Expect: header the next time around... + */ + + expect = (http_status_t)0; + + DEBUG_puts("2cupsSendRequest: Reconnecting after " + "HTTP_EXPECTATION_FAILED."); + + if (httpReconnect2(http, 30000, NULL)) + { + DEBUG_puts("1cupsSendRequest: Unable to reconnect."); + return (HTTP_STATUS_SERVICE_UNAVAILABLE); + } + break; + + default : + /* + * Some other error... + */ + + return (status); + } + } +} + + +/* + * 'cupsWriteRequestData()' - Write additional data after an IPP request. + * + * This function is used after @link cupsSendRequest@ to provide a PPD and + * after @link cupsStartDocument@ to provide a document file. + * + * @since CUPS 1.4/macOS 10.6@ + */ + +http_status_t /* O - @code HTTP_STATUS_CONTINUE@ if OK or HTTP status on error */ +cupsWriteRequestData( + http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */ + const char *buffer, /* I - Bytes to write */ + size_t length) /* I - Number of bytes to write */ +{ + int wused; /* Previous bytes in buffer */ + + + /* + * Get the default connection as needed... + */ + + DEBUG_printf(("cupsWriteRequestData(http=%p, buffer=%p, length=" CUPS_LLFMT ")", (void *)http, (void *)buffer, CUPS_LLCAST length)); + + if (!http) + { + _cups_globals_t *cg = _cupsGlobals(); + /* Pointer to library globals */ + + if ((http = cg->http) == NULL) + { + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No active connection"), 1); + DEBUG_puts("1cupsWriteRequestData: Returning HTTP_STATUS_ERROR."); + return (HTTP_STATUS_ERROR); + } + } + + /* + * Then write to the HTTP connection... + */ + + wused = http->wused; + + if (httpWrite2(http, buffer, length) < 0) + { + DEBUG_puts("1cupsWriteRequestData: Returning HTTP_STATUS_ERROR."); + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(http->error), 0); + return (HTTP_STATUS_ERROR); + } + + /* + * Finally, check if we have any pending data from the server... + */ + + if (length >= HTTP_MAX_BUFFER || + http->wused < wused || + (wused > 0 && (size_t)http->wused == length)) + { + /* + * We've written something to the server, so check for response data... + */ + + if (_httpWait(http, 0, 1)) + { + http_status_t status; /* Status from _httpUpdate */ + + _httpUpdate(http, &status); + if (status >= HTTP_STATUS_MULTIPLE_CHOICES) + { + _cupsSetHTTPError(status); + + do + { + status = httpUpdate(http); + } + while (status != HTTP_STATUS_ERROR && http->state == HTTP_STATE_POST_RECV); + + httpFlush(http); + } + + DEBUG_printf(("1cupsWriteRequestData: Returning %d.\n", status)); + return (status); + } + } + + DEBUG_puts("1cupsWriteRequestData: Returning HTTP_STATUS_CONTINUE."); + return (HTTP_STATUS_CONTINUE); +} + + +/* + * '_cupsConnect()' - Get the default server connection... + */ + +http_t * /* O - HTTP connection */ +_cupsConnect(void) +{ + _cups_globals_t *cg = _cupsGlobals(); /* Pointer to library globals */ + + + /* + * See if we are connected to the same server... + */ + + if (cg->http) + { + /* + * Compare the connection hostname, port, and encryption settings to + * the cached defaults; these were initialized the first time we + * connected... + */ + + if (strcmp(cg->http->hostname, cg->server) || + cg->ipp_port != httpAddrPort(cg->http->hostaddr) || + (cg->http->encryption != cg->encryption && + cg->http->encryption == HTTP_ENCRYPTION_NEVER)) + { + /* + * Need to close the current connection because something has changed... + */ + + httpClose(cg->http); + cg->http = NULL; + } + else + { + /* + * Same server, see if the connection is still established... + */ + + char ch; /* Connection check byte */ + ssize_t n; /* Number of bytes */ + +#ifdef WIN32 + if ((n = recv(cg->http->fd, &ch, 1, MSG_PEEK)) == 0 || + (n < 0 && WSAGetLastError() != WSAEWOULDBLOCK)) +#else + if ((n = recv(cg->http->fd, &ch, 1, MSG_PEEK | MSG_DONTWAIT)) == 0 || + (n < 0 && errno != EWOULDBLOCK)) +#endif /* WIN32 */ + { + /* + * Nope, close the connection... + */ + + httpClose(cg->http); + cg->http = NULL; + } + } + } + + /* + * (Re)connect as needed... + */ + + if (!cg->http) + { + if ((cg->http = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC, + cupsEncryption(), 1, 30000, NULL)) == NULL) + { + if (errno) + _cupsSetError(IPP_STATUS_ERROR_SERVICE_UNAVAILABLE, NULL, 0); + else + _cupsSetError(IPP_STATUS_ERROR_SERVICE_UNAVAILABLE, + _("Unable to connect to host."), 1); + } + } + + /* + * Return the cached connection... + */ + + return (cg->http); +} + + +/* + * '_cupsSetError()' - Set the last IPP status code and status-message. + */ + +void +_cupsSetError(ipp_status_t status, /* I - IPP status code */ + const char *message, /* I - status-message value */ + int localize) /* I - Localize the message? */ +{ + _cups_globals_t *cg; /* Global data */ + + + if (!message && errno) + { + message = strerror(errno); + localize = 0; + } + + cg = _cupsGlobals(); + cg->last_error = status; + + if (cg->last_status_message) + { + _cupsStrFree(cg->last_status_message); + + cg->last_status_message = NULL; + } + + if (message) + { + if (localize) + { + /* + * Get the message catalog... + */ + + if (!cg->lang_default) + cg->lang_default = cupsLangDefault(); + + cg->last_status_message = _cupsStrAlloc(_cupsLangString(cg->lang_default, + message)); + } + else + cg->last_status_message = _cupsStrAlloc(message); + } + + DEBUG_printf(("4_cupsSetError: last_error=%s, last_status_message=\"%s\"", + ippErrorString(cg->last_error), cg->last_status_message)); +} + + +/* + * '_cupsSetHTTPError()' - Set the last error using the HTTP status. */ void @@ -515,43 +1119,54 @@ _cupsSetHTTPError(http_status_t status) /* I - HTTP status code */ { switch (status) { - case HTTP_NOT_FOUND : - _cupsSetError(IPP_NOT_FOUND, httpStatus(status)); + case HTTP_STATUS_NOT_FOUND : + _cupsSetError(IPP_STATUS_ERROR_NOT_FOUND, httpStatus(status), 0); + break; + + case HTTP_STATUS_UNAUTHORIZED : + _cupsSetError(IPP_STATUS_ERROR_NOT_AUTHENTICATED, httpStatus(status), 0); break; - case HTTP_UNAUTHORIZED : - _cupsSetError(IPP_NOT_AUTHORIZED, httpStatus(status)); + case HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED : + _cupsSetError(IPP_STATUS_ERROR_CUPS_AUTHENTICATION_CANCELED, httpStatus(status), 0); break; - case HTTP_FORBIDDEN : - _cupsSetError(IPP_FORBIDDEN, httpStatus(status)); + case HTTP_STATUS_FORBIDDEN : + _cupsSetError(IPP_STATUS_ERROR_FORBIDDEN, httpStatus(status), 0); break; - case HTTP_BAD_REQUEST : - _cupsSetError(IPP_BAD_REQUEST, httpStatus(status)); + case HTTP_STATUS_BAD_REQUEST : + _cupsSetError(IPP_STATUS_ERROR_BAD_REQUEST, httpStatus(status), 0); break; - case HTTP_REQUEST_TOO_LARGE : - _cupsSetError(IPP_REQUEST_VALUE, httpStatus(status)); + case HTTP_STATUS_REQUEST_TOO_LARGE : + _cupsSetError(IPP_STATUS_ERROR_REQUEST_VALUE, httpStatus(status), 0); break; - case HTTP_NOT_IMPLEMENTED : - _cupsSetError(IPP_OPERATION_NOT_SUPPORTED, httpStatus(status)); + case HTTP_STATUS_NOT_IMPLEMENTED : + _cupsSetError(IPP_STATUS_ERROR_OPERATION_NOT_SUPPORTED, httpStatus(status), 0); break; - case HTTP_NOT_SUPPORTED : - _cupsSetError(IPP_VERSION_NOT_SUPPORTED, httpStatus(status)); + case HTTP_STATUS_NOT_SUPPORTED : + _cupsSetError(IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED, httpStatus(status), 0); break; + case HTTP_STATUS_UPGRADE_REQUIRED : + _cupsSetError(IPP_STATUS_ERROR_CUPS_UPGRADE_REQUIRED, httpStatus(status), 0); + break; + + case HTTP_STATUS_CUPS_PKI_ERROR : + _cupsSetError(IPP_STATUS_ERROR_CUPS_PKI, httpStatus(status), 0); + break; + + case HTTP_STATUS_ERROR : + _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0); + break; + default : - DEBUG_printf(("HTTP error %d mapped to IPP_SERVICE_UNAVAILABLE!\n", - status)); - _cupsSetError(IPP_SERVICE_UNAVAILABLE, httpStatus(status)); + DEBUG_printf(("4_cupsSetHTTPError: HTTP error %d mapped to " + "IPP_STATUS_ERROR_SERVICE_UNAVAILABLE!", status)); + _cupsSetError(IPP_STATUS_ERROR_SERVICE_UNAVAILABLE, httpStatus(status), 0); break; } } - - -/* - * End of "$Id: request.c 6879 2007-08-29 20:26:50Z mike $". - */