#include "cups-private.h"
#include <fcntl.h>
#include <sys/stat.h>
#if defined(WIN32) || defined(__EMX__)
# include <io.h>
#else
# include <unistd.h>
#endif
#ifndef O_BINARY
# define O_BINARY 0
#endif
ipp_t *
cupsDoFileRequest(http_t *http,
ipp_t *request,
const char *resource,
const char *filename)
{
ipp_t *response;
int infile;
DEBUG_printf(("cupsDoFileRequest(http=%p, request=%p(%s), resource=\"%s\", "
"filename=\"%s\")", http, request,
request ? ippOpString(request->request.op.operation_id) : "?",
resource, filename));
if (filename)
{
if ((infile = open(filename, O_RDONLY | O_BINARY)) < 0)
{
_cupsSetError(errno == ENOENT ? IPP_NOT_FOUND : IPP_NOT_AUTHORIZED,
NULL, 0);
ippDelete(request);
return (NULL);
}
}
else
infile = -1;
response = cupsDoIORequest(http, request, resource, infile, -1);
if (infile >= 0)
close(infile);
return (response);
}
ipp_t *
cupsDoIORequest(http_t *http,
ipp_t *request,
const char *resource,
int infile,
int outfile)
{
ipp_t *response = NULL;
size_t length = 0;
http_status_t status;
struct stat fileinfo;
int bytes;
char buffer[32768];
DEBUG_printf(("cupsDoIORequest(http=%p, request=%p(%s), resource=\"%s\", "
"infile=%d, outfile=%d)", http, request,
request ? ippOpString(request->request.op.operation_id) : "?",
resource, infile, outfile));
if (!request || !resource)
{
ippDelete(request);
_cupsSetError(IPP_INTERNAL_ERROR, strerror(EINVAL), 0);
return (NULL);
}
if (!http)
if ((http = _cupsConnect()) == NULL)
{
ippDelete(request);
return (NULL);
}
if (infile >= 0)
{
if (fstat(infile, &fileinfo))
{
_cupsSetError(errno == EBADF ? IPP_NOT_FOUND : IPP_NOT_AUTHORIZED,
NULL, 0);
ippDelete(request);
return (NULL);
}
#ifdef WIN32
if (fileinfo.st_mode & _S_IFDIR)
#else
if (S_ISDIR(fileinfo.st_mode))
#endif
{
ippDelete(request);
_cupsSetError(IPP_NOT_POSSIBLE, strerror(EISDIR), 0);
return (NULL);
}
#ifndef WIN32
if (!S_ISREG(fileinfo.st_mode))
length = 0;
else
#endif
length = ippLength(request) + fileinfo.st_size;
}
else
length = ippLength(request);
DEBUG_printf(("2cupsDoIORequest: Request length=%ld, total length=%ld",
(long)ippLength(request), (long)length));
if (http->authstring && !strncmp(http->authstring, "Local ", 6))
httpSetAuthString(http, NULL, NULL);
while (response == NULL)
{
DEBUG_puts("2cupsDoIORequest: setup...");
status = cupsSendRequest(http, request, resource, length);
DEBUG_printf(("2cupsDoIORequest: status=%d", status));
if (status == HTTP_CONTINUE && request->state == IPP_DATA && infile >= 0)
{
DEBUG_puts("2cupsDoIORequest: file write...");
#ifndef WIN32
if (S_ISREG(fileinfo.st_mode))
#endif
lseek(infile, 0, SEEK_SET);
while ((bytes = (int)read(infile, buffer, sizeof(buffer))) > 0)
{
if ((status = cupsWriteRequestData(http, buffer, bytes))
!= HTTP_CONTINUE)
break;
}
}
if (status != HTTP_ERROR)
{
response = cupsGetResponse(http, resource);
status = httpGetStatus(http);
}
DEBUG_printf(("2cupsDoIORequest: status=%d", status));
if (status == HTTP_ERROR ||
(status >= HTTP_BAD_REQUEST && status != HTTP_UNAUTHORIZED &&
status != HTTP_UPGRADE_REQUIRED))
{
_cupsSetHTTPError(status);
break;
}
if (response && outfile >= 0)
{
while ((bytes = (int)httpRead2(http, buffer, sizeof(buffer))) > 0)
if (write(outfile, buffer, bytes) < bytes)
break;
}
if (http->state != HTTP_WAITING)
{
httpFlush(http);
}
}
ippDelete(request);
return (response);
}
ipp_t *
cupsDoRequest(http_t *http,
ipp_t *request,
const char *resource)
{
DEBUG_printf(("cupsDoRequest(http=%p, request=%p(%s), resource=\"%s\")",
http, request,
request ? ippOpString(request->request.op.operation_id) : "?",
resource));
return (cupsDoIORequest(http, request, resource, -1, -1));
}
ipp_t *
cupsGetResponse(http_t *http,
const char *resource)
{
http_status_t status;
ipp_state_t state;
ipp_t *response = NULL;
DEBUG_printf(("cupsGetResponse(http=%p, resource=\"%s\")", http, resource));
if (!http)
http = _cupsConnect();
if (!http || (http->state != HTTP_POST_RECV && http->state != HTTP_POST_SEND))
return (NULL);
if (http->data_encoding == HTTP_ENCODE_CHUNKED)
{
DEBUG_puts("2cupsGetResponse: Finishing chunked POST...");
if (httpWrite2(http, "", 0) < 0)
return (NULL);
}
DEBUG_printf(("2cupsGetResponse: Update loop, http->status=%d...",
http->status));
do
{
status = httpUpdate(http);
}
while (status != HTTP_ERROR && http->state == HTTP_POST_RECV);
DEBUG_printf(("2cupsGetResponse: status=%d", status));
if (status == HTTP_OK)
{
response = ippNew();
while ((state = ippRead(http, response)) != IPP_DATA)
if (state == IPP_ERROR)
break;
if (state == IPP_ERROR)
{
DEBUG_puts("1cupsGetResponse: IPP read error!");
httpFlush(http);
ippDelete(response);
response = NULL;
http->status = status = HTTP_ERROR;
http->error = EINVAL;
}
}
else if (status != HTTP_ERROR)
{
httpFlush(http);
if (status == HTTP_UNAUTHORIZED)
{
DEBUG_puts("2cupsGetResponse: Need authorization...");
if (!cupsDoAuthentication(http, "POST", resource))
httpReconnect(http);
else
http->status = status = HTTP_AUTHORIZATION_CANCELED;
}
#ifdef HAVE_SSL
else if (status == HTTP_UPGRADE_REQUIRED)
{
DEBUG_puts("2cupsGetResponse: Need encryption...");
if (!httpReconnect(http))
httpEncryption(http, HTTP_ENCRYPT_REQUIRED);
}
#endif
}
if (response)
{
ipp_attribute_t *attr;
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), 0);
}
return (response);
}
ipp_status_t
cupsLastError(void)
{
return (_cupsGlobals()->last_error);
}
const char *
cupsLastErrorString(void)
{
return (_cupsGlobals()->last_status_message);
}
int
_cupsNextDelay(int current,
int *previous)
{
int next;
if (current > 0)
{
next = (current + *previous) % 12;
*previous = next < current ? 0 : current;
}
else
{
next = 1;
*previous = 0;
}
return (next);
}
ssize_t
cupsReadResponseData(
http_t *http,
char *buffer,
size_t length)
{
DEBUG_printf(("cupsReadResponseData(http=%p, buffer=%p, "
"length=" CUPS_LLFMT ")", http, buffer, CUPS_LLCAST length));
if (!http)
{
_cups_globals_t *cg = _cupsGlobals();
if ((http = cg->http) == NULL)
{
_cupsSetError(IPP_INTERNAL_ERROR, _("No active connection"), 1);
return (-1);
}
}
return (httpRead2(http, buffer, length));
}
http_status_t
cupsSendRequest(http_t *http,
ipp_t *request,
const char *resource,
size_t length)
{
http_status_t status;
int got_status;
ipp_state_t state;
http_status_t expect;
DEBUG_printf(("cupsSendRequest(http=%p, request=%p(%s), resource=\"%s\", "
"length=" CUPS_LLFMT ")", http, request,
request ? ippOpString(request->request.op.operation_id) : "?",
resource, CUPS_LLCAST length));
if (!request || !resource)
{
_cupsSetError(IPP_INTERNAL_ERROR, strerror(EINVAL), 0);
return (HTTP_ERROR);
}
if (!http)
if ((http = _cupsConnect()) == NULL)
return (HTTP_SERVICE_UNAVAILABLE);
if (http->state == HTTP_GET_SEND ||
http->state == HTTP_POST_SEND)
{
DEBUG_puts("2cupsSendRequest: Flush prior response.");
httpFlush(http);
}
else if (http->state != HTTP_WAITING)
{
DEBUG_printf(("1cupsSendRequest: Unknown HTTP state (%d), "
"reconnecting.", http->state));
if (httpReconnect(http))
return (HTTP_ERROR);
}
#ifdef HAVE_SSL
if (ippFindAttribute(request, "auth-info", IPP_TAG_TEXT) &&
!httpAddrLocalhost(http->hostaddr) && !http->tls &&
httpEncryption(http, HTTP_ENCRYPT_REQUIRED))
{
DEBUG_puts("1cupsSendRequest: Unable to encrypt connection.");
return (HTTP_SERVICE_UNAVAILABLE);
}
#endif
if (!_cups_strcasecmp(http->fields[HTTP_FIELD_CONNECTION], "close"))
{
DEBUG_puts("2cupsSendRequest: Connection: close");
httpClearFields(http);
if (httpReconnect(http))
{
DEBUG_puts("1cupsSendRequest: Unable to reconnect.");
return (HTTP_SERVICE_UNAVAILABLE);
}
}
expect = HTTP_CONTINUE;
for (;;)
{
DEBUG_puts("2cupsSendRequest: Setup...");
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))
{
_cupsSetNegotiateAuthString(http, "POST", resource);
}
#endif
httpSetField(http, HTTP_FIELD_AUTHORIZATION, http->authstring);
DEBUG_printf(("2cupsSendRequest: authstring=\"%s\"", http->authstring));
DEBUG_puts("2cupsSendRequest: Sending HTTP POST...");
if (httpPost(http, resource))
{
DEBUG_puts("2cupsSendRequest: POST failed, reconnecting.");
if (httpReconnect(http))
{
DEBUG_puts("1cupsSendRequest: Unable to reconnect.");
return (HTTP_SERVICE_UNAVAILABLE);
}
else
continue;
}
DEBUG_puts("2cupsSendRequest: Writing IPP request...");
request->state = IPP_IDLE;
status = HTTP_CONTINUE;
got_status = 0;
while ((state = ippWrite(http, request)) != IPP_DATA)
if (state == IPP_ERROR)
break;
else if (httpCheck(http))
{
got_status = 1;
_httpUpdate(http, &status);
if (status >= HTTP_MULTIPLE_CHOICES)
break;
}
if (state == IPP_ERROR)
{
DEBUG_puts("1cupsSendRequest: Unable to send IPP request.");
http->status = HTTP_ERROR;
http->state = HTTP_WAITING;
return (HTTP_ERROR);
}
if (!got_status)
{
if (expect == HTTP_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));
if (status >= HTTP_MULTIPLE_CHOICES)
{
_cupsSetHTTPError(status);
do
{
status = httpUpdate(http);
}
while (status != HTTP_ERROR && http->state == HTTP_POST_RECV);
httpFlush(http);
}
switch (status)
{
case HTTP_ERROR :
case HTTP_CONTINUE :
case HTTP_OK :
DEBUG_printf(("1cupsSendRequest: Returning %d.", status));
return (status);
case HTTP_UNAUTHORIZED :
if (cupsDoAuthentication(http, "POST", resource))
{
DEBUG_puts("1cupsSendRequest: Returning HTTP_AUTHORIZATION_CANCELED.");
return (HTTP_AUTHORIZATION_CANCELED);
}
DEBUG_puts("2cupsSendRequest: Reconnecting after HTTP_UNAUTHORIZED.");
if (httpReconnect(http))
{
DEBUG_puts("1cupsSendRequest: Unable to reconnect.");
return (HTTP_SERVICE_UNAVAILABLE);
}
break;
#ifdef HAVE_SSL
case HTTP_UPGRADE_REQUIRED :
DEBUG_puts("2cupsSendRequest: Reconnecting after "
"HTTP_UPGRADE_REQUIRED.");
if (httpReconnect(http))
{
DEBUG_puts("1cupsSendRequest: Unable to reconnect.");
return (HTTP_SERVICE_UNAVAILABLE);
}
DEBUG_puts("2cupsSendRequest: Upgrading to TLS.");
if (httpEncryption(http, HTTP_ENCRYPT_REQUIRED))
{
DEBUG_puts("1cupsSendRequest: Unable to encrypt connection.");
return (HTTP_SERVICE_UNAVAILABLE);
}
break;
#endif
case HTTP_EXPECTATION_FAILED :
expect = (http_status_t)0;
DEBUG_puts("2cupsSendRequest: Reconnecting after "
"HTTP_EXPECTATION_FAILED.");
if (httpReconnect(http))
{
DEBUG_puts("1cupsSendRequest: Unable to reconnect.");
return (HTTP_SERVICE_UNAVAILABLE);
}
break;
default :
return (status);
}
}
}
http_status_t
cupsWriteRequestData(
http_t *http,
const char *buffer,
size_t length)
{
int wused;
DEBUG_printf(("cupsWriteRequestData(http=%p, buffer=%p, "
"length=" CUPS_LLFMT ")", http, buffer, CUPS_LLCAST length));
if (!http)
{
_cups_globals_t *cg = _cupsGlobals();
if ((http = cg->http) == NULL)
{
_cupsSetError(IPP_INTERNAL_ERROR, _("No active connection"), 1);
DEBUG_puts("1cupsWriteRequestData: Returning HTTP_ERROR.");
return (HTTP_ERROR);
}
}
wused = http->wused;
if (httpWrite2(http, buffer, length) < 0)
{
DEBUG_puts("1cupsWriteRequestData: Returning HTTP_ERROR.");
_cupsSetError(IPP_INTERNAL_ERROR, strerror(http->error), 0);
return (HTTP_ERROR);
}
if (length >= HTTP_MAX_BUFFER ||
http->wused < wused ||
(wused > 0 && http->wused == length))
{
if (_httpWait(http, 0, 1))
{
http_status_t status;
_httpUpdate(http, &status);
if (status >= HTTP_MULTIPLE_CHOICES)
{
_cupsSetHTTPError(status);
do
{
status = httpUpdate(http);
}
while (status != HTTP_ERROR && http->state == HTTP_POST_RECV);
httpFlush(http);
}
DEBUG_printf(("1cupsWriteRequestData: Returning %d.\n", status));
return (status);
}
}
DEBUG_puts("1cupsWriteRequestData: Returning HTTP_CONTINUE.");
return (HTTP_CONTINUE);
}
http_t *
_cupsConnect(void)
{
_cups_globals_t *cg = _cupsGlobals();
if (cg->http)
{
if (strcmp(cg->http->hostname, cg->server) ||
cg->ipp_port != _httpAddrPort(cg->http->hostaddr) ||
(cg->http->encryption != cg->encryption &&
cg->http->encryption == HTTP_ENCRYPT_NEVER))
{
httpClose(cg->http);
cg->http = NULL;
}
}
if (!cg->http)
{
if ((cg->http = httpConnectEncrypt(cupsServer(), ippPort(),
cupsEncryption())) == NULL)
{
if (errno)
_cupsSetError(IPP_SERVICE_UNAVAILABLE, NULL, 0);
else
_cupsSetError(IPP_SERVICE_UNAVAILABLE,
_("Unable to connect to host."), 1);
}
}
return (cg->http);
}
void
_cupsSetError(ipp_status_t status,
const char *message,
int localize)
{
_cups_globals_t *cg;
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)
{
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));
}
void
_cupsSetHTTPError(http_status_t status)
{
switch (status)
{
case HTTP_NOT_FOUND :
_cupsSetError(IPP_NOT_FOUND, httpStatus(status), 0);
break;
case HTTP_UNAUTHORIZED :
_cupsSetError(IPP_NOT_AUTHENTICATED, httpStatus(status), 0);
break;
case HTTP_AUTHORIZATION_CANCELED :
_cupsSetError(IPP_AUTHENTICATION_CANCELED, httpStatus(status), 0);
break;
case HTTP_FORBIDDEN :
_cupsSetError(IPP_FORBIDDEN, httpStatus(status), 0);
break;
case HTTP_BAD_REQUEST :
_cupsSetError(IPP_BAD_REQUEST, httpStatus(status), 0);
break;
case HTTP_REQUEST_TOO_LARGE :
_cupsSetError(IPP_REQUEST_VALUE, httpStatus(status), 0);
break;
case HTTP_NOT_IMPLEMENTED :
_cupsSetError(IPP_OPERATION_NOT_SUPPORTED, httpStatus(status), 0);
break;
case HTTP_NOT_SUPPORTED :
_cupsSetError(IPP_VERSION_NOT_SUPPORTED, httpStatus(status), 0);
break;
case HTTP_UPGRADE_REQUIRED :
_cupsSetError(IPP_UPGRADE_REQUIRED, httpStatus(status), 0);
break;
case HTTP_PKI_ERROR :
_cupsSetError(IPP_PKI_ERROR, httpStatus(status), 0);
break;
case HTTP_ERROR :
_cupsSetError(IPP_INTERNAL_ERROR, strerror(errno), 0);
break;
default :
DEBUG_printf(("4_cupsSetHTTPError: HTTP error %d mapped to "
"IPP_SERVICE_UNAVAILABLE!", status));
_cupsSetError(IPP_SERVICE_UNAVAILABLE, httpStatus(status), 0);
break;
}
}