Home | History | Annotate | Download | only in cups
      1 /*
      2  * Printing utilities for CUPS.
      3  *
      4  * Copyright 2007-2015 by Apple Inc.
      5  * Copyright 1997-2006 by Easy Software Products.
      6  *
      7  * These coded instructions, statements, and computer programs are the
      8  * property of Apple Inc. and are protected by Federal copyright
      9  * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
     10  * which should have been included with this file.  If this file is
     11  * missing or damaged, see the license at "http://www.cups.org/".
     12  *
     13  * This file is subject to the Apple OS-Developed Software exception.
     14  */
     15 
     16 /*
     17  * Include necessary headers...
     18  */
     19 
     20 #include "cups-private.h"
     21 #include <fcntl.h>
     22 #include <sys/stat.h>
     23 #if defined(WIN32) || defined(__EMX__)
     24 #  include <io.h>
     25 #else
     26 #  include <unistd.h>
     27 #endif /* WIN32 || __EMX__ */
     28 
     29 
     30 /*
     31  * 'cupsCancelJob()' - Cancel a print job on the default server.
     32  *
     33  * Pass @code CUPS_JOBID_ALL@ to cancel all jobs or @code CUPS_JOBID_CURRENT@
     34  * to cancel the current job on the named destination.
     35  *
     36  * Use the @link cupsLastError@ and @link cupsLastErrorString@ functions to get
     37  * the cause of any failure.
     38  */
     39 
     40 int					/* O - 1 on success, 0 on failure */
     41 cupsCancelJob(const char *name,		/* I - Name of printer or class */
     42               int        job_id)	/* I - Job ID, @code CUPS_JOBID_CURRENT@ for the current job, or @code CUPS_JOBID_ALL@ for all jobs */
     43 {
     44   return (cupsCancelJob2(CUPS_HTTP_DEFAULT, name, job_id, 0)
     45               < IPP_STATUS_REDIRECTION_OTHER_SITE);
     46 }
     47 
     48 
     49 /*
     50  * 'cupsCancelJob2()' - Cancel or purge a print job.
     51  *
     52  * Canceled jobs remain in the job history while purged jobs are removed
     53  * from the job history.
     54  *
     55  * Pass @code CUPS_JOBID_ALL@ to cancel all jobs or @code CUPS_JOBID_CURRENT@
     56  * to cancel the current job on the named destination.
     57  *
     58  * Use the @link cupsLastError@ and @link cupsLastErrorString@ functions to get
     59  * the cause of any failure.
     60  *
     61  * @since CUPS 1.4/macOS 10.6@
     62  */
     63 
     64 ipp_status_t				/* O - IPP status */
     65 cupsCancelJob2(http_t     *http,	/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
     66                const char *name,	/* I - Name of printer or class */
     67                int        job_id,	/* I - Job ID, @code CUPS_JOBID_CURRENT@ for the current job, or @code CUPS_JOBID_ALL@ for all jobs */
     68 	       int        purge)	/* I - 1 to purge, 0 to cancel */
     69 {
     70   char		uri[HTTP_MAX_URI];	/* Job/printer URI */
     71   ipp_t		*request;		/* IPP request */
     72 
     73 
     74  /*
     75   * Range check input...
     76   */
     77 
     78   if (job_id < -1 || (!name && job_id == 0))
     79   {
     80     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
     81     return (0);
     82   }
     83 
     84  /*
     85   * Connect to the default server as needed...
     86   */
     87 
     88   if (!http)
     89     if ((http = _cupsConnect()) == NULL)
     90       return (IPP_STATUS_ERROR_SERVICE_UNAVAILABLE);
     91 
     92  /*
     93   * Build an IPP_CANCEL_JOB or IPP_PURGE_JOBS request, which requires the following
     94   * attributes:
     95   *
     96   *    attributes-charset
     97   *    attributes-natural-language
     98   *    job-uri or printer-uri + job-id
     99   *    requesting-user-name
    100   *    [purge-job] or [purge-jobs]
    101   */
    102 
    103   request = ippNewRequest(job_id < 0 ? IPP_OP_PURGE_JOBS : IPP_OP_CANCEL_JOB);
    104 
    105   if (name)
    106   {
    107     httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
    108                      "localhost", ippPort(), "/printers/%s", name);
    109 
    110     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
    111                  uri);
    112     ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id",
    113                   job_id);
    114   }
    115   else if (job_id > 0)
    116   {
    117     snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", job_id);
    118 
    119     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
    120   }
    121 
    122   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
    123                NULL, cupsUser());
    124 
    125   if (purge && job_id >= 0)
    126     ippAddBoolean(request, IPP_TAG_OPERATION, "purge-job", 1);
    127   else if (!purge && job_id < 0)
    128     ippAddBoolean(request, IPP_TAG_OPERATION, "purge-jobs", 0);
    129 
    130  /*
    131   * Do the request...
    132   */
    133 
    134   ippDelete(cupsDoRequest(http, request, "/jobs/"));
    135 
    136   return (cupsLastError());
    137 }
    138 
    139 
    140 /*
    141  * 'cupsCreateJob()' - Create an empty job for streaming.
    142  *
    143  * Use this function when you want to stream print data using the
    144  * @link cupsStartDocument@, @link cupsWriteRequestData@, and
    145  * @link cupsFinishDocument@ functions.  If you have one or more files to
    146  * print, use the @link cupsPrintFile2@ or @link cupsPrintFiles2@ function
    147  * instead.
    148  *
    149  * @since CUPS 1.4/macOS 10.6@
    150  */
    151 
    152 int					/* O - Job ID or 0 on error */
    153 cupsCreateJob(
    154     http_t        *http,		/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
    155     const char    *name,		/* I - Destination name */
    156     const char    *title,		/* I - Title of job */
    157     int           num_options,		/* I - Number of options */
    158     cups_option_t *options)		/* I - Options */
    159 {
    160   char		printer_uri[1024],	/* Printer URI */
    161 		resource[1024];		/* Printer resource */
    162   ipp_t		*request,		/* Create-Job request */
    163 		*response;		/* Create-Job response */
    164   ipp_attribute_t *attr;		/* job-id attribute */
    165   int		job_id = 0;		/* job-id value */
    166 
    167 
    168   DEBUG_printf(("cupsCreateJob(http=%p, name=\"%s\", title=\"%s\", num_options=%d, options=%p)", (void *)http, name, title, num_options, (void *)options));
    169 
    170  /*
    171   * Range check input...
    172   */
    173 
    174   if (!name)
    175   {
    176     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
    177     return (0);
    178   }
    179 
    180  /*
    181   * Build a Create-Job request...
    182   */
    183 
    184   if ((request = ippNewRequest(IPP_OP_CREATE_JOB)) == NULL)
    185   {
    186     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(ENOMEM), 0);
    187     return (0);
    188   }
    189 
    190   httpAssembleURIf(HTTP_URI_CODING_ALL, printer_uri, sizeof(printer_uri), "ipp",
    191                    NULL, "localhost", ippPort(), "/printers/%s", name);
    192   snprintf(resource, sizeof(resource), "/printers/%s", name);
    193 
    194   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
    195                NULL, printer_uri);
    196   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
    197                NULL, cupsUser());
    198   if (title)
    199     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL,
    200                  title);
    201   cupsEncodeOptions2(request, num_options, options, IPP_TAG_OPERATION);
    202   cupsEncodeOptions2(request, num_options, options, IPP_TAG_JOB);
    203   cupsEncodeOptions2(request, num_options, options, IPP_TAG_SUBSCRIPTION);
    204 
    205  /*
    206   * Send the request and get the job-id...
    207   */
    208 
    209   response = cupsDoRequest(http, request, resource);
    210 
    211   if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) != NULL)
    212     job_id = attr->values[0].integer;
    213 
    214   ippDelete(response);
    215 
    216  /*
    217   * Return it...
    218   */
    219 
    220   return (job_id);
    221 }
    222 
    223 
    224 /*
    225  * 'cupsFinishDocument()' - Finish sending a document.
    226  *
    227  * The document must have been started using @link cupsStartDocument@.
    228  *
    229  * @since CUPS 1.4/macOS 10.6@
    230  */
    231 
    232 ipp_status_t				/* O - Status of document submission */
    233 cupsFinishDocument(http_t     *http,	/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
    234                    const char *name)	/* I - Destination name */
    235 {
    236   char	resource[1024];			/* Printer resource */
    237 
    238 
    239   snprintf(resource, sizeof(resource), "/printers/%s", name);
    240 
    241   ippDelete(cupsGetResponse(http, resource));
    242 
    243   return (cupsLastError());
    244 }
    245 
    246 
    247 /*
    248  * 'cupsFreeJobs()' - Free memory used by job data.
    249  */
    250 
    251 void
    252 cupsFreeJobs(int        num_jobs,	/* I - Number of jobs */
    253              cups_job_t *jobs)		/* I - Jobs */
    254 {
    255   int		i;			/* Looping var */
    256   cups_job_t	*job;			/* Current job */
    257 
    258 
    259   if (num_jobs <= 0 || !jobs)
    260     return;
    261 
    262   for (i = num_jobs, job = jobs; i > 0; i --, job ++)
    263   {
    264     _cupsStrFree(job->dest);
    265     _cupsStrFree(job->user);
    266     _cupsStrFree(job->format);
    267     _cupsStrFree(job->title);
    268   }
    269 
    270   free(jobs);
    271 }
    272 
    273 
    274 /*
    275  * 'cupsGetClasses()' - Get a list of printer classes from the default server.
    276  *
    277  * This function is deprecated and no longer returns a list of printer
    278  * classes - use @link cupsGetDests@ instead.
    279  *
    280  * @deprecated@
    281  */
    282 
    283 int					/* O - Number of classes */
    284 cupsGetClasses(char ***classes)		/* O - Classes */
    285 {
    286   if (classes)
    287     *classes = NULL;
    288 
    289   return (0);
    290 }
    291 
    292 
    293 /*
    294  * 'cupsGetDefault()' - Get the default printer or class for the default server.
    295  *
    296  * This function returns the default printer or class as defined by
    297  * the LPDEST or PRINTER environment variables. If these environment
    298  * variables are not set, the server default destination is returned.
    299  * Applications should use the @link cupsGetDests@ and @link cupsGetDest@
    300  * functions to get the user-defined default printer, as this function does
    301  * not support the lpoptions-defined default printer.
    302  */
    303 
    304 const char *				/* O - Default printer or @code NULL@ */
    305 cupsGetDefault(void)
    306 {
    307  /*
    308   * Return the default printer...
    309   */
    310 
    311   return (cupsGetDefault2(CUPS_HTTP_DEFAULT));
    312 }
    313 
    314 
    315 /*
    316  * 'cupsGetDefault2()' - Get the default printer or class for the specified server.
    317  *
    318  * This function returns the default printer or class as defined by
    319  * the LPDEST or PRINTER environment variables. If these environment
    320  * variables are not set, the server default destination is returned.
    321  * Applications should use the @link cupsGetDests@ and @link cupsGetDest@
    322  * functions to get the user-defined default printer, as this function does
    323  * not support the lpoptions-defined default printer.
    324  *
    325  * @since CUPS 1.1.21/macOS 10.4@
    326  */
    327 
    328 const char *				/* O - Default printer or @code NULL@ */
    329 cupsGetDefault2(http_t *http)		/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
    330 {
    331   ipp_t		*request,		/* IPP Request */
    332 		*response;		/* IPP Response */
    333   ipp_attribute_t *attr;		/* Current attribute */
    334   _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */
    335 
    336 
    337  /*
    338   * See if we have a user default printer set...
    339   */
    340 
    341   if (_cupsUserDefault(cg->def_printer, sizeof(cg->def_printer)))
    342     return (cg->def_printer);
    343 
    344  /*
    345   * Connect to the server as needed...
    346   */
    347 
    348   if (!http)
    349     if ((http = _cupsConnect()) == NULL)
    350       return (NULL);
    351 
    352  /*
    353   * Build a CUPS_GET_DEFAULT request, which requires the following
    354   * attributes:
    355   *
    356   *    attributes-charset
    357   *    attributes-natural-language
    358   */
    359 
    360   request = ippNewRequest(IPP_OP_CUPS_GET_DEFAULT);
    361 
    362  /*
    363   * Do the request and get back a response...
    364   */
    365 
    366   if ((response = cupsDoRequest(http, request, "/")) != NULL)
    367   {
    368     if ((attr = ippFindAttribute(response, "printer-name",
    369                                  IPP_TAG_NAME)) != NULL)
    370     {
    371       strlcpy(cg->def_printer, attr->values[0].string.text,
    372               sizeof(cg->def_printer));
    373       ippDelete(response);
    374       return (cg->def_printer);
    375     }
    376 
    377     ippDelete(response);
    378   }
    379 
    380   return (NULL);
    381 }
    382 
    383 
    384 /*
    385  * 'cupsGetJobs()' - Get the jobs from the default server.
    386  *
    387  * A "whichjobs" value of @code CUPS_WHICHJOBS_ALL@ returns all jobs regardless
    388  * of state, while @code CUPS_WHICHJOBS_ACTIVE@ returns jobs that are
    389  * pending, processing, or held and @code CUPS_WHICHJOBS_COMPLETED@ returns
    390  * jobs that are stopped, canceled, aborted, or completed.
    391  */
    392 
    393 int					/* O - Number of jobs */
    394 cupsGetJobs(cups_job_t **jobs,		/* O - Job data */
    395             const char *name,		/* I - @code NULL@ = all destinations, otherwise show jobs for named destination */
    396             int        myjobs,		/* I - 0 = all users, 1 = mine */
    397 	    int        whichjobs)	/* I - @code CUPS_WHICHJOBS_ALL@, @code CUPS_WHICHJOBS_ACTIVE@, or @code CUPS_WHICHJOBS_COMPLETED@ */
    398 {
    399  /*
    400   * Return the jobs...
    401   */
    402 
    403   return (cupsGetJobs2(CUPS_HTTP_DEFAULT, jobs, name, myjobs, whichjobs));
    404 }
    405 
    406 
    407 
    408 /*
    409  * 'cupsGetJobs2()' - Get the jobs from the specified server.
    410  *
    411  * A "whichjobs" value of @code CUPS_WHICHJOBS_ALL@ returns all jobs regardless
    412  * of state, while @code CUPS_WHICHJOBS_ACTIVE@ returns jobs that are
    413  * pending, processing, or held and @code CUPS_WHICHJOBS_COMPLETED@ returns
    414  * jobs that are stopped, canceled, aborted, or completed.
    415  *
    416  * @since CUPS 1.1.21/macOS 10.4@
    417  */
    418 
    419 int					/* O - Number of jobs */
    420 cupsGetJobs2(http_t     *http,		/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
    421              cups_job_t **jobs,		/* O - Job data */
    422              const char *name,		/* I - @code NULL@ = all destinations, otherwise show jobs for named destination */
    423              int        myjobs,		/* I - 0 = all users, 1 = mine */
    424 	     int        whichjobs)	/* I - @code CUPS_WHICHJOBS_ALL@, @code CUPS_WHICHJOBS_ACTIVE@, or @code CUPS_WHICHJOBS_COMPLETED@ */
    425 {
    426   int		n;			/* Number of jobs */
    427   ipp_t		*request,		/* IPP Request */
    428 		*response;		/* IPP Response */
    429   ipp_attribute_t *attr;		/* Current attribute */
    430   cups_job_t	*temp;			/* Temporary pointer */
    431   int		id,			/* job-id */
    432 		priority,		/* job-priority */
    433 		size;			/* job-k-octets */
    434   ipp_jstate_t	state;			/* job-state */
    435   time_t	completed_time,		/* time-at-completed */
    436 		creation_time,		/* time-at-creation */
    437 		processing_time;	/* time-at-processing */
    438   const char	*dest,			/* job-printer-uri */
    439 		*format,		/* document-format */
    440 		*title,			/* job-name */
    441 		*user;			/* job-originating-user-name */
    442   char		uri[HTTP_MAX_URI];	/* URI for jobs */
    443   _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */
    444   static const char * const attrs[] =	/* Requested attributes */
    445 		{
    446 		  "document-format",
    447 		  "job-id",
    448 		  "job-k-octets",
    449 		  "job-name",
    450 		  "job-originating-user-name",
    451 		  "job-printer-uri",
    452 		  "job-priority",
    453 		  "job-state",
    454 		  "time-at-completed",
    455 		  "time-at-creation",
    456 		  "time-at-processing"
    457 		};
    458 
    459 
    460  /*
    461   * Range check input...
    462   */
    463 
    464   if (!jobs)
    465   {
    466     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
    467 
    468     return (-1);
    469   }
    470 
    471  /*
    472   * Get the right URI...
    473   */
    474 
    475   if (name)
    476   {
    477     if (httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
    478                          "localhost", 0, "/printers/%s",
    479                          name) < HTTP_URI_STATUS_OK)
    480     {
    481       _cupsSetError(IPP_STATUS_ERROR_INTERNAL,
    482                     _("Unable to create printer-uri"), 1);
    483 
    484       return (-1);
    485     }
    486   }
    487   else
    488     strlcpy(uri, "ipp://localhost/", sizeof(uri));
    489 
    490   if (!http)
    491     if ((http = _cupsConnect()) == NULL)
    492       return (-1);
    493 
    494  /*
    495   * Build an IPP_GET_JOBS request, which requires the following
    496   * attributes:
    497   *
    498   *    attributes-charset
    499   *    attributes-natural-language
    500   *    printer-uri
    501   *    requesting-user-name
    502   *    which-jobs
    503   *    my-jobs
    504   *    requested-attributes
    505   */
    506 
    507   request = ippNewRequest(IPP_OP_GET_JOBS);
    508 
    509   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
    510                "printer-uri", NULL, uri);
    511 
    512   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
    513                "requesting-user-name", NULL, cupsUser());
    514 
    515   if (myjobs)
    516     ippAddBoolean(request, IPP_TAG_OPERATION, "my-jobs", 1);
    517 
    518   if (whichjobs == CUPS_WHICHJOBS_COMPLETED)
    519     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
    520                  "which-jobs", NULL, "completed");
    521   else if (whichjobs == CUPS_WHICHJOBS_ALL)
    522     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
    523                  "which-jobs", NULL, "all");
    524 
    525   ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
    526                 "requested-attributes", sizeof(attrs) / sizeof(attrs[0]),
    527 		NULL, attrs);
    528 
    529  /*
    530   * Do the request and get back a response...
    531   */
    532 
    533   n     = 0;
    534   *jobs = NULL;
    535 
    536   if ((response = cupsDoRequest(http, request, "/")) != NULL)
    537   {
    538     for (attr = response->attrs; attr; attr = attr->next)
    539     {
    540      /*
    541       * Skip leading attributes until we hit a job...
    542       */
    543 
    544       while (attr && attr->group_tag != IPP_TAG_JOB)
    545         attr = attr->next;
    546 
    547       if (!attr)
    548         break;
    549 
    550      /*
    551       * Pull the needed attributes from this job...
    552       */
    553 
    554       id              = 0;
    555       size            = 0;
    556       priority        = 50;
    557       state           = IPP_JSTATE_PENDING;
    558       user            = "unknown";
    559       dest            = NULL;
    560       format          = "application/octet-stream";
    561       title           = "untitled";
    562       creation_time   = 0;
    563       completed_time  = 0;
    564       processing_time = 0;
    565 
    566       while (attr && attr->group_tag == IPP_TAG_JOB)
    567       {
    568         if (!strcmp(attr->name, "job-id") &&
    569 	    attr->value_tag == IPP_TAG_INTEGER)
    570 	  id = attr->values[0].integer;
    571         else if (!strcmp(attr->name, "job-state") &&
    572 	         attr->value_tag == IPP_TAG_ENUM)
    573 	  state = (ipp_jstate_t)attr->values[0].integer;
    574         else if (!strcmp(attr->name, "job-priority") &&
    575 	         attr->value_tag == IPP_TAG_INTEGER)
    576 	  priority = attr->values[0].integer;
    577         else if (!strcmp(attr->name, "job-k-octets") &&
    578 	         attr->value_tag == IPP_TAG_INTEGER)
    579 	  size = attr->values[0].integer;
    580         else if (!strcmp(attr->name, "time-at-completed") &&
    581 	         attr->value_tag == IPP_TAG_INTEGER)
    582 	  completed_time = attr->values[0].integer;
    583         else if (!strcmp(attr->name, "time-at-creation") &&
    584 	         attr->value_tag == IPP_TAG_INTEGER)
    585 	  creation_time = attr->values[0].integer;
    586         else if (!strcmp(attr->name, "time-at-processing") &&
    587 	         attr->value_tag == IPP_TAG_INTEGER)
    588 	  processing_time = attr->values[0].integer;
    589         else if (!strcmp(attr->name, "job-printer-uri") &&
    590 	         attr->value_tag == IPP_TAG_URI)
    591 	{
    592 	  if ((dest = strrchr(attr->values[0].string.text, '/')) != NULL)
    593 	    dest ++;
    594         }
    595         else if (!strcmp(attr->name, "job-originating-user-name") &&
    596 	         attr->value_tag == IPP_TAG_NAME)
    597 	  user = attr->values[0].string.text;
    598         else if (!strcmp(attr->name, "document-format") &&
    599 	         attr->value_tag == IPP_TAG_MIMETYPE)
    600 	  format = attr->values[0].string.text;
    601         else if (!strcmp(attr->name, "job-name") &&
    602 	         (attr->value_tag == IPP_TAG_TEXT ||
    603 		  attr->value_tag == IPP_TAG_NAME))
    604 	  title = attr->values[0].string.text;
    605 
    606         attr = attr->next;
    607       }
    608 
    609      /*
    610       * See if we have everything needed...
    611       */
    612 
    613       if (!dest || !id)
    614       {
    615         if (!attr)
    616 	  break;
    617 	else
    618           continue;
    619       }
    620 
    621      /*
    622       * Allocate memory for the job...
    623       */
    624 
    625       if (n == 0)
    626         temp = malloc(sizeof(cups_job_t));
    627       else
    628 	temp = realloc(*jobs, sizeof(cups_job_t) * (size_t)(n + 1));
    629 
    630       if (!temp)
    631       {
    632        /*
    633         * Ran out of memory!
    634         */
    635 
    636         _cupsSetError(IPP_STATUS_ERROR_INTERNAL, NULL, 0);
    637 
    638 	cupsFreeJobs(n, *jobs);
    639 	*jobs = NULL;
    640 
    641         ippDelete(response);
    642 
    643 	return (-1);
    644       }
    645 
    646       *jobs = temp;
    647       temp  += n;
    648       n ++;
    649 
    650      /*
    651       * Copy the data over...
    652       */
    653 
    654       temp->dest            = _cupsStrAlloc(dest);
    655       temp->user            = _cupsStrAlloc(user);
    656       temp->format          = _cupsStrAlloc(format);
    657       temp->title           = _cupsStrAlloc(title);
    658       temp->id              = id;
    659       temp->priority        = priority;
    660       temp->state           = state;
    661       temp->size            = size;
    662       temp->completed_time  = completed_time;
    663       temp->creation_time   = creation_time;
    664       temp->processing_time = processing_time;
    665 
    666       if (!attr)
    667         break;
    668     }
    669 
    670     ippDelete(response);
    671   }
    672 
    673   if (n == 0 && cg->last_error >= IPP_STATUS_ERROR_BAD_REQUEST)
    674     return (-1);
    675   else
    676     return (n);
    677 }
    678 
    679 
    680 /*
    681  * 'cupsGetPrinters()' - Get a list of printers from the default server.
    682  *
    683  * This function is deprecated and no longer returns a list of printers - use
    684  * @link cupsGetDests@ instead.
    685  *
    686  * @deprecated@
    687  */
    688 
    689 int					/* O - Number of printers */
    690 cupsGetPrinters(char ***printers)	/* O - Printers */
    691 {
    692   if (printers)
    693     *printers = NULL;
    694 
    695   return (0);
    696 }
    697 
    698 
    699 /*
    700  * 'cupsPrintFile()' - Print a file to a printer or class on the default server.
    701  */
    702 
    703 int					/* O - Job ID or 0 on error */
    704 cupsPrintFile(const char    *name,	/* I - Destination name */
    705               const char    *filename,	/* I - File to print */
    706 	      const char    *title,	/* I - Title of job */
    707               int           num_options,/* I - Number of options */
    708 	      cups_option_t *options)	/* I - Options */
    709 {
    710   DEBUG_printf(("cupsPrintFile(name=\"%s\", filename=\"%s\", title=\"%s\", num_options=%d, options=%p)", name, filename, title, num_options, (void *)options));
    711 
    712   return (cupsPrintFiles2(CUPS_HTTP_DEFAULT, name, 1, &filename, title,
    713                           num_options, options));
    714 }
    715 
    716 
    717 /*
    718  * 'cupsPrintFile2()' - Print a file to a printer or class on the specified
    719  *                      server.
    720  *
    721  * @since CUPS 1.1.21/macOS 10.4@
    722  */
    723 
    724 int					/* O - Job ID or 0 on error */
    725 cupsPrintFile2(
    726     http_t        *http,		/* I - Connection to server */
    727     const char    *name,		/* I - Destination name */
    728     const char    *filename,		/* I - File to print */
    729     const char    *title,		/* I - Title of job */
    730     int           num_options,		/* I - Number of options */
    731     cups_option_t *options)		/* I - Options */
    732 {
    733   DEBUG_printf(("cupsPrintFile2(http=%p, name=\"%s\", filename=\"%s\",  title=\"%s\", num_options=%d, options=%p)", (void *)http, name, filename, title, num_options, (void *)options));
    734 
    735   return (cupsPrintFiles2(http, name, 1, &filename, title, num_options,
    736                           options));
    737 }
    738 
    739 
    740 /*
    741  * 'cupsPrintFiles()' - Print one or more files to a printer or class on the
    742  *                      default server.
    743  */
    744 
    745 int					/* O - Job ID or 0 on error */
    746 cupsPrintFiles(
    747     const char    *name,		/* I - Destination name */
    748     int           num_files,		/* I - Number of files */
    749     const char    **files,		/* I - File(s) to print */
    750     const char    *title,		/* I - Title of job */
    751     int           num_options,		/* I - Number of options */
    752     cups_option_t *options)		/* I - Options */
    753 {
    754   DEBUG_printf(("cupsPrintFiles(name=\"%s\", num_files=%d, files=%p, title=\"%s\", num_options=%d, options=%p)", name, num_files, (void *)files, title, num_options, (void *)options));
    755 
    756  /*
    757   * Print the file(s)...
    758   */
    759 
    760   return (cupsPrintFiles2(CUPS_HTTP_DEFAULT, name, num_files, files, title,
    761                           num_options, options));
    762 }
    763 
    764 
    765 /*
    766  * 'cupsPrintFiles2()' - Print one or more files to a printer or class on the
    767  *                       specified server.
    768  *
    769  * @since CUPS 1.1.21/macOS 10.4@
    770  */
    771 
    772 int					/* O - Job ID or 0 on error */
    773 cupsPrintFiles2(
    774     http_t        *http,		/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
    775     const char    *name,		/* I - Destination name */
    776     int           num_files,		/* I - Number of files */
    777     const char    **files,		/* I - File(s) to print */
    778     const char    *title,		/* I - Title of job */
    779     int           num_options,		/* I - Number of options */
    780     cups_option_t *options)		/* I - Options */
    781 {
    782   int		i;			/* Looping var */
    783   int		job_id;			/* New job ID */
    784   const char	*docname;		/* Basename of current filename */
    785   const char	*format;		/* Document format */
    786   cups_file_t	*fp;			/* Current file */
    787   char		buffer[8192];		/* Copy buffer */
    788   ssize_t	bytes;			/* Bytes in buffer */
    789   http_status_t	status;			/* Status of write */
    790   _cups_globals_t *cg = _cupsGlobals();	/* Global data */
    791   ipp_status_t	cancel_status;		/* Status code to preserve */
    792   char		*cancel_message;	/* Error message to preserve */
    793 
    794 
    795   DEBUG_printf(("cupsPrintFiles2(http=%p, name=\"%s\", num_files=%d, files=%p, title=\"%s\", num_options=%d, options=%p)", (void *)http, name, num_files, (void *)files, title, num_options, (void *)options));
    796 
    797  /*
    798   * Range check input...
    799   */
    800 
    801   if (!name || num_files < 1 || !files)
    802   {
    803     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
    804 
    805     return (0);
    806   }
    807 
    808  /*
    809   * Create the print job...
    810   */
    811 
    812   if ((job_id = cupsCreateJob(http, name, title, num_options, options)) == 0)
    813     return (0);
    814 
    815  /*
    816   * Send each of the files...
    817   */
    818 
    819   if (cupsGetOption("raw", num_options, options))
    820     format = CUPS_FORMAT_RAW;
    821   else if ((format = cupsGetOption("document-format", num_options,
    822 				   options)) == NULL)
    823     format = CUPS_FORMAT_AUTO;
    824 
    825   for (i = 0; i < num_files; i ++)
    826   {
    827    /*
    828     * Start the next file...
    829     */
    830 
    831     if ((docname = strrchr(files[i], '/')) != NULL)
    832       docname ++;
    833     else
    834       docname = files[i];
    835 
    836     if ((fp = cupsFileOpen(files[i], "rb")) == NULL)
    837     {
    838      /*
    839       * Unable to open print file, cancel the job and return...
    840       */
    841 
    842       _cupsSetError(IPP_STATUS_ERROR_DOCUMENT_ACCESS, NULL, 0);
    843       goto cancel_job;
    844     }
    845 
    846     status = cupsStartDocument(http, name, job_id, docname, format,
    847 			       i == (num_files - 1));
    848 
    849     while (status == HTTP_STATUS_CONTINUE &&
    850 	   (bytes = cupsFileRead(fp, buffer, sizeof(buffer))) > 0)
    851       status = cupsWriteRequestData(http, buffer, (size_t)bytes);
    852 
    853     cupsFileClose(fp);
    854 
    855     if (status != HTTP_STATUS_CONTINUE || cupsFinishDocument(http, name) != IPP_STATUS_OK)
    856     {
    857      /*
    858       * Unable to queue, cancel the job and return...
    859       */
    860 
    861       goto cancel_job;
    862     }
    863   }
    864 
    865   return (job_id);
    866 
    867  /*
    868   * If we get here, something happened while sending the print job so we need
    869   * to cancel the job without setting the last error (since we need to preserve
    870   * the current error...
    871   */
    872 
    873   cancel_job:
    874 
    875   cancel_status  = cg->last_error;
    876   cancel_message = cg->last_status_message ?
    877                        _cupsStrRetain(cg->last_status_message) : NULL;
    878 
    879   cupsCancelJob2(http, name, job_id, 0);
    880 
    881   cg->last_error          = cancel_status;
    882   cg->last_status_message = cancel_message;
    883 
    884   return (0);
    885 }
    886 
    887 
    888 /*
    889  * 'cupsStartDocument()' - Add a document to a job created with cupsCreateJob().
    890  *
    891  * Use @link cupsWriteRequestData@ to write data for the document and
    892  * @link cupsFinishDocument@ to finish the document and get the submission status.
    893  *
    894  * The MIME type constants @code CUPS_FORMAT_AUTO@, @code CUPS_FORMAT_PDF@,
    895  * @code CUPS_FORMAT_POSTSCRIPT@, @code CUPS_FORMAT_RAW@, and
    896  * @code CUPS_FORMAT_TEXT@ are provided for the "format" argument, although
    897  * any supported MIME type string can be supplied.
    898  *
    899  * @since CUPS 1.4/macOS 10.6@
    900  */
    901 
    902 http_status_t				/* O - HTTP status of request */
    903 cupsStartDocument(
    904     http_t     *http,			/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
    905     const char *name,			/* I - Destination name */
    906     int        job_id,			/* I - Job ID from @link cupsCreateJob@ */
    907     const char *docname,		/* I - Name of document */
    908     const char *format,			/* I - MIME type or @code CUPS_FORMAT_foo@ */
    909     int        last_document)		/* I - 1 for last document in job, 0 otherwise */
    910 {
    911   char		resource[1024],		/* Resource for destinatio */
    912 		printer_uri[1024];	/* Printer URI */
    913   ipp_t		*request;		/* Send-Document request */
    914   http_status_t	status;			/* HTTP status */
    915 
    916 
    917  /*
    918   * Create a Send-Document request...
    919   */
    920 
    921   if ((request = ippNewRequest(IPP_OP_SEND_DOCUMENT)) == NULL)
    922   {
    923     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(ENOMEM), 0);
    924     return (HTTP_STATUS_ERROR);
    925   }
    926 
    927   httpAssembleURIf(HTTP_URI_CODING_ALL, printer_uri, sizeof(printer_uri), "ipp",
    928                    NULL, "localhost", ippPort(), "/printers/%s", name);
    929   snprintf(resource, sizeof(resource), "/printers/%s", name);
    930 
    931   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
    932                NULL, printer_uri);
    933   ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", job_id);
    934   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
    935                NULL, cupsUser());
    936   if (docname)
    937     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "document-name",
    938                  NULL, docname);
    939   if (format)
    940     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE,
    941                  "document-format", NULL, format);
    942   ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", (char)last_document);
    943 
    944  /*
    945   * Send and delete the request, then return the status...
    946   */
    947 
    948   status = cupsSendRequest(http, request, resource, CUPS_LENGTH_VARIABLE);
    949 
    950   ippDelete(request);
    951 
    952   return (status);
    953 }
    954