/*
 * "$Id: util.c 6145 2006-12-06 20:10:16Z mike $"
 *
 *   Printing utilities for the Common UNIX Printing System (CUPS).
 *
 *   Copyright 1997-2006 by Easy Software Products.
 *
 *   These coded instructions, statements, and computer programs are the
 *   property of Easy Software Products 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 please contact Easy Software Products
 *   at:
 *
 *       Attn: CUPS Licensing Information
 *       Easy Software Products
 *       44141 Airport View Drive, Suite 204
 *       Hollywood, Maryland 20636 USA
 *
 *       Voice: (301) 373-9600
 *       EMail: cups-info@cups.org
 *         WWW: http://www.cups.org
 *
 *   This file is subject to the Apple OS-Developed Software exception.
 *
 * Contents:
 *
 *   cupsCancelJob()        - Cancel a print job on the default server.
 *   cupsFreeJobs()         - Free memory used by job data.
 *   cupsGetClasses()       - Get a list of printer classes from the default
 *                            server.
 *   cupsGetDefault()       - Get the default printer or class from the default
 *                            server.
 *   cupsGetDefault2()      - Get the default printer or class from the
 *                            specified server.
 *   cupsGetJobs()          - Get the jobs from the default server.
 *   cupsGetJobs2()         - Get the jobs from the specified server.
 *   cupsGetPPD()           - Get the PPD file for a printer on the default
 *                            server.
 *   cupsGetPPD2()          - Get the PPD file for a printer on the specified
 *                            server.
 *   cupsGetPrinters()      - Get a list of printers from the default server.
 *   cupsLastError()        - Return the last IPP status code.
 *   cupsLastErrorString()  - Return the last IPP status-message.
 *   cupsPrintFile()        - Print a file to a printer or class on the default
 *                            server.
 *   cupsPrintFile2()       - Print a file to a printer or class on the
 *                            specified server.
 *   cupsPrintFiles()       - Print one or more files to a printer or class on
 *                            the default server.
 *   cupsPrintFiles2()      - Print one or more files to a printer or class on
 *                            the specified server.
 *   cups_connect()         - Connect to the specified host...
 *   cups_get_printer_uri() - Get the printer-uri-supported attribute for the
 *                            first printer in a class.
 */

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


/*
 * Local functions...
 */

static char	*cups_connect(const char *name, char *printer, char *hostname);
static int	cups_get_printer_uri(http_t *http, const char *name,
		                     char *host, int hostsize, int *port,
				     char *resource, int resourcesize,
				     int depth);


/*
 * 'cupsCancelJob()' - Cancel a print job on the default server.
 *
 * Use the cupsLastError() and cupsLastErrorString() functions to get
 * the cause of any failure.
 */

int					/* O - 1 on success, 0 on failure */
cupsCancelJob(const char *name,		/* I - Name of printer or class */
              int        job)		/* I - Job ID */
{
  char		printer[HTTP_MAX_URI],	/* Printer name */
		hostname[HTTP_MAX_URI],	/* Hostname */
		uri[HTTP_MAX_URI];	/* Printer URI */
  ipp_t		*request,		/* IPP request */
		*response;		/* IPP response */
  cups_lang_t	*language;		/* Language info */
  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */


 /*
  * See if we can connect to the server...
  */

  if (!cups_connect(name, printer, hostname))
  {
    DEBUG_puts("Unable to connect to server!");

    return (0);
  }

 /*
  * Create a printer URI...
  */

  if (httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
                       "localhost", 0, "/printers/%s", printer) != HTTP_URI_OK)
  {
    _cupsSetError(IPP_INTERNAL_ERROR, NULL);

    return (0);
  }

 /*
  * Build an IPP_CANCEL_JOB request, which requires the following
  * attributes:
  *
  *    attributes-charset
  *    attributes-natural-language
  *    printer-uri
  *    job-id
  *    [requesting-user-name]
  */

  request = ippNew();

  request->request.op.operation_id = IPP_CANCEL_JOB;
  request->request.op.request_id   = 1;

  language = cupsLangDefault();

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
               "attributes-charset", NULL, cupsLangEncoding(language));

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
               "attributes-natural-language", NULL,
               language != NULL ? language->language : "C");

  cupsLangFree(language);

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
               NULL, uri);

  ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", job);

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
               NULL, cupsUser());

 /*
  * Do the request...
  */

  if ((response = cupsDoRequest(cg->http, request, "/jobs/")) != NULL)
    ippDelete(response);

  return (cg->last_error < IPP_REDIRECTION_OTHER_SITE);
}


/*
 * 'cupsFreeJobs()' - Free memory used by job data.
 */

void
cupsFreeJobs(int        num_jobs,	/* I - Number of jobs */
             cups_job_t *jobs)		/* I - Jobs */
{
  int	i;				/* Looping var */


  if (num_jobs <= 0 || jobs == NULL)
    return;

  for (i = 0; i < num_jobs; i ++)
  {
    free(jobs[i].dest);
    free(jobs[i].user);
    free(jobs[i].format);
    free(jobs[i].title);
  }

  free(jobs);
}


/*
 * 'cupsGetClasses()' - Get a list of printer classes from the default server.
 *
 * This function is deprecated - use cupsGetDests() instead.
 *
 * @deprecated@
 */

int					/* O - Number of classes */
cupsGetClasses(char ***classes)		/* O - Classes */
{
  int		n;			/* Number of classes */
  ipp_t		*request,		/* IPP Request */
		*response;		/* IPP Response */
  ipp_attribute_t *attr;		/* Current attribute */
  cups_lang_t	*language;		/* Default language */
  char		**temp;			/* Temporary pointer */
  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */


  if (classes == NULL)
  {
    _cupsSetError(IPP_INTERNAL_ERROR, NULL);

    return (0);
  }

 /*
  * Try to connect to the server...
  */

  if (!cups_connect("default", NULL, NULL))
  {
    DEBUG_puts("Unable to connect to server!");

    return (0);
  }

 /*
  * Build a CUPS_GET_CLASSES request, which requires the following
  * attributes:
  *
  *    attributes-charset
  *    attributes-natural-language
  *    requested-attributes
  */

  request = ippNew();

  request->request.op.operation_id = CUPS_GET_CLASSES;
  request->request.op.request_id   = 1;

  language = cupsLangDefault();

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
               "attributes-charset", NULL, cupsLangEncoding(language));

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
               "attributes-natural-language", NULL, language->language);

  cupsLangFree(language);

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
               "requested-attributes", NULL, "printer-name");

 /*
  * Do the request and get back a response...
  */

  n        = 0;
  *classes = NULL;

  if ((response = cupsDoRequest(cg->http, request, "/")) != NULL)
  {
    for (attr = response->attrs; attr != NULL; attr = attr->next)
      if (attr->name != NULL &&
          strcasecmp(attr->name, "printer-name") == 0 &&
          attr->value_tag == IPP_TAG_NAME)
      {
        if (n == 0)
	  temp = malloc(sizeof(char *));
	else
	  temp = realloc(*classes, sizeof(char *) * (n + 1));

	if (temp == NULL)
	{
	 /*
	  * Ran out of memory!
	  */

          while (n > 0)
	  {
	    n --;
	    free((*classes)[n]);
	  }

	  free(*classes);
	  ippDelete(response);
	  return (0);
	}

        *classes = temp;
        temp[n]  = strdup(attr->values[0].string.text);
	n ++;
      }

    ippDelete(response);
  }

  return (n);
}


/*
 * 'cupsGetDefault()' - Get the default printer or class for the default server.
 *
 * This function returns the default printer or class as defined by
 * the LPDEST or PRINTER environment variables. If these environment
 * variables are not set, the server default destination is returned.
 * Applications should use the cupsGetDests() and cupsGetDest() functions
 * to get the user-defined default printer, as this function does not
 * support the lpoptions-defined default printer.
 */

const char *				/* O - Default printer or NULL */
cupsGetDefault(void)
{
  const char	*var;			/* Environment variable */
  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */


 /*
  * First see if the LPDEST or PRINTER environment variables are
  * set...  However, if PRINTER is set to "lp", ignore it to work
  * around a "feature" in most Linux distributions - the default
  * user login scripts set PRINTER to "lp"...
  */

  if ((var = getenv("LPDEST")) != NULL)
    return (var);
  else if ((var = getenv("PRINTER")) != NULL && strcmp(var, "lp") != 0)
    return (var);

 /*
  * Try to connect to the server...
  */

  if (!cups_connect("default", NULL, NULL))
  {
    DEBUG_puts("Unable to connect to server!");

    return (NULL);
  }

 /*
  * Return the default printer...
  */

  return (cupsGetDefault2(cg->http));
}


/*
 * 'cupsGetDefault2()' - Get the default printer or class for the specified server.
 *
 * This function returns the default printer or class as defined by
 * the LPDEST or PRINTER environment variables. If these environment
 * variables are not set, the server default destination is returned.
 * Applications should use the cupsGetDests() and cupsGetDest() functions
 * to get the user-defined default printer, as this function does not
 * support the lpoptions-defined default printer.
 *
 * @since CUPS 1.1.21@
 */

const char *				/* O - Default printer or NULL */
cupsGetDefault2(http_t *http)		/* I - HTTP connection */
{
  ipp_t		*request,		/* IPP Request */
		*response;		/* IPP Response */
  ipp_attribute_t *attr;		/* Current attribute */
  cups_lang_t	*language;		/* Default language */
  const char	*var;			/* Environment variable */
  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */


 /*
  * First see if the LPDEST or PRINTER environment variables are
  * set...  However, if PRINTER is set to "lp", ignore it to work
  * around a "feature" in most Linux distributions - the default
  * user login scripts set PRINTER to "lp"...
  */

  if ((var = getenv("LPDEST")) != NULL)
    return (var);
  else if ((var = getenv("PRINTER")) != NULL && strcmp(var, "lp") != 0)
    return (var);

 /*
  * Range check input...
  */

  if (!http)
    return (NULL);

 /*
  * Build a CUPS_GET_DEFAULT request, which requires the following
  * attributes:
  *
  *    attributes-charset
  *    attributes-natural-language
  */

  request = ippNew();

  request->request.op.operation_id = CUPS_GET_DEFAULT;
  request->request.op.request_id   = 1;

  language = cupsLangDefault();

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
               "attributes-charset", NULL, cupsLangEncoding(language));

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
               "attributes-natural-language", NULL, language->language);

  cupsLangFree(language);

 /*
  * Do the request and get back a response...
  */

  if ((response = cupsDoRequest(http, request, "/")) != NULL)
  {
    if ((attr = ippFindAttribute(response, "printer-name", IPP_TAG_NAME)) != NULL)
    {
      strlcpy(cg->def_printer, attr->values[0].string.text, sizeof(cg->def_printer));
      ippDelete(response);
      return (cg->def_printer);
    }

    ippDelete(response);
  }

  return (NULL);
}


/*
 * 'cupsGetJobs()' - Get the jobs from the default server.
 */

int					/* O - Number of jobs */
cupsGetJobs(cups_job_t **jobs,		/* O - Job data */
            const char *mydest,		/* I - NULL = all destinations,       *
	                                 *     otherwise show jobs for mydest */
            int        myjobs,		/* I - 0 = all users, 1 = mine */
	    int        completed)	/* I - -1 = show all, 0 = active, *
	                                 *     1 = completed jobs         */
{
  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */

 /*
  * Try to connect to the server...
  */

  if (!cups_connect("default", NULL, NULL))
  {
    DEBUG_puts("Unable to connect to server!");

    return (-1);
  }

 /*
  * Return the jobs...
  */

  return (cupsGetJobs2(cg->http, jobs, mydest, myjobs, completed));
}



/*
 * 'cupsGetJobs2()' - Get the jobs from the specified server.
 *
 * @since CUPS 1.1.21@
 */

int					/* O - Number of jobs */
cupsGetJobs2(http_t     *http,		/* I - HTTP connection */
             cups_job_t **jobs,		/* O - Job data */
             const char *mydest,	/* I - NULL = all destinations,       *
	                                 *     otherwise show jobs for mydest */
             int        myjobs,		/* I - 0 = all users, 1 = mine */
	     int        completed)	/* I - -1 = show all, 0 = active, *
	                                 *     1 = completed jobs         */
{
  int		n;			/* Number of jobs */
  ipp_t		*request,		/* IPP Request */
		*response;		/* IPP Response */
  ipp_attribute_t *attr;		/* Current attribute */
  cups_lang_t	*language;		/* Default language */
  cups_job_t	*temp;			/* Temporary pointer */
  int		id,			/* job-id */
		priority,		/* job-priority */
		size;			/* job-k-octets */
  ipp_jstate_t	state;			/* job-state */
  time_t	completed_time,		/* time-at-completed */
		creation_time,		/* time-at-creation */
		processing_time;	/* time-at-processing */
  const char	*dest,			/* job-printer-uri */
		*format,		/* document-format */
		*title,			/* job-name */
		*user;			/* job-originating-user-name */
  char		uri[HTTP_MAX_URI];	/* URI for jobs */
  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */
  static const char * const attrs[] =	/* Requested attributes */
		{
		  "job-id",
		  "job-priority",
		  "job-k-octets",
		  "job-state",
		  "time-at-completed",
		  "time-at-creation",
		  "time-at-processing",
		  "job-printer-uri",
		  "document-format",
		  "job-name",
		  "job-originating-user-name"
		};


 /*
  * Range check input...
  */

  if (!http || !jobs)
  {
    _cupsSetError(IPP_INTERNAL_ERROR, NULL);

    return (-1);
  }

 /*
  * Get the right URI...
  */

  if (mydest)
  {
    if (httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
                         "localhost", 0, "/printers/%s", mydest) != HTTP_URI_OK)
    {
      _cupsSetError(IPP_INTERNAL_ERROR, NULL);

      return (-1);
    }
  }
  else
    strcpy(uri, "ipp://localhost/jobs");


 /*
  * Build an IPP_GET_JOBS request, which requires the following
  * attributes:
  *
  *    attributes-charset
  *    attributes-natural-language
  *    printer-uri
  *    requesting-user-name
  *    which-jobs
  *    my-jobs
  *    requested-attributes
  */

  request = ippNew();

  request->request.op.operation_id = IPP_GET_JOBS;
  request->request.op.request_id   = 1;

  language = cupsLangDefault();

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
               "attributes-charset", NULL, cupsLangEncoding(language));

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
               "attributes-natural-language", NULL, language->language);

  cupsLangFree(language);

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
               "printer-uri", NULL, uri);

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
               "requesting-user-name", NULL, cupsUser());

  if (myjobs)
    ippAddBoolean(request, IPP_TAG_OPERATION, "my-jobs", 1);

  if (completed > 0)
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
                 "which-jobs", NULL, "completed");
  else if (completed < 0)
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
                 "which-jobs", NULL, "all");

  ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
                "requested-attributes", sizeof(attrs) / sizeof(attrs[0]),
		NULL, attrs);

 /*
  * Do the request and get back a response...
  */

  n     = 0;
  *jobs = NULL;

  if ((response = cupsDoRequest(http, request, "/")) != NULL)
  {
    for (attr = response->attrs; attr != NULL; attr = attr->next)
    {
     /*
      * Skip leading attributes until we hit a job...
      */

      while (attr != NULL && attr->group_tag != IPP_TAG_JOB)
        attr = attr->next;

      if (attr == NULL)
        break;

     /*
      * Pull the needed attributes from this job...
      */

      id              = 0;
      size            = 0;
      priority        = 50;
      state           = IPP_JOB_PENDING;
      user            = "unknown";
      dest            = NULL;
      format          = "application/octet-stream";
      title           = "untitled";
      creation_time   = 0;
      completed_time  = 0;
      processing_time = 0;

      while (attr != NULL && attr->group_tag == IPP_TAG_JOB)
      {
        if (strcmp(attr->name, "job-id") == 0 &&
	    attr->value_tag == IPP_TAG_INTEGER)
	  id = attr->values[0].integer;
        else if (strcmp(attr->name, "job-state") == 0 &&
	         attr->value_tag == IPP_TAG_ENUM)
	  state = (ipp_jstate_t)attr->values[0].integer;
        else if (strcmp(attr->name, "job-priority") == 0 &&
	         attr->value_tag == IPP_TAG_INTEGER)
	  priority = attr->values[0].integer;
        else if (strcmp(attr->name, "job-k-octets") == 0 &&
	         attr->value_tag == IPP_TAG_INTEGER)
	  size = attr->values[0].integer;
        else if (strcmp(attr->name, "time-at-completed") == 0 &&
	         attr->value_tag == IPP_TAG_INTEGER)
	  completed_time = attr->values[0].integer;
        else if (strcmp(attr->name, "time-at-creation") == 0 &&
	         attr->value_tag == IPP_TAG_INTEGER)
	  creation_time = attr->values[0].integer;
        else if (strcmp(attr->name, "time-at-processing") == 0 &&
	         attr->value_tag == IPP_TAG_INTEGER)
	  processing_time = attr->values[0].integer;
        else if (strcmp(attr->name, "job-printer-uri") == 0 &&
	         attr->value_tag == IPP_TAG_URI)
	{
	  if ((dest = strrchr(attr->values[0].string.text, '/')) != NULL)
	    dest ++;
        }
        else if (strcmp(attr->name, "job-originating-user-name") == 0 &&
	         attr->value_tag == IPP_TAG_NAME)
	  user = attr->values[0].string.text;
        else if (strcmp(attr->name, "document-format") == 0 &&
	         attr->value_tag == IPP_TAG_MIMETYPE)
	  format = attr->values[0].string.text;
        else if (strcmp(attr->name, "job-name") == 0 &&
	         (attr->value_tag == IPP_TAG_TEXT ||
		  attr->value_tag == IPP_TAG_NAME))
	  title = attr->values[0].string.text;

        attr = attr->next;
      }

     /*
      * See if we have everything needed...
      */

      if (dest == NULL || id == 0)
      {
        if (attr == NULL)
	  break;
	else
          continue;
      }

     /*
      * Allocate memory for the job...
      */

      if (n == 0)
        temp = malloc(sizeof(cups_job_t));
      else
	temp = realloc(*jobs, sizeof(cups_job_t) * (n + 1));

      if (temp == NULL)
      {
       /*
        * Ran out of memory!
        */

	cupsFreeJobs(n, *jobs);
	*jobs = NULL;

        ippDelete(response);
	return (0);
      }

      *jobs = temp;
      temp  += n;
      n ++;

     /*
      * Copy the data over...
      */

      temp->dest            = strdup(dest);
      temp->user            = strdup(user);
      temp->format          = strdup(format);
      temp->title           = strdup(title);
      temp->id              = id;
      temp->priority        = priority;
      temp->state           = state;
      temp->size            = size;
      temp->completed_time  = completed_time;
      temp->creation_time   = creation_time;
      temp->processing_time = processing_time;

      if (attr == NULL)
        break;
    }

    ippDelete(response);
  }

  if (n == 0 && cg->last_error >= IPP_BAD_REQUEST)
    return (-1);
  else
    return (n);
}


/*
 * 'cupsGetPPD()' - Get the PPD file for a printer on the default server.
 *
 * For classes, cupsGetPPD() returns the PPD file for the first printer
 * in the class.
 */

const char *				/* O - Filename for PPD file */
cupsGetPPD(const char *name)		/* I - Printer name */
{
  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */

 /*
  * See if we can connect to the server...
  */

  if (!cups_connect(name, NULL, NULL))
  {
    DEBUG_puts("Unable to connect to server!");

    return (NULL);
  }

 /*
  * Return the PPD file...
  */

  return (cupsGetPPD2(cg->http, name));
}


/*
 * 'cupsGetPPD2()' - Get the PPD file for a printer from the specified server.
 *
 * For classes, cupsGetPPD2() returns the PPD file for the first printer
 * in the class.
 *
 * @since CUPS 1.1.21@
 */

const char *				/* O - Filename for PPD file */
cupsGetPPD2(http_t     *http,		/* I - HTTP connection */
            const char *name)		/* I - Printer name */
{
  int		http_port;		/* Port number */
  char		http_hostname[HTTP_MAX_HOST];
					/* Hostname associated with connection */
  http_t	*http2;			/* Alternate HTTP connection */
  int		fd;			/* PPD file */
  char		localhost[HTTP_MAX_URI],/* Local hostname */
		hostname[HTTP_MAX_URI],	/* Hostname */
		resource[HTTP_MAX_URI];	/* Resource name */
  int		port;			/* Port number */
  http_status_t	status;			/* HTTP status from server */
  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */


 /*
  * Range check input...
  */

  DEBUG_printf(("cupsGetPPD2(http=%p, name=\"%s\")\n", http,
                name ? name : "(null)"));

  if (!http || !name)
  {
    if (!http)
      _cupsSetError(IPP_INTERNAL_ERROR, "No HTTP connection!");
    else
      _cupsSetError(IPP_INTERNAL_ERROR, "No printer name!");

    return (NULL);
  }

 /*
  * Try finding a printer URI for this printer...
  */

  if (!cups_get_printer_uri(http, name, hostname, sizeof(hostname), &port,
                            resource, sizeof(resource), 0))
    return (NULL);

  DEBUG_printf(("Printer hostname=\"%s\", port=%d\n", hostname, port));

 /*
  * Remap local hostname to localhost...
  */

  httpGetHostname(NULL, localhost, sizeof(localhost));

  DEBUG_printf(("Local hostname=\"%s\"\n", localhost));

  if (!strcasecmp(localhost, hostname))
    strcpy(hostname, "localhost");

 /*
  * Get the hostname and port number we are connected to...
  */

  httpGetHostname(http, http_hostname, sizeof(http_hostname));

#ifdef AF_INET6
  if (http->hostaddr->addr.sa_family == AF_INET6)
    http_port = ntohs(http->hostaddr->ipv6.sin6_port);
  else
#endif /* AF_INET6 */
  if (http->hostaddr->addr.sa_family == AF_INET)
    http_port = ntohs(http->hostaddr->ipv4.sin_port);
  else
    http_port = ippPort(); 

  DEBUG_printf(("Connection hostname=\"%s\", port=%d\n", http_hostname,
                http_port));

 /*
  * Reconnect to the correct server as needed...
  */

  if (!strcasecmp(http_hostname, hostname) && port == http_port)
    http2 = http;
  else if ((http2 = httpConnectEncrypt(hostname, port,
                                       cupsEncryption())) == NULL)
  {
    DEBUG_puts("Unable to connect to server!");

    return (NULL);
  }

 /*
  * Get a temp file...
  */

  if ((fd = cupsTempFd(cg->ppd_filename, sizeof(cg->ppd_filename))) < 0)
  {
   /*
    * Can't open file; close the server connection and return NULL...
    */

    _cupsSetError(IPP_INTERNAL_ERROR, strerror(errno));

    if (http2 != http)
      httpClose(http2);

    return (NULL);
  }

 /*
  * And send a request to the HTTP server...
  */

  strlcat(resource, ".ppd", sizeof(resource));

  status = cupsGetFd(http2, resource, fd);

  close(fd);

  if (http2 != http)
    httpClose(http2);

 /*
  * See if we actually got the file or an error...
  */

  if (status != HTTP_OK)
  {
    switch (status)
    {
      case HTTP_NOT_FOUND :
          _cupsSetError(IPP_NOT_FOUND, httpStatus(status));
	  break;

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

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

    unlink(cg->ppd_filename);

    return (NULL);
  }

 /*
  * Return the PPD file...
  */

  return (cg->ppd_filename);
}


/*
 * 'cupsGetPrinters()' - Get a list of printers from the default server.
 *
 * This function is deprecated - use cupsGetDests() instead.
 *
 * @deprecated@
 */

int					/* O - Number of printers */
cupsGetPrinters(char ***printers)	/* O - Printers */
{
  int		n;			/* Number of printers */
  ipp_t		*request,		/* IPP Request */
		*response;		/* IPP Response */
  ipp_attribute_t *attr;		/* Current attribute */
  cups_lang_t	*language;		/* Default language */
  char		**temp;			/* Temporary pointer */
  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */


  if (printers == NULL)
  {
    _cupsSetError(IPP_INTERNAL_ERROR, NULL);

    return (0);
  }

 /*
  * Try to connect to the server...
  */

  if (!cups_connect("default", NULL, NULL))
  {
    DEBUG_puts("Unable to connect to server!");

    return (0);
  }

 /*
  * Build a CUPS_GET_PRINTERS request, which requires the following
  * attributes:
  *
  *    attributes-charset
  *    attributes-natural-language
  *    requested-attributes
  */

  request = ippNew();

  request->request.op.operation_id = CUPS_GET_PRINTERS;
  request->request.op.request_id   = 1;

  language = cupsLangDefault();

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
               "attributes-charset", NULL, cupsLangEncoding(language));

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
               "attributes-natural-language", NULL, language->language);

  cupsLangFree(language);

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
               "requested-attributes", NULL, "printer-name");

  ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM,
                "printer-type", 0);

  ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_ENUM,
                "printer-type-mask", CUPS_PRINTER_CLASS);

 /*
  * Do the request and get back a response...
  */

  n         = 0;
  *printers = NULL;

  if ((response = cupsDoRequest(cg->http, request, "/")) != NULL)
  {
    for (attr = response->attrs; attr != NULL; attr = attr->next)
      if (attr->name != NULL &&
          strcasecmp(attr->name, "printer-name") == 0 &&
          attr->value_tag == IPP_TAG_NAME)
      {
        if (n == 0)
	  temp = malloc(sizeof(char *));
	else
	  temp = realloc(*printers, sizeof(char *) * (n + 1));

	if (temp == NULL)
	{
	 /*
	  * Ran out of memory!
	  */

	  while (n > 0)
	  {
	    n --;
	    free((*printers)[n]);
	  }

	  free(*printers);
	  ippDelete(response);
	  return (0);
	}

        *printers = temp;
        temp[n]   = strdup(attr->values[0].string.text);
	n ++;
      }

    ippDelete(response);
  }

  return (n);
}


/*
 * 'cupsLastError()' - Return the last IPP status code.
 */

ipp_status_t				/* O - IPP status code from last request */
cupsLastError(void)
{
  return (_cupsGlobals()->last_error);
}


/*
 * 'cupsLastErrorString()' - Return the last IPP status-message.
 *
 * @since CUPS 1.2@
 */

const char *				/* O - status-message text from last request */
cupsLastErrorString(void)
{
  return (_cupsGlobals()->last_status_message);
}


/*
 * 'cupsPrintFile()' - Print a file to a printer or class on the default server.
 */

int					/* O - Job ID */
cupsPrintFile(const char    *name,	/* I - Printer or class name */
              const char    *filename,	/* I - File to print */
	      const char    *title,	/* I - Title of job */
              int           num_options,/* I - Number of options */
	      cups_option_t *options)	/* I - Options */
{
  DEBUG_printf(("cupsPrintFile(name=\"%s\", filename=\"%s\", "
                "title=\"%s\", num_options=%d, options=%p)\n",
                name, filename, title, num_options, options));

  return (cupsPrintFiles(name, 1, &filename, title, num_options, options));
}


/*
 * 'cupsPrintFile2()' - Print a file to a printer or class on the specified server.
 *
 * @since CUPS 1.1.21@
 */

int					/* O - Job ID */
cupsPrintFile2(http_t        *http,	/* I - HTTP connection */
               const char    *name,	/* I - Printer or class name */
               const char    *filename,	/* I - File to print */
	       const char    *title,	/* I - Title of job */
               int           num_options,
					/* I - Number of options */
	       cups_option_t *options)	/* I - Options */
{
  DEBUG_printf(("cupsPrintFile2(http=%p, name=\"%s\", filename=\"%s\", "
                "title=\"%s\", num_options=%d, options=%p)\n",
                http, name, filename, title, num_options, options));

  return (cupsPrintFiles2(http, name, 1, &filename, title, num_options, options));
}


/*
 * 'cupsPrintFiles()' - Print one or more files to a printer or class on the
 *                      default server.
 */

int					/* O - Job ID */
cupsPrintFiles(const char    *name,	/* I - Printer or class name */
               int           num_files,	/* I - Number of files */
               const char    **files,	/* I - File(s) to print */
	       const char    *title,	/* I - Title of job */
               int           num_options,
					/* I - Number of options */
	       cups_option_t *options)	/* I - Options */
{
  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */

  DEBUG_printf(("cupsPrintFiles(name=\"%s\", num_files=%d, "
                "files=%p, title=\"%s\", num_options=%d, options=%p)\n",
                name, num_files, files, title, num_options, options));


 /*
  * Setup a connection and request data...
  */

  if (!cups_connect(name, NULL, NULL))
  {
    DEBUG_printf(("cupsPrintFiles: Unable to open connection - %s.\n",
                  strerror(errno)));
    DEBUG_puts("Unable to connect to server!");

    return (0);
  }

 /*
  * Print the file(s)...
  */

  return (cupsPrintFiles2(cg->http, name, num_files, files, title,
                          num_options, options));
}



/*
 * 'cupsPrintFiles2()' - Print one or more files to a printer or class on the
 *                       specified server.
 *
 * @since CUPS 1.1.21@
 */

int					/* O - Job ID */
cupsPrintFiles2(http_t        *http,	/* I - HTTP connection */
                const char    *name,	/* I - Printer or class name */
                int           num_files,/* I - Number of files */
                const char    **files,	/* I - File(s) to print */
	        const char    *title,	/* I - Title of job */
                int           num_options,
					/* I - Number of options */
	        cups_option_t *options)	/* I - Options */
{
  int		i;			/* Looping var */
  const char	*val;			/* Pointer to option value */
  ipp_t		*request;		/* IPP request */
  ipp_t		*response;		/* IPP response */
  ipp_attribute_t *attr;		/* IPP job-id attribute */
  char		uri[HTTP_MAX_URI];	/* Printer URI */
  cups_lang_t	*language;		/* Language to use */
  int		jobid;			/* New job ID */
  const char	*base;			/* Basename of current filename */


  DEBUG_printf(("cupsPrintFiles(http=%p, name=\"%s\", num_files=%d, "
                "files=%p, title=\"%s\", num_options=%d, options=%p)\n",
                http, name, num_files, files, title, num_options, options));

 /*
  * Range check input...
  */

  if (!http || !name || num_files < 1 || files == NULL)
  {
    _cupsSetError(IPP_INTERNAL_ERROR, NULL);

    return (0);
  }

 /*
  * Setup the printer URI...
  */

  if (httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
                       "localhost", 0, "/printers/%s", name) != HTTP_URI_OK)
  {
    _cupsSetError(IPP_INTERNAL_ERROR, NULL);

    return (0);
  }

 /*
  * Setup the request data...
  */

  language = cupsLangDefault();

 /*
  * Build a standard CUPS URI for the printer and fill the standard IPP
  * attributes...
  */

  if ((request = ippNew()) == NULL)
  {
    _cupsSetError(IPP_INTERNAL_ERROR, NULL);

    return (0);
  }

  request->request.op.operation_id = num_files == 1 ? IPP_PRINT_JOB :
                                                      IPP_CREATE_JOB;
  request->request.op.request_id   = 1;

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
               "attributes-charset", NULL, cupsLangEncoding(language));

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
               "attributes-natural-language", NULL,
               language != NULL ? language->language : "C");

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
               NULL, uri);

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
               NULL, cupsUser());

  if (title)
    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL,
                 title);

 /*
  * Then add all options...
  */

  cupsEncodeOptions(request, num_options, options);

 /*
  * Do the request...
  */

  snprintf(uri, sizeof(uri), "/printers/%s", name);

  if (num_files == 1)
    response = cupsDoFileRequest(http, request, uri, *files);
  else
    response = cupsDoRequest(http, request, uri);

  if (response == NULL)
    jobid = 0;
  else if (response->request.status.status_code > IPP_OK_CONFLICT)
  {
    DEBUG_printf(("IPP response code was 0x%x!\n",
                  response->request.status.status_code));
    jobid = 0;
  }
  else if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) == NULL)
  {
    DEBUG_puts("No job ID!");

    _cupsSetError(IPP_INTERNAL_ERROR, NULL);

    jobid = 0;
  }
  else
    jobid = attr->values[0].integer;

  if (response != NULL)
    ippDelete(response);

 /*
  * Handle multiple file jobs if the create-job operation worked...
  */

  if (jobid > 0 && num_files > 1)
    for (i = 0; i < num_files; i ++)
    {
     /*
      * Build a standard CUPS URI for the job and fill the standard IPP
      * attributes...
      */

      if ((request = ippNew()) == NULL)
	return (0);

      request->request.op.operation_id = IPP_SEND_DOCUMENT;
      request->request.op.request_id   = 1;

      snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", jobid);

      ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
        	   "attributes-charset", NULL, cupsLangEncoding(language));

      ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
        	   "attributes-natural-language", NULL,
        	   language != NULL ? language->language : "C");

      ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri",
        	   NULL, uri);

     /*
      * Handle raw print files...
      */

      if (cupsGetOption("raw", num_options, options))
	ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE,
	             "document-format", NULL, "application/vnd.cups-raw");
      else if ((val = cupsGetOption("document-format", num_options,
                                    options)) != NULL)
	ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE,
	             "document-format", NULL, val);
      else
	ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE,
	             "document-format", NULL, "application/octet-stream");

      ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
                   "requesting-user-name", NULL, cupsUser());

     /*
      * Add the original document filename...
      */

      if ((base = strrchr(files[i], '/')) != NULL)
        base ++;
      else
        base = files[i];

      ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "document-name",
                   NULL, base);

     /*
      * Is this the last document?
      */

      if (i == (num_files - 1))
        ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", 1);

     /*
      * Send the file...
      */

      snprintf(uri, sizeof(uri), "/printers/%s", name);

      if ((response = cupsDoFileRequest(http, request, uri,
                                        files[i])) != NULL)
	ippDelete(response);
    }

  cupsLangFree(language);

  return (jobid);
}


/*
 * 'cups_connect()' - Connect to the specified host...
 */

static char *				/* I - Printer name or NULL */
cups_connect(const char *name,		/* I - Destination (printer[@host]) */
	     char       *printer,	/* O - Printer name [HTTP_MAX_URI] */
             char       *hostname)	/* O - Hostname [HTTP_MAX_URI] */
{
  char	hostbuf[HTTP_MAX_URI],		/* Name of host */
	http_hostname[HTTP_MAX_HOST];	/* Hostname associated with connection */
  _cups_globals_t  *cg = _cupsGlobals();/* Pointer to library globals */


  DEBUG_printf(("cups_connect(\"%s\", %p, %p)\n", name, printer, hostname));

  if (name == NULL)
  {
    _cupsSetError(IPP_BAD_REQUEST, NULL);

    return (NULL);
  }

 /*
  * All jobs are now queued to cupsServer() to avoid hostname
  * resolution problems and to ensure that the user sees all
  * locally queued jobs locally.
  */

  strlcpy(hostbuf, cupsServer(), sizeof(hostbuf));

  httpGetHostname(cg->http, http_hostname, sizeof(http_hostname));

  if (hostname != NULL)
    strlcpy(hostname, hostbuf, HTTP_MAX_URI);
  else
    hostname = hostbuf;

  if (printer != NULL)
    strlcpy(printer, name, HTTP_MAX_URI);
  else
    printer = (char *)name;

  if (cg->http != NULL)
  {
    if (!strcasecmp(http_hostname, hostname))
      return (printer);

    httpClose(cg->http);
  }

  DEBUG_printf(("connecting to %s on port %d...\n", hostname, ippPort()));

  if ((cg->http = httpConnectEncrypt(hostname, ippPort(),
                                     cupsEncryption())) == NULL)
  {
    DEBUG_puts("Unable to connect to server!");

    _cupsSetError(IPP_SERVICE_UNAVAILABLE, strerror(errno));

    return (NULL);
  }
  else
    return (printer);
}


/*
 * 'cups_get_printer_uri()' - Get the printer-uri-supported attribute for the first printer in a class.
 */

static int				/* O - 1 on success, 0 on failure */
cups_get_printer_uri(
    http_t     *http,			/* I - HTTP connection */
    const char *name,			/* I - Name of printer or class */
    char       *host,			/* I - Hostname buffer */
    int        hostsize,		/* I - Size of hostname buffer */
    int        *port,			/* O - Port number */
    char       *resource,		/* I - Resource buffer */
    int        resourcesize,		/* I - Size of resource buffer */
    int        depth)			/* I - Depth of query */
{
  int		i;			/* Looping var */
  int		http_port;		/* Port number */
  http_t	*http2;			/* Alternate HTTP connection */
  ipp_t		*request,		/* IPP request */
		*response;		/* IPP response */
  ipp_attribute_t *attr;		/* Current attribute */
  char		uri[HTTP_MAX_URI],	/* printer-uri attribute */
		scheme[HTTP_MAX_URI],	/* Scheme name */
		username[HTTP_MAX_URI],	/* Username:password */
		classname[255],		/* Temporary class name */
		http_hostname[HTTP_MAX_HOST];
					/* Hostname associated with connection */
  static const char * const requested_attrs[] =
		{			/* Requested attributes */
		  "printer-uri-supported",
		  "printer-type",
		  "member-uris"
		};


  DEBUG_printf(("cups_get_printer_uri(http=%p, name=\"%s\", host=%p, "
                "hostsize=%d, resource=%p, resourcesize=%d, depth=%d)\n",
		http, name ? name : "(null)", host, hostsize,
		resource, resourcesize, depth));

 /*
  * Setup the printer URI...
  */

  if (httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
                       "localhost", 0, "/printers/%s", name) != HTTP_URI_OK)
  {
    _cupsSetError(IPP_INTERNAL_ERROR, "Unable to create printer-uri!");

    *host     = '\0';
    *resource = '\0';

    return (0);
  }

  DEBUG_printf(("cups_get_printer_uri: printer-uri=\"%s\"\n", uri));

 /*
  * Get the hostname and port number we are connected to...
  */

  httpGetHostname(http, http_hostname, sizeof(http_hostname));

#ifdef AF_INET6
  if (http->hostaddr->addr.sa_family == AF_INET6)
    http_port = ntohs(http->hostaddr->ipv6.sin6_port);
  else
#endif /* AF_INET6 */
  if (http->hostaddr->addr.sa_family == AF_INET)
    http_port = ntohs(http->hostaddr->ipv4.sin_port);
  else
    http_port = ippPort(); 

 /*
  * Build an IPP_GET_PRINTER_ATTRIBUTES request, which requires the following
  * attributes:
  *
  *    attributes-charset
  *    attributes-natural-language
  *    printer-uri
  *    requested-attributes
  */

  request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);

  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
               NULL, uri);

  ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
                "requested-attributes",
		sizeof(requested_attrs) / sizeof(requested_attrs[0]),
		NULL, requested_attrs);

 /*
  * Do the request and get back a response...
  */

  if ((response = cupsDoRequest(http, request, "/")) != NULL)
  {
    if ((attr = ippFindAttribute(response, "member-uris", IPP_TAG_URI)) != NULL)
    {
     /*
      * Get the first actual printer name in the class...
      */

      for (i = 0; i < attr->num_values; i ++)
      {
	httpSeparateURI(HTTP_URI_CODING_ALL, attr->values[i].string.text,
	                scheme, sizeof(scheme), username, sizeof(username),
			host, hostsize, port, resource, resourcesize);
	if (!strncmp(resource, "/printers/", 10))
	{
	 /*
	  * Found a printer!
	  */

          ippDelete(response);

	  return (1);
	}
      }

     /*
      * No printers in this class - try recursively looking for a printer,
      * but not more than 3 levels deep...
      */

      if (depth < 3)
      {
	for (i = 0; i < attr->num_values; i ++)
	{
	  httpSeparateURI(HTTP_URI_CODING_ALL, attr->values[i].string.text,
	                  scheme, sizeof(scheme), username, sizeof(username),
			  host, hostsize, port, resource, resourcesize);
	  if (!strncmp(resource, "/classes/", 9))
	  {
	   /*
	    * Found a class!  Connect to the right server...
	    */

	    if (!strcasecmp(http_hostname, host) && *port == http_port)
	      http2 = http;
	    else if ((http2 = httpConnectEncrypt(host, *port,
						 cupsEncryption())) == NULL)
	    {
	      DEBUG_puts("Unable to connect to server!");

	      continue;
	    }

           /*
	    * Look up printers on that server...
	    */

            strlcpy(classname, resource + 9, sizeof(classname));

            cups_get_printer_uri(http2, classname, host, hostsize, port,
	                         resource, resourcesize, depth + 1);

           /*
	    * Close the connection as needed...
	    */

	    if (http2 != http)
	      httpClose(http2);

            if (*host)
	      return (1);
	  }
	}
      }
    }
    else if ((attr = ippFindAttribute(response, "printer-uri-supported",
                                      IPP_TAG_URI)) != NULL)
    {
      httpSeparateURI(HTTP_URI_CODING_ALL, attr->values[0].string.text,
                      scheme, sizeof(scheme), username, sizeof(username),
		      host, hostsize, port, resource, resourcesize);
      ippDelete(response);

      return (1);
    }

    ippDelete(response);
  }

  if (cupsLastError() != IPP_NOT_FOUND)
    _cupsSetError(IPP_INTERNAL_ERROR, "No printer-uri found!");

  *host     = '\0';
  *resource = '\0';

  return (0);
}


/*
 * End of "$Id: util.c 6145 2006-12-06 20:10:16Z mike $".
 */
