request.c   [plain text]


/*
 * "$Id: request.c 6712 2007-07-24 00:13:05Z mike $"
 *
 *   IPP utilities for the Common UNIX Printing System (CUPS).
 *
 *   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
 *   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.
 *   cupsDoRequest()     - Do an IPP request.
 *   _cupsSetError()     - Set the last IPP status code and status-message.
 *   _cupsSetHTTPError() - Set the last error using the HTTP status.
 */

/*
 * Include necessary headers...
 */

#include "globals.h"
#include "debug.h"
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#if defined(WIN32) || defined(__EMX__)
#  include <io.h>
#else
#  include <unistd.h>
#endif /* WIN32 || __EMX__ */
#ifndef O_BINARY
#  define O_BINARY 0
#endif /* O_BINARY */


/*
 * '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.
 */

ipp_t *					/* O - Response data */
cupsDoFileRequest(http_t     *http,	/* I - HTTP connection to server */
                  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 */
{
  ipp_t		*response;		/* IPP response data */
  int		infile;			/* Input file */


  if (filename)
  {
    if ((infile = open(filename, O_RDONLY | O_BINARY)) < 0)
    {
     /*
      * Can't get file information!
      */

      _cupsSetError(errno == ENOENT ? IPP_NOT_FOUND : IPP_NOT_AUTHORIZED,
                    strerror(errno));

      ippDelete(request);

      return (NULL);
    }
  }
  else
    infile = -1;

  response = cupsDoIORequest(http, request, resource, infile, -1);

  if (infile >= 0)
    close(infile);

  return (response);
}


/*
 * '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.
 *
 * If "infile" is a valid file descriptor, cupsDoIORequest() copies
 * all of the data from the file after the IPP request message.
 *
 * If "outfile" is a valid file descriptor, cupsDoIORequest() copies
 * all of the data after the IPP response message to the file.
 *
 * @since CUPS 1.3@
 */

ipp_t *					/* O - Response data */
cupsDoIORequest(http_t     *http,	/* I - HTTP connection to server */
                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 */
  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 */
  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)"));

  if (http == NULL || request == NULL || resource == NULL)
  {
    if (request != NULL)
      ippDelete(request);

    _cupsSetError(IPP_INTERNAL_ERROR, NULL);

    return (NULL);
  }

 /*
  * See if we have a file to send...
  */

  if (infile >= 0)
  {
    if (fstat(infile, &fileinfo))
    {
     /*
      * Can't get file information!
      */

      _cupsSetError(errno == ENOENT ? IPP_NOT_FOUND : IPP_NOT_AUTHORIZED,
                    strerror(errno));

      ippDelete(request);

      return (NULL);
    }

#ifdef WIN32
    if (fileinfo.st_mode & _S_IFDIR)
#else
    if (S_ISDIR(fileinfo.st_mode))
#endif /* WIN32 */
    {
     /*
      * Can't send a directory...
      */

      ippDelete(request);

      _cupsSetError(IPP_NOT_POSSIBLE, strerror(EISDIR));

      return (NULL);
    }
  }

#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_ENCRYPT_REQUIRED))
    return (NULL);
#endif /* HAVE_SSL */

 /*
  * 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...");

   /*
    * Setup the HTTP variables needed...
    */

    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;
    }

    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(("cupsDoFileRequest: authstring=\"%s\"\n", http->authstring));

   /*
    * Try the request...
    */

    DEBUG_puts("cupsDoFileRequest: post...");

    if (httpPost(http, resource))
    {
      if (httpReconnect(http))
      {
        status = HTTP_ERROR;
        break;
      }
      else
        continue;
    }

   /*
    * Send the IPP data...
    */

    DEBUG_puts("cupsDoFileRequest: ipp write...");

    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;

	if ((status = httpUpdate(http)) != HTTP_CONTINUE)
	  break;
      }

    if (!got_status)
    {
     /*
      * Wait up to 1 second to get the 100-continue response...
      */

      if (httpWait(http, 1000))
        status = httpUpdate(http);
    }
    else if (httpCheck(http))
      status = httpUpdate(http);

    if (status == HTTP_CONTINUE && state == IPP_DATA && infile >= 0)
    {
      DEBUG_puts("cupsDoFileRequest: file write...");

     /*
      * Send the file...
      */

#ifndef WIN32
      if (S_ISREG(fileinfo.st_mode))
#endif /* WIN32 */
      lseek(infile, 0, SEEK_SET);

      while ((bytes = (int)read(infile, buffer, sizeof(buffer))) > 0)
      {
	if (httpCheck(http))
	{
	  if ((status = httpUpdate(http)) != HTTP_CONTINUE)
	    break;
        }

  	if (httpWrite2(http, buffer, bytes) < bytes)
          break;
      }
    }

   /*
    * Get the server's return status...
    */

    DEBUG_puts("cupsDoFileRequest: update...");

    while (status == HTTP_CONTINUE)
      status = httpUpdate(http);

    DEBUG_printf(("cupsDoFileRequest: status = %d\n", status));

    if (status == HTTP_UNAUTHORIZED)
    {
      DEBUG_puts("cupsDoFileRequest: unauthorized...");

     /*
      * Flush any error message...
      */

      httpFlush(http);

     /*
      * See if we can do authentication...
      */

      if (cupsDoAuthentication(http, "POST", resource))
        break;

      if (httpReconnect(http))
      {
        status = HTTP_ERROR;
	break;
      }

      continue;
    }
    else if (status == HTTP_ERROR)
    {
      DEBUG_printf(("cupsDoFileRequest: http->error=%d (%s)\n", http->error,
                    strerror(http->error)));

#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)
    {
      /* Flush any error message... */
      httpFlush(http);

      /* Reconnect... */
      if (httpReconnect(http))
      {
        status = HTTP_ERROR;
        break;
      }

      /* Upgrade with encryption... */
      httpEncryption(http, HTTP_ENCRYPT_REQUIRED);

      /* 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...
      */

      expect = (http_status_t)0;
    }
    else if (status != HTTP_OK)
    {
      DEBUG_printf(("cupsDoFileRequest: error %d...\n", status));

     /*
      * Flush any error message...
      */

      httpFlush(http);
      break;
    }
    else
    {
     /*
      * Read the response...
      */

      DEBUG_puts("cupsDoFileRequest: response...");

      response = ippNew();

      while ((state = ippRead(http, response)) != IPP_DATA)
	if (state == IPP_ERROR)
	  break;

      if (state == IPP_ERROR)
      {
       /*
        * Delete the response...
	*/

        DEBUG_puts("IPP read error!");
	ippDelete(response);
	response = NULL;

        _cupsSetError(IPP_SERVICE_UNAVAILABLE, strerror(errno));

	break;
      }
      else if (outfile >= 0)
      {
       /*
        * Write trailing data to file...
	*/

	while ((bytes = (int)httpRead2(http, buffer, sizeof(buffer))) > 0)
	  if (write(outfile, buffer, bytes) < bytes)
	    break;
      }
      else
      {
       /*
        * Flush any remaining data...
        */

        httpFlush(http);
      }
    }
  }

 /*
  * Delete the original request and return the response...
  */
  
  ippDelete(request);

  if (response)
  {
    ipp_attribute_t	*attr;		/* status-message attribute */


    attr = ippFindAttribute(response, "status-message", IPP_TAG_TEXT);

    _cupsSetError(response->request.status.status_code,
                   attr ? attr->values[0].string.text :
		       ippErrorString(response->request.status.status_code));
  }
  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.
 */

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 */
{
  return (cupsDoFileRequest(http, request, resource, NULL));
}


/*
 * '_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 */
{
  _cups_globals_t	*cg;		/* Global data */


  cg             = _cupsGlobals();
  cg->last_error = status;

  if (cg->last_status_message)
  {
    free(cg->last_status_message);

    cg->last_status_message = NULL;
  }

  if (message)
    cg->last_status_message = strdup(message);
}


/*
 * '_cupsSetHTTPError()' - Set the last error using the HTTP status.
 */

void
_cupsSetHTTPError(http_status_t status)	/* I - HTTP status code */
{
  switch (status)
  {
    case HTTP_NOT_FOUND :
	_cupsSetError(IPP_NOT_FOUND, httpStatus(status));
	break;

    case HTTP_UNAUTHORIZED :
	_cupsSetError(IPP_NOT_AUTHORIZED, httpStatus(status));
	break;

    case HTTP_FORBIDDEN :
	_cupsSetError(IPP_FORBIDDEN, httpStatus(status));
	break;

    case HTTP_BAD_REQUEST :
	_cupsSetError(IPP_BAD_REQUEST, httpStatus(status));
	break;

    case HTTP_REQUEST_TOO_LARGE :
	_cupsSetError(IPP_REQUEST_VALUE, httpStatus(status));
	break;

    case HTTP_NOT_IMPLEMENTED :
	_cupsSetError(IPP_OPERATION_NOT_SUPPORTED, httpStatus(status));
	break;

    case HTTP_NOT_SUPPORTED :
	_cupsSetError(IPP_VERSION_NOT_SUPPORTED, httpStatus(status));
	break;

    default :
	DEBUG_printf(("HTTP error %d mapped to IPP_SERVICE_UNAVAILABLE!\n",
		      status));
	_cupsSetError(IPP_SERVICE_UNAVAILABLE, httpStatus(status));
	break;
  }
}


/*
 * End of "$Id: request.c 6712 2007-07-24 00:13:05Z mike $".
 */