1/*
2 * "$Id: util.c 12078 2014-07-31 11:45:57Z msweet $"
3 *
4 * Printing utilities for CUPS.
5 *
6 * Copyright 2007-2014 by Apple Inc.
7 * Copyright 1997-2006 by Easy Software Products.
8 *
9 * These coded instructions, statements, and computer programs are the
10 * property of Apple Inc. and are protected by Federal copyright
11 * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
12 * which should have been included with this file.  If this file is
13 * file is missing or damaged, see the license at "http://www.cups.org/".
14 *
15 * This file is subject to the Apple OS-Developed Software exception.
16 */
17
18/*
19 * Include necessary headers...
20 */
21
22#include "cups-private.h"
23#include <fcntl.h>
24#include <sys/stat.h>
25#if defined(WIN32) || defined(__EMX__)
26#  include <io.h>
27#else
28#  include <unistd.h>
29#endif /* WIN32 || __EMX__ */
30
31
32/*
33 * Local functions...
34 */
35
36static int	cups_get_printer_uri(http_t *http, const char *name,
37		                     char *host, int hostsize, int *port,
38				     char *resource, int resourcesize,
39				     int depth);
40
41
42/*
43 * 'cupsCancelJob()' - Cancel a print job on the default server.
44 *
45 * Pass @code CUPS_JOBID_ALL@ to cancel all jobs or @code CUPS_JOBID_CURRENT@
46 * to cancel the current job on the named destination.
47 *
48 * Use the @link cupsLastError@ and @link cupsLastErrorString@ functions to get
49 * the cause of any failure.
50 */
51
52int					/* O - 1 on success, 0 on failure */
53cupsCancelJob(const char *name,		/* I - Name of printer or class */
54              int        job_id)	/* I - Job ID, @code CUPS_JOBID_CURRENT@ for the current job, or @code CUPS_JOBID_ALL@ for all jobs */
55{
56  return (cupsCancelJob2(CUPS_HTTP_DEFAULT, name, job_id, 0)
57              < IPP_STATUS_REDIRECTION_OTHER_SITE);
58}
59
60
61/*
62 * 'cupsCancelJob2()' - Cancel or purge a print job.
63 *
64 * Canceled jobs remain in the job history while purged jobs are removed
65 * from the job history.
66 *
67 * Pass @code CUPS_JOBID_ALL@ to cancel all jobs or @code CUPS_JOBID_CURRENT@
68 * to cancel the current job on the named destination.
69 *
70 * Use the @link cupsLastError@ and @link cupsLastErrorString@ functions to get
71 * the cause of any failure.
72 *
73 * @since CUPS 1.4/OS X 10.6@
74 */
75
76ipp_status_t				/* O - IPP status */
77cupsCancelJob2(http_t     *http,	/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
78               const char *name,	/* I - Name of printer or class */
79               int        job_id,	/* I - Job ID, @code CUPS_JOBID_CURRENT@ for the current job, or @code CUPS_JOBID_ALL@ for all jobs */
80	       int        purge)	/* I - 1 to purge, 0 to cancel */
81{
82  char		uri[HTTP_MAX_URI];	/* Job/printer URI */
83  ipp_t		*request;		/* IPP request */
84
85
86 /*
87  * Range check input...
88  */
89
90  if (job_id < -1 || (!name && job_id == 0))
91  {
92    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
93    return (0);
94  }
95
96 /*
97  * Connect to the default server as needed...
98  */
99
100  if (!http)
101    if ((http = _cupsConnect()) == NULL)
102      return (IPP_STATUS_ERROR_SERVICE_UNAVAILABLE);
103
104 /*
105  * Build an IPP_CANCEL_JOB or IPP_PURGE_JOBS request, which requires the following
106  * attributes:
107  *
108  *    attributes-charset
109  *    attributes-natural-language
110  *    job-uri or printer-uri + job-id
111  *    requesting-user-name
112  *    [purge-job] or [purge-jobs]
113  */
114
115  request = ippNewRequest(job_id < 0 ? IPP_OP_PURGE_JOBS : IPP_OP_CANCEL_JOB);
116
117  if (name)
118  {
119    httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
120                     "localhost", ippPort(), "/printers/%s", name);
121
122    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
123                 uri);
124    ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id",
125                  job_id);
126  }
127  else if (job_id > 0)
128  {
129    snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", job_id);
130
131    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
132  }
133
134  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
135               NULL, cupsUser());
136
137  if (purge && job_id >= 0)
138    ippAddBoolean(request, IPP_TAG_OPERATION, "purge-job", 1);
139  else if (!purge && job_id < 0)
140    ippAddBoolean(request, IPP_TAG_OPERATION, "purge-jobs", 0);
141
142 /*
143  * Do the request...
144  */
145
146  ippDelete(cupsDoRequest(http, request, "/jobs/"));
147
148  return (cupsLastError());
149}
150
151
152/*
153 * 'cupsCreateJob()' - Create an empty job for streaming.
154 *
155 * Use this function when you want to stream print data using the
156 * @link cupsStartDocument@, @link cupsWriteRequestData@, and
157 * @link cupsFinishDocument@ functions.  If you have one or more files to
158 * print, use the @link cupsPrintFile2@ or @link cupsPrintFiles2@ function
159 * instead.
160 *
161 * @since CUPS 1.4/OS X 10.6@
162 */
163
164int					/* O - Job ID or 0 on error */
165cupsCreateJob(
166    http_t        *http,		/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
167    const char    *name,		/* I - Destination name */
168    const char    *title,		/* I - Title of job */
169    int           num_options,		/* I - Number of options */
170    cups_option_t *options)		/* I - Options */
171{
172  char		printer_uri[1024],	/* Printer URI */
173		resource[1024];		/* Printer resource */
174  ipp_t		*request,		/* Create-Job request */
175		*response;		/* Create-Job response */
176  ipp_attribute_t *attr;		/* job-id attribute */
177  int		job_id = 0;		/* job-id value */
178
179
180  DEBUG_printf(("cupsCreateJob(http=%p, name=\"%s\", title=\"%s\", "
181                "num_options=%d, options=%p)",
182                http, name, title, num_options, options));
183
184 /*
185  * Range check input...
186  */
187
188  if (!name)
189  {
190    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
191    return (0);
192  }
193
194 /*
195  * Build a Create-Job request...
196  */
197
198  if ((request = ippNewRequest(IPP_OP_CREATE_JOB)) == NULL)
199  {
200    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(ENOMEM), 0);
201    return (0);
202  }
203
204  httpAssembleURIf(HTTP_URI_CODING_ALL, printer_uri, sizeof(printer_uri), "ipp",
205                   NULL, "localhost", ippPort(), "/printers/%s", name);
206  snprintf(resource, sizeof(resource), "/printers/%s", name);
207
208  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
209               NULL, printer_uri);
210  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
211               NULL, cupsUser());
212  if (title)
213    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL,
214                 title);
215  cupsEncodeOptions2(request, num_options, options, IPP_TAG_OPERATION);
216  cupsEncodeOptions2(request, num_options, options, IPP_TAG_JOB);
217  cupsEncodeOptions2(request, num_options, options, IPP_TAG_SUBSCRIPTION);
218
219 /*
220  * Send the request and get the job-id...
221  */
222
223  response = cupsDoRequest(http, request, resource);
224
225  if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) != NULL)
226    job_id = attr->values[0].integer;
227
228  ippDelete(response);
229
230 /*
231  * Return it...
232  */
233
234  return (job_id);
235}
236
237
238/*
239 * 'cupsFinishDocument()' - Finish sending a document.
240 *
241 * The document must have been started using @link cupsStartDocument@.
242 *
243 * @since CUPS 1.4/OS X 10.6@
244 */
245
246ipp_status_t				/* O - Status of document submission */
247cupsFinishDocument(http_t     *http,	/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
248                   const char *name)	/* I - Destination name */
249{
250  char	resource[1024];			/* Printer resource */
251
252
253  snprintf(resource, sizeof(resource), "/printers/%s", name);
254
255  ippDelete(cupsGetResponse(http, resource));
256
257  return (cupsLastError());
258}
259
260
261/*
262 * 'cupsFreeJobs()' - Free memory used by job data.
263 */
264
265void
266cupsFreeJobs(int        num_jobs,	/* I - Number of jobs */
267             cups_job_t *jobs)		/* I - Jobs */
268{
269  int		i;			/* Looping var */
270  cups_job_t	*job;			/* Current job */
271
272
273  if (num_jobs <= 0 || !jobs)
274    return;
275
276  for (i = num_jobs, job = jobs; i > 0; i --, job ++)
277  {
278    _cupsStrFree(job->dest);
279    _cupsStrFree(job->user);
280    _cupsStrFree(job->format);
281    _cupsStrFree(job->title);
282  }
283
284  free(jobs);
285}
286
287
288/*
289 * 'cupsGetClasses()' - Get a list of printer classes from the default server.
290 *
291 * This function is deprecated and no longer returns a list of printer
292 * classes - use @link cupsGetDests@ instead.
293 *
294 * @deprecated@
295 */
296
297int					/* O - Number of classes */
298cupsGetClasses(char ***classes)		/* O - Classes */
299{
300  if (classes)
301    *classes = NULL;
302
303  return (0);
304}
305
306
307/*
308 * 'cupsGetDefault()' - Get the default printer or class for the default server.
309 *
310 * This function returns the default printer or class as defined by
311 * the LPDEST or PRINTER environment variables. If these environment
312 * variables are not set, the server default destination is returned.
313 * Applications should use the @link cupsGetDests@ and @link cupsGetDest@
314 * functions to get the user-defined default printer, as this function does
315 * not support the lpoptions-defined default printer.
316 */
317
318const char *				/* O - Default printer or @code NULL@ */
319cupsGetDefault(void)
320{
321 /*
322  * Return the default printer...
323  */
324
325  return (cupsGetDefault2(CUPS_HTTP_DEFAULT));
326}
327
328
329/*
330 * 'cupsGetDefault2()' - Get the default printer or class for the specified server.
331 *
332 * This function returns the default printer or class as defined by
333 * the LPDEST or PRINTER environment variables. If these environment
334 * variables are not set, the server default destination is returned.
335 * Applications should use the @link cupsGetDests@ and @link cupsGetDest@
336 * functions to get the user-defined default printer, as this function does
337 * not support the lpoptions-defined default printer.
338 *
339 * @since CUPS 1.1.21/OS X 10.4@
340 */
341
342const char *				/* O - Default printer or @code NULL@ */
343cupsGetDefault2(http_t *http)		/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
344{
345  ipp_t		*request,		/* IPP Request */
346		*response;		/* IPP Response */
347  ipp_attribute_t *attr;		/* Current attribute */
348  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */
349
350
351 /*
352  * See if we have a user default printer set...
353  */
354
355  if (_cupsUserDefault(cg->def_printer, sizeof(cg->def_printer)))
356    return (cg->def_printer);
357
358 /*
359  * Connect to the server as needed...
360  */
361
362  if (!http)
363    if ((http = _cupsConnect()) == NULL)
364      return (NULL);
365
366 /*
367  * Build a CUPS_GET_DEFAULT request, which requires the following
368  * attributes:
369  *
370  *    attributes-charset
371  *    attributes-natural-language
372  */
373
374  request = ippNewRequest(IPP_OP_CUPS_GET_DEFAULT);
375
376 /*
377  * Do the request and get back a response...
378  */
379
380  if ((response = cupsDoRequest(http, request, "/")) != NULL)
381  {
382    if ((attr = ippFindAttribute(response, "printer-name",
383                                 IPP_TAG_NAME)) != NULL)
384    {
385      strlcpy(cg->def_printer, attr->values[0].string.text,
386              sizeof(cg->def_printer));
387      ippDelete(response);
388      return (cg->def_printer);
389    }
390
391    ippDelete(response);
392  }
393
394  return (NULL);
395}
396
397
398/*
399 * 'cupsGetJobs()' - Get the jobs from the default server.
400 *
401 * A "whichjobs" value of @code CUPS_WHICHJOBS_ALL@ returns all jobs regardless
402 * of state, while @code CUPS_WHICHJOBS_ACTIVE@ returns jobs that are
403 * pending, processing, or held and @code CUPS_WHICHJOBS_COMPLETED@ returns
404 * jobs that are stopped, canceled, aborted, or completed.
405 */
406
407int					/* O - Number of jobs */
408cupsGetJobs(cups_job_t **jobs,		/* O - Job data */
409            const char *name,		/* I - @code NULL@ = all destinations, otherwise show jobs for named destination */
410            int        myjobs,		/* I - 0 = all users, 1 = mine */
411	    int        whichjobs)	/* I - @code CUPS_WHICHJOBS_ALL@, @code CUPS_WHICHJOBS_ACTIVE@, or @code CUPS_WHICHJOBS_COMPLETED@ */
412{
413 /*
414  * Return the jobs...
415  */
416
417  return (cupsGetJobs2(CUPS_HTTP_DEFAULT, jobs, name, myjobs, whichjobs));
418}
419
420
421
422/*
423 * 'cupsGetJobs2()' - Get the jobs from the specified server.
424 *
425 * A "whichjobs" value of @code CUPS_WHICHJOBS_ALL@ returns all jobs regardless
426 * of state, while @code CUPS_WHICHJOBS_ACTIVE@ returns jobs that are
427 * pending, processing, or held and @code CUPS_WHICHJOBS_COMPLETED@ returns
428 * jobs that are stopped, canceled, aborted, or completed.
429 *
430 * @since CUPS 1.1.21/OS X 10.4@
431 */
432
433int					/* O - Number of jobs */
434cupsGetJobs2(http_t     *http,		/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
435             cups_job_t **jobs,		/* O - Job data */
436             const char *name,		/* I - @code NULL@ = all destinations, otherwise show jobs for named destination */
437             int        myjobs,		/* I - 0 = all users, 1 = mine */
438	     int        whichjobs)	/* I - @code CUPS_WHICHJOBS_ALL@, @code CUPS_WHICHJOBS_ACTIVE@, or @code CUPS_WHICHJOBS_COMPLETED@ */
439{
440  int		n;			/* Number of jobs */
441  ipp_t		*request,		/* IPP Request */
442		*response;		/* IPP Response */
443  ipp_attribute_t *attr;		/* Current attribute */
444  cups_job_t	*temp;			/* Temporary pointer */
445  int		id,			/* job-id */
446		priority,		/* job-priority */
447		size;			/* job-k-octets */
448  ipp_jstate_t	state;			/* job-state */
449  time_t	completed_time,		/* time-at-completed */
450		creation_time,		/* time-at-creation */
451		processing_time;	/* time-at-processing */
452  const char	*dest,			/* job-printer-uri */
453		*format,		/* document-format */
454		*title,			/* job-name */
455		*user;			/* job-originating-user-name */
456  char		uri[HTTP_MAX_URI];	/* URI for jobs */
457  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */
458  static const char * const attrs[] =	/* Requested attributes */
459		{
460		  "document-format",
461		  "job-id",
462		  "job-k-octets",
463		  "job-name",
464		  "job-originating-user-name",
465		  "job-printer-uri",
466		  "job-priority",
467		  "job-state",
468		  "time-at-completed",
469		  "time-at-creation",
470		  "time-at-processing"
471		};
472
473
474 /*
475  * Range check input...
476  */
477
478  if (!jobs)
479  {
480    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
481
482    return (-1);
483  }
484
485 /*
486  * Get the right URI...
487  */
488
489  if (name)
490  {
491    if (httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
492                         "localhost", 0, "/printers/%s",
493                         name) < HTTP_URI_STATUS_OK)
494    {
495      _cupsSetError(IPP_STATUS_ERROR_INTERNAL,
496                    _("Unable to create printer-uri"), 1);
497
498      return (-1);
499    }
500  }
501  else
502    strlcpy(uri, "ipp://localhost/", sizeof(uri));
503
504  if (!http)
505    if ((http = _cupsConnect()) == NULL)
506      return (-1);
507
508 /*
509  * Build an IPP_GET_JOBS request, which requires the following
510  * attributes:
511  *
512  *    attributes-charset
513  *    attributes-natural-language
514  *    printer-uri
515  *    requesting-user-name
516  *    which-jobs
517  *    my-jobs
518  *    requested-attributes
519  */
520
521  request = ippNewRequest(IPP_OP_GET_JOBS);
522
523  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
524               "printer-uri", NULL, uri);
525
526  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
527               "requesting-user-name", NULL, cupsUser());
528
529  if (myjobs)
530    ippAddBoolean(request, IPP_TAG_OPERATION, "my-jobs", 1);
531
532  if (whichjobs == CUPS_WHICHJOBS_COMPLETED)
533    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
534                 "which-jobs", NULL, "completed");
535  else if (whichjobs == CUPS_WHICHJOBS_ALL)
536    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
537                 "which-jobs", NULL, "all");
538
539  ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
540                "requested-attributes", sizeof(attrs) / sizeof(attrs[0]),
541		NULL, attrs);
542
543 /*
544  * Do the request and get back a response...
545  */
546
547  n     = 0;
548  *jobs = NULL;
549
550  if ((response = cupsDoRequest(http, request, "/")) != NULL)
551  {
552    for (attr = response->attrs; attr; attr = attr->next)
553    {
554     /*
555      * Skip leading attributes until we hit a job...
556      */
557
558      while (attr && attr->group_tag != IPP_TAG_JOB)
559        attr = attr->next;
560
561      if (!attr)
562        break;
563
564     /*
565      * Pull the needed attributes from this job...
566      */
567
568      id              = 0;
569      size            = 0;
570      priority        = 50;
571      state           = IPP_JSTATE_PENDING;
572      user            = "unknown";
573      dest            = NULL;
574      format          = "application/octet-stream";
575      title           = "untitled";
576      creation_time   = 0;
577      completed_time  = 0;
578      processing_time = 0;
579
580      while (attr && attr->group_tag == IPP_TAG_JOB)
581      {
582        if (!strcmp(attr->name, "job-id") &&
583	    attr->value_tag == IPP_TAG_INTEGER)
584	  id = attr->values[0].integer;
585        else if (!strcmp(attr->name, "job-state") &&
586	         attr->value_tag == IPP_TAG_ENUM)
587	  state = (ipp_jstate_t)attr->values[0].integer;
588        else if (!strcmp(attr->name, "job-priority") &&
589	         attr->value_tag == IPP_TAG_INTEGER)
590	  priority = attr->values[0].integer;
591        else if (!strcmp(attr->name, "job-k-octets") &&
592	         attr->value_tag == IPP_TAG_INTEGER)
593	  size = attr->values[0].integer;
594        else if (!strcmp(attr->name, "time-at-completed") &&
595	         attr->value_tag == IPP_TAG_INTEGER)
596	  completed_time = attr->values[0].integer;
597        else if (!strcmp(attr->name, "time-at-creation") &&
598	         attr->value_tag == IPP_TAG_INTEGER)
599	  creation_time = attr->values[0].integer;
600        else if (!strcmp(attr->name, "time-at-processing") &&
601	         attr->value_tag == IPP_TAG_INTEGER)
602	  processing_time = attr->values[0].integer;
603        else if (!strcmp(attr->name, "job-printer-uri") &&
604	         attr->value_tag == IPP_TAG_URI)
605	{
606	  if ((dest = strrchr(attr->values[0].string.text, '/')) != NULL)
607	    dest ++;
608        }
609        else if (!strcmp(attr->name, "job-originating-user-name") &&
610	         attr->value_tag == IPP_TAG_NAME)
611	  user = attr->values[0].string.text;
612        else if (!strcmp(attr->name, "document-format") &&
613	         attr->value_tag == IPP_TAG_MIMETYPE)
614	  format = attr->values[0].string.text;
615        else if (!strcmp(attr->name, "job-name") &&
616	         (attr->value_tag == IPP_TAG_TEXT ||
617		  attr->value_tag == IPP_TAG_NAME))
618	  title = attr->values[0].string.text;
619
620        attr = attr->next;
621      }
622
623     /*
624      * See if we have everything needed...
625      */
626
627      if (!dest || !id)
628      {
629        if (!attr)
630	  break;
631	else
632          continue;
633      }
634
635     /*
636      * Allocate memory for the job...
637      */
638
639      if (n == 0)
640        temp = malloc(sizeof(cups_job_t));
641      else
642	temp = realloc(*jobs, sizeof(cups_job_t) * (size_t)(n + 1));
643
644      if (!temp)
645      {
646       /*
647        * Ran out of memory!
648        */
649
650        _cupsSetError(IPP_STATUS_ERROR_INTERNAL, NULL, 0);
651
652	cupsFreeJobs(n, *jobs);
653	*jobs = NULL;
654
655        ippDelete(response);
656
657	return (-1);
658      }
659
660      *jobs = temp;
661      temp  += n;
662      n ++;
663
664     /*
665      * Copy the data over...
666      */
667
668      temp->dest            = _cupsStrAlloc(dest);
669      temp->user            = _cupsStrAlloc(user);
670      temp->format          = _cupsStrAlloc(format);
671      temp->title           = _cupsStrAlloc(title);
672      temp->id              = id;
673      temp->priority        = priority;
674      temp->state           = state;
675      temp->size            = size;
676      temp->completed_time  = completed_time;
677      temp->creation_time   = creation_time;
678      temp->processing_time = processing_time;
679
680      if (!attr)
681        break;
682    }
683
684    ippDelete(response);
685  }
686
687  if (n == 0 && cg->last_error >= IPP_STATUS_ERROR_BAD_REQUEST)
688    return (-1);
689  else
690    return (n);
691}
692
693
694/*
695 * 'cupsGetPPD()' - Get the PPD file for a printer on the default server.
696 *
697 * For classes, @code cupsGetPPD@ returns the PPD file for the first printer
698 * in the class.
699 *
700 * The returned filename is stored in a static buffer and is overwritten with
701 * each call to @code cupsGetPPD@ or @link cupsGetPPD2@.  The caller "owns" the
702 * file that is created and must @code unlink@ the returned filename.
703 */
704
705const char *				/* O - Filename for PPD file */
706cupsGetPPD(const char *name)		/* I - Destination name */
707{
708  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */
709  time_t	modtime = 0;		/* Modification time */
710
711
712 /*
713  * Return the PPD file...
714  */
715
716  cg->ppd_filename[0] = '\0';
717
718  if (cupsGetPPD3(CUPS_HTTP_DEFAULT, name, &modtime, cg->ppd_filename,
719                  sizeof(cg->ppd_filename)) == HTTP_STATUS_OK)
720    return (cg->ppd_filename);
721  else
722    return (NULL);
723}
724
725
726/*
727 * 'cupsGetPPD2()' - Get the PPD file for a printer from the specified server.
728 *
729 * For classes, @code cupsGetPPD2@ returns the PPD file for the first printer
730 * in the class.
731 *
732 * The returned filename is stored in a static buffer and is overwritten with
733 * each call to @link cupsGetPPD@ or @code cupsGetPPD2@.  The caller "owns" the
734 * file that is created and must @code unlink@ the returned filename.
735 *
736 * @since CUPS 1.1.21/OS X 10.4@
737 */
738
739const char *				/* O - Filename for PPD file */
740cupsGetPPD2(http_t     *http,		/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
741            const char *name)		/* I - Destination name */
742{
743  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */
744  time_t	modtime = 0;		/* Modification time */
745
746
747  cg->ppd_filename[0] = '\0';
748
749  if (cupsGetPPD3(http, name, &modtime, cg->ppd_filename,
750                  sizeof(cg->ppd_filename)) == HTTP_STATUS_OK)
751    return (cg->ppd_filename);
752  else
753    return (NULL);
754}
755
756
757/*
758 * 'cupsGetPPD3()' - Get the PPD file for a printer on the specified
759 *                   server if it has changed.
760 *
761 * The "modtime" parameter contains the modification time of any
762 * locally-cached content and is updated with the time from the PPD file on
763 * the server.
764 *
765 * The "buffer" parameter contains the local PPD filename.  If it contains
766 * the empty string, a new temporary file is created, otherwise the existing
767 * file will be overwritten as needed.  The caller "owns" the file that is
768 * created and must @code unlink@ the returned filename.
769 *
770 * On success, @code HTTP_STATUS_OK@ is returned for a new PPD file and
771 * @code HTTP_STATUS_NOT_MODIFIED@ if the existing PPD file is up-to-date.  Any other
772 * status is an error.
773 *
774 * For classes, @code cupsGetPPD3@ returns the PPD file for the first printer
775 * in the class.
776 *
777 * @since CUPS 1.4/OS X 10.6@
778 */
779
780http_status_t				/* O  - HTTP status */
781cupsGetPPD3(http_t     *http,		/* I  - HTTP connection or @code CUPS_HTTP_DEFAULT@ */
782            const char *name,		/* I  - Destination name */
783	    time_t     *modtime,	/* IO - Modification time */
784	    char       *buffer,		/* I  - Filename buffer */
785	    size_t     bufsize)		/* I  - Size of filename buffer */
786{
787  int		http_port;		/* Port number */
788  char		http_hostname[HTTP_MAX_HOST];
789					/* Hostname associated with connection */
790  http_t	*http2;			/* Alternate HTTP connection */
791  int		fd;			/* PPD file */
792  char		localhost[HTTP_MAX_URI],/* Local hostname */
793		hostname[HTTP_MAX_URI],	/* Hostname */
794		resource[HTTP_MAX_URI];	/* Resource name */
795  int		port;			/* Port number */
796  http_status_t	status;			/* HTTP status from server */
797  char		tempfile[1024] = "";	/* Temporary filename */
798  _cups_globals_t *cg = _cupsGlobals();	/* Pointer to library globals */
799
800
801 /*
802  * Range check input...
803  */
804
805  DEBUG_printf(("cupsGetPPD3(http=%p, name=\"%s\", modtime=%p(%d), buffer=%p, "
806                "bufsize=%d)", http, name, modtime,
807		modtime ? (int)*modtime : 0, buffer, (int)bufsize));
808
809  if (!name)
810  {
811    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No printer name"), 1);
812    return (HTTP_STATUS_NOT_ACCEPTABLE);
813  }
814
815  if (!modtime)
816  {
817    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No modification time"), 1);
818    return (HTTP_STATUS_NOT_ACCEPTABLE);
819  }
820
821  if (!buffer || bufsize <= 1)
822  {
823    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Bad filename buffer"), 1);
824    return (HTTP_STATUS_NOT_ACCEPTABLE);
825  }
826
827#ifndef WIN32
828 /*
829  * See if the PPD file is available locally...
830  */
831
832  if (http)
833    httpGetHostname(http, hostname, sizeof(hostname));
834  else
835  {
836    strlcpy(hostname, cupsServer(), sizeof(hostname));
837    if (hostname[0] == '/')
838      strlcpy(hostname, "localhost", sizeof(hostname));
839  }
840
841  if (!_cups_strcasecmp(hostname, "localhost"))
842  {
843    char	ppdname[1024];		/* PPD filename */
844    struct stat	ppdinfo;		/* PPD file information */
845
846
847    snprintf(ppdname, sizeof(ppdname), "%s/ppd/%s.ppd", cg->cups_serverroot,
848             name);
849    if (!stat(ppdname, &ppdinfo))
850    {
851     /*
852      * OK, the file exists, use it!
853      */
854
855      if (buffer[0])
856      {
857        unlink(buffer);
858
859	if (symlink(ppdname, buffer) && errno != EEXIST)
860        {
861          _cupsSetError(IPP_STATUS_ERROR_INTERNAL, NULL, 0);
862
863	  return (HTTP_STATUS_SERVER_ERROR);
864	}
865      }
866      else
867      {
868        int		tries;		/* Number of tries */
869        const char	*tmpdir;	/* TMPDIR environment variable */
870	struct timeval	curtime;	/* Current time */
871
872       /*
873	* Previously we put root temporary files in the default CUPS temporary
874	* directory under /var/spool/cups.  However, since the scheduler cleans
875	* out temporary files there and runs independently of the user apps, we
876	* don't want to use it unless specifically told to by cupsd.
877	*/
878
879	if ((tmpdir = getenv("TMPDIR")) == NULL)
880#  ifdef __APPLE__
881	  tmpdir = "/private/tmp";	/* /tmp is a symlink to /private/tmp */
882#  else
883          tmpdir = "/tmp";
884#  endif /* __APPLE__ */
885
886       /*
887	* Make the temporary name using the specified directory...
888	*/
889
890	tries = 0;
891
892	do
893	{
894	 /*
895	  * Get the current time of day...
896	  */
897
898	  gettimeofday(&curtime, NULL);
899
900	 /*
901	  * Format a string using the hex time values...
902	  */
903
904	  snprintf(buffer, bufsize, "%s/%08lx%05lx", tmpdir,
905		   (unsigned long)curtime.tv_sec,
906		   (unsigned long)curtime.tv_usec);
907
908	 /*
909	  * Try to make a symlink...
910	  */
911
912	  if (!symlink(ppdname, buffer))
913	    break;
914
915	  tries ++;
916	}
917	while (tries < 1000);
918
919        if (tries >= 1000)
920	{
921          _cupsSetError(IPP_STATUS_ERROR_INTERNAL, NULL, 0);
922
923	  return (HTTP_STATUS_SERVER_ERROR);
924	}
925      }
926
927      if (*modtime >= ppdinfo.st_mtime)
928        return (HTTP_STATUS_NOT_MODIFIED);
929      else
930      {
931        *modtime = ppdinfo.st_mtime;
932	return (HTTP_STATUS_OK);
933      }
934    }
935  }
936#endif /* !WIN32 */
937
938 /*
939  * Try finding a printer URI for this printer...
940  */
941
942  if (!http)
943    if ((http = _cupsConnect()) == NULL)
944      return (HTTP_STATUS_SERVICE_UNAVAILABLE);
945
946  if (!cups_get_printer_uri(http, name, hostname, sizeof(hostname), &port,
947                            resource, sizeof(resource), 0))
948    return (HTTP_STATUS_NOT_FOUND);
949
950  DEBUG_printf(("2cupsGetPPD3: Printer hostname=\"%s\", port=%d", hostname,
951                port));
952
953  if (cupsServer()[0] == '/' && !_cups_strcasecmp(hostname, "localhost") && port == ippPort())
954  {
955   /*
956    * Redirect localhost to domain socket...
957    */
958
959    strlcpy(hostname, cupsServer(), sizeof(hostname));
960    port = 0;
961
962    DEBUG_printf(("2cupsGetPPD3: Redirecting to \"%s\".", hostname));
963  }
964
965 /*
966  * Remap local hostname to localhost...
967  */
968
969  httpGetHostname(NULL, localhost, sizeof(localhost));
970
971  DEBUG_printf(("2cupsGetPPD3: Local hostname=\"%s\"", localhost));
972
973  if (!_cups_strcasecmp(localhost, hostname))
974    strlcpy(hostname, "localhost", sizeof(hostname));
975
976 /*
977  * Get the hostname and port number we are connected to...
978  */
979
980  httpGetHostname(http, http_hostname, sizeof(http_hostname));
981  http_port = httpAddrPort(http->hostaddr);
982
983  DEBUG_printf(("2cupsGetPPD3: Connection hostname=\"%s\", port=%d",
984                http_hostname, http_port));
985
986 /*
987  * Reconnect to the correct server as needed...
988  */
989
990  if (!_cups_strcasecmp(http_hostname, hostname) && port == http_port)
991    http2 = http;
992  else if ((http2 = httpConnect2(hostname, port, NULL, AF_UNSPEC,
993				 cupsEncryption(), 1, 30000, NULL)) == NULL)
994  {
995    DEBUG_puts("1cupsGetPPD3: Unable to connect to server");
996
997    return (HTTP_STATUS_SERVICE_UNAVAILABLE);
998  }
999
1000 /*
1001  * Get a temp file...
1002  */
1003
1004  if (buffer[0])
1005    fd = open(buffer, O_CREAT | O_TRUNC | O_WRONLY, 0600);
1006  else
1007    fd = cupsTempFd(tempfile, sizeof(tempfile));
1008
1009  if (fd < 0)
1010  {
1011   /*
1012    * Can't open file; close the server connection and return NULL...
1013    */
1014
1015    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, NULL, 0);
1016
1017    if (http2 != http)
1018      httpClose(http2);
1019
1020    return (HTTP_STATUS_SERVER_ERROR);
1021  }
1022
1023 /*
1024  * And send a request to the HTTP server...
1025  */
1026
1027  strlcat(resource, ".ppd", sizeof(resource));
1028
1029  if (*modtime > 0)
1030    httpSetField(http2, HTTP_FIELD_IF_MODIFIED_SINCE,
1031                 httpGetDateString(*modtime));
1032
1033  status = cupsGetFd(http2, resource, fd);
1034
1035  close(fd);
1036
1037 /*
1038  * See if we actually got the file or an error...
1039  */
1040
1041  if (status == HTTP_STATUS_OK)
1042  {
1043    *modtime = httpGetDateTime(httpGetField(http2, HTTP_FIELD_DATE));
1044
1045    if (tempfile[0])
1046      strlcpy(buffer, tempfile, bufsize);
1047  }
1048  else if (status != HTTP_STATUS_NOT_MODIFIED)
1049  {
1050    _cupsSetHTTPError(status);
1051
1052    if (buffer[0])
1053      unlink(buffer);
1054    else if (tempfile[0])
1055      unlink(tempfile);
1056  }
1057  else if (tempfile[0])
1058    unlink(tempfile);
1059
1060  if (http2 != http)
1061    httpClose(http2);
1062
1063 /*
1064  * Return the PPD file...
1065  */
1066
1067  DEBUG_printf(("1cupsGetPPD3: Returning status %d", status));
1068
1069  return (status);
1070}
1071
1072
1073/*
1074 * 'cupsGetPrinters()' - Get a list of printers from the default server.
1075 *
1076 * This function is deprecated and no longer returns a list of printers - use
1077 * @link cupsGetDests@ instead.
1078 *
1079 * @deprecated@
1080 */
1081
1082int					/* O - Number of printers */
1083cupsGetPrinters(char ***printers)	/* O - Printers */
1084{
1085  if (printers)
1086    *printers = NULL;
1087
1088  return (0);
1089}
1090
1091
1092/*
1093 * 'cupsGetServerPPD()' - Get an available PPD file from the server.
1094 *
1095 * This function returns the named PPD file from the server.  The
1096 * list of available PPDs is provided by the IPP @code CUPS_GET_PPDS@
1097 * operation.
1098 *
1099 * You must remove (unlink) the PPD file when you are finished with
1100 * it. The PPD filename is stored in a static location that will be
1101 * overwritten on the next call to @link cupsGetPPD@, @link cupsGetPPD2@,
1102 * or @link cupsGetServerPPD@.
1103 *
1104 * @since CUPS 1.3/OS X 10.5@
1105 */
1106
1107char *					/* O - Name of PPD file or @code NULL@ on error */
1108cupsGetServerPPD(http_t     *http,	/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
1109                 const char *name)	/* I - Name of PPD file ("ppd-name") */
1110{
1111  int			fd;		/* PPD file descriptor */
1112  ipp_t			*request;	/* IPP request */
1113  _cups_globals_t	*cg = _cupsGlobals();
1114					/* Pointer to library globals */
1115
1116
1117 /*
1118  * Range check input...
1119  */
1120
1121  if (!name)
1122  {
1123    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No PPD name"), 1);
1124
1125    return (NULL);
1126  }
1127
1128  if (!http)
1129    if ((http = _cupsConnect()) == NULL)
1130      return (NULL);
1131
1132 /*
1133  * Get a temp file...
1134  */
1135
1136  if ((fd = cupsTempFd(cg->ppd_filename, sizeof(cg->ppd_filename))) < 0)
1137  {
1138   /*
1139    * Can't open file; close the server connection and return NULL...
1140    */
1141
1142    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, NULL, 0);
1143
1144    return (NULL);
1145  }
1146
1147 /*
1148  * Get the PPD file...
1149  */
1150
1151  request = ippNewRequest(IPP_OP_CUPS_GET_PPD);
1152  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "ppd-name", NULL,
1153               name);
1154
1155  ippDelete(cupsDoIORequest(http, request, "/", -1, fd));
1156
1157  close(fd);
1158
1159  if (cupsLastError() != IPP_STATUS_OK)
1160  {
1161    unlink(cg->ppd_filename);
1162    return (NULL);
1163  }
1164  else
1165    return (cg->ppd_filename);
1166}
1167
1168
1169/*
1170 * 'cupsPrintFile()' - Print a file to a printer or class on the default server.
1171 */
1172
1173int					/* O - Job ID or 0 on error */
1174cupsPrintFile(const char    *name,	/* I - Destination name */
1175              const char    *filename,	/* I - File to print */
1176	      const char    *title,	/* I - Title of job */
1177              int           num_options,/* I - Number of options */
1178	      cups_option_t *options)	/* I - Options */
1179{
1180  DEBUG_printf(("cupsPrintFile(name=\"%s\", filename=\"%s\", "
1181                "title=\"%s\", num_options=%d, options=%p)",
1182                name, filename, title, num_options, options));
1183
1184  return (cupsPrintFiles2(CUPS_HTTP_DEFAULT, name, 1, &filename, title,
1185                          num_options, options));
1186}
1187
1188
1189/*
1190 * 'cupsPrintFile2()' - Print a file to a printer or class on the specified
1191 *                      server.
1192 *
1193 * @since CUPS 1.1.21/OS X 10.4@
1194 */
1195
1196int					/* O - Job ID or 0 on error */
1197cupsPrintFile2(
1198    http_t        *http,		/* I - Connection to server */
1199    const char    *name,		/* I - Destination name */
1200    const char    *filename,		/* I - File to print */
1201    const char    *title,		/* I - Title of job */
1202    int           num_options,		/* I - Number of options */
1203    cups_option_t *options)		/* I - Options */
1204{
1205  DEBUG_printf(("cupsPrintFile2(http=%p, name=\"%s\", filename=\"%s\", "
1206                "title=\"%s\", num_options=%d, options=%p)",
1207                http, name, filename, title, num_options, options));
1208
1209  return (cupsPrintFiles2(http, name, 1, &filename, title, num_options,
1210                          options));
1211}
1212
1213
1214/*
1215 * 'cupsPrintFiles()' - Print one or more files to a printer or class on the
1216 *                      default server.
1217 */
1218
1219int					/* O - Job ID or 0 on error */
1220cupsPrintFiles(
1221    const char    *name,		/* I - Destination name */
1222    int           num_files,		/* I - Number of files */
1223    const char    **files,		/* I - File(s) to print */
1224    const char    *title,		/* I - Title of job */
1225    int           num_options,		/* I - Number of options */
1226    cups_option_t *options)		/* I - Options */
1227{
1228  DEBUG_printf(("cupsPrintFiles(name=\"%s\", num_files=%d, "
1229                "files=%p, title=\"%s\", num_options=%d, options=%p)",
1230                name, num_files, files, title, num_options, options));
1231
1232 /*
1233  * Print the file(s)...
1234  */
1235
1236  return (cupsPrintFiles2(CUPS_HTTP_DEFAULT, name, num_files, files, title,
1237                          num_options, options));
1238}
1239
1240
1241/*
1242 * 'cupsPrintFiles2()' - Print one or more files to a printer or class on the
1243 *                       specified server.
1244 *
1245 * @since CUPS 1.1.21/OS X 10.4@
1246 */
1247
1248int					/* O - Job ID or 0 on error */
1249cupsPrintFiles2(
1250    http_t        *http,		/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
1251    const char    *name,		/* I - Destination name */
1252    int           num_files,		/* I - Number of files */
1253    const char    **files,		/* I - File(s) to print */
1254    const char    *title,		/* I - Title of job */
1255    int           num_options,		/* I - Number of options */
1256    cups_option_t *options)		/* I - Options */
1257{
1258  int		i;			/* Looping var */
1259  int		job_id;			/* New job ID */
1260  const char	*docname;		/* Basename of current filename */
1261  const char	*format;		/* Document format */
1262  cups_file_t	*fp;			/* Current file */
1263  char		buffer[8192];		/* Copy buffer */
1264  ssize_t	bytes;			/* Bytes in buffer */
1265  http_status_t	status;			/* Status of write */
1266  _cups_globals_t *cg = _cupsGlobals();	/* Global data */
1267  ipp_status_t	cancel_status;		/* Status code to preserve */
1268  char		*cancel_message;	/* Error message to preserve */
1269
1270
1271  DEBUG_printf(("cupsPrintFiles2(http=%p, name=\"%s\", num_files=%d, "
1272                "files=%p, title=\"%s\", num_options=%d, options=%p)",
1273                http, name, num_files, files, title, num_options, options));
1274
1275 /*
1276  * Range check input...
1277  */
1278
1279  if (!name || num_files < 1 || !files)
1280  {
1281    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
1282
1283    return (0);
1284  }
1285
1286 /*
1287  * Create the print job...
1288  */
1289
1290  if ((job_id = cupsCreateJob(http, name, title, num_options, options)) == 0)
1291    return (0);
1292
1293 /*
1294  * Send each of the files...
1295  */
1296
1297  if (cupsGetOption("raw", num_options, options))
1298    format = CUPS_FORMAT_RAW;
1299  else if ((format = cupsGetOption("document-format", num_options,
1300				   options)) == NULL)
1301    format = CUPS_FORMAT_AUTO;
1302
1303  for (i = 0; i < num_files; i ++)
1304  {
1305   /*
1306    * Start the next file...
1307    */
1308
1309    if ((docname = strrchr(files[i], '/')) != NULL)
1310      docname ++;
1311    else
1312      docname = files[i];
1313
1314    if ((fp = cupsFileOpen(files[i], "rb")) == NULL)
1315    {
1316     /*
1317      * Unable to open print file, cancel the job and return...
1318      */
1319
1320      _cupsSetError(IPP_STATUS_ERROR_DOCUMENT_ACCESS, NULL, 0);
1321      goto cancel_job;
1322    }
1323
1324    status = cupsStartDocument(http, name, job_id, docname, format,
1325			       i == (num_files - 1));
1326
1327    while (status == HTTP_STATUS_CONTINUE &&
1328	   (bytes = cupsFileRead(fp, buffer, sizeof(buffer))) > 0)
1329      status = cupsWriteRequestData(http, buffer, (size_t)bytes);
1330
1331    cupsFileClose(fp);
1332
1333    if (status != HTTP_STATUS_CONTINUE || cupsFinishDocument(http, name) != IPP_STATUS_OK)
1334    {
1335     /*
1336      * Unable to queue, cancel the job and return...
1337      */
1338
1339      goto cancel_job;
1340    }
1341  }
1342
1343  return (job_id);
1344
1345 /*
1346  * If we get here, something happened while sending the print job so we need
1347  * to cancel the job without setting the last error (since we need to preserve
1348  * the current error...
1349  */
1350
1351  cancel_job:
1352
1353  cancel_status  = cg->last_error;
1354  cancel_message = cg->last_status_message ?
1355                       _cupsStrRetain(cg->last_status_message) : NULL;
1356
1357  cupsCancelJob2(http, name, job_id, 0);
1358
1359  cg->last_error          = cancel_status;
1360  cg->last_status_message = cancel_message;
1361
1362  return (0);
1363}
1364
1365
1366/*
1367 * 'cupsStartDocument()' - Add a document to a job created with cupsCreateJob().
1368 *
1369 * Use @link cupsWriteRequestData@ to write data for the document and
1370 * @link cupsFinishDocument@ to finish the document and get the submission status.
1371 *
1372 * The MIME type constants @code CUPS_FORMAT_AUTO@, @code CUPS_FORMAT_PDF@,
1373 * @code CUPS_FORMAT_POSTSCRIPT@, @code CUPS_FORMAT_RAW@, and
1374 * @code CUPS_FORMAT_TEXT@ are provided for the "format" argument, although
1375 * any supported MIME type string can be supplied.
1376 *
1377 * @since CUPS 1.4/OS X 10.6@
1378 */
1379
1380http_status_t				/* O - HTTP status of request */
1381cupsStartDocument(
1382    http_t     *http,			/* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
1383    const char *name,			/* I - Destination name */
1384    int        job_id,			/* I - Job ID from @link cupsCreateJob@ */
1385    const char *docname,		/* I - Name of document */
1386    const char *format,			/* I - MIME type or @code CUPS_FORMAT_foo@ */
1387    int        last_document)		/* I - 1 for last document in job, 0 otherwise */
1388{
1389  char		resource[1024],		/* Resource for destinatio */
1390		printer_uri[1024];	/* Printer URI */
1391  ipp_t		*request;		/* Send-Document request */
1392  http_status_t	status;			/* HTTP status */
1393
1394
1395 /*
1396  * Create a Send-Document request...
1397  */
1398
1399  if ((request = ippNewRequest(IPP_OP_SEND_DOCUMENT)) == NULL)
1400  {
1401    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(ENOMEM), 0);
1402    return (HTTP_STATUS_ERROR);
1403  }
1404
1405  httpAssembleURIf(HTTP_URI_CODING_ALL, printer_uri, sizeof(printer_uri), "ipp",
1406                   NULL, "localhost", ippPort(), "/printers/%s", name);
1407  snprintf(resource, sizeof(resource), "/printers/%s", name);
1408
1409  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
1410               NULL, printer_uri);
1411  ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", job_id);
1412  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
1413               NULL, cupsUser());
1414  if (docname)
1415    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "document-name",
1416                 NULL, docname);
1417  if (format)
1418    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE,
1419                 "document-format", NULL, format);
1420  ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", (char)last_document);
1421
1422 /*
1423  * Send and delete the request, then return the status...
1424  */
1425
1426  status = cupsSendRequest(http, request, resource, CUPS_LENGTH_VARIABLE);
1427
1428  ippDelete(request);
1429
1430  return (status);
1431}
1432
1433
1434/*
1435 * 'cups_get_printer_uri()' - Get the printer-uri-supported attribute for the
1436 *                            first printer in a class.
1437 */
1438
1439static int				/* O - 1 on success, 0 on failure */
1440cups_get_printer_uri(
1441    http_t     *http,			/* I - Connection to server */
1442    const char *name,			/* I - Name of printer or class */
1443    char       *host,			/* I - Hostname buffer */
1444    int        hostsize,		/* I - Size of hostname buffer */
1445    int        *port,			/* O - Port number */
1446    char       *resource,		/* I - Resource buffer */
1447    int        resourcesize,		/* I - Size of resource buffer */
1448    int        depth)			/* I - Depth of query */
1449{
1450  int		i;			/* Looping var */
1451  int		http_port;		/* Port number */
1452  http_t	*http2;			/* Alternate HTTP connection */
1453  ipp_t		*request,		/* IPP request */
1454		*response;		/* IPP response */
1455  ipp_attribute_t *attr;		/* Current attribute */
1456  char		uri[HTTP_MAX_URI],	/* printer-uri attribute */
1457		scheme[HTTP_MAX_URI],	/* Scheme name */
1458		username[HTTP_MAX_URI],	/* Username:password */
1459		classname[255],		/* Temporary class name */
1460		http_hostname[HTTP_MAX_HOST];
1461					/* Hostname associated with connection */
1462  static const char * const requested_attrs[] =
1463		{			/* Requested attributes */
1464		  "device-uri",
1465		  "member-uris",
1466		  "printer-uri-supported",
1467		  "printer-type"
1468		};
1469
1470
1471  DEBUG_printf(("4cups_get_printer_uri(http=%p, name=\"%s\", host=%p, hostsize=%d, resource=%p, resourcesize=%d, depth=%d)", http, name, host, hostsize, resource, resourcesize, depth));
1472
1473 /*
1474  * Setup the printer URI...
1475  */
1476
1477  if (httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, "localhost", 0, "/printers/%s", name) < HTTP_URI_STATUS_OK)
1478  {
1479    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unable to create printer-uri"), 1);
1480
1481    *host     = '\0';
1482    *resource = '\0';
1483
1484    return (0);
1485  }
1486
1487  DEBUG_printf(("5cups_get_printer_uri: printer-uri=\"%s\"", uri));
1488
1489 /*
1490  * Get the hostname and port number we are connected to...
1491  */
1492
1493  httpGetHostname(http, http_hostname, sizeof(http_hostname));
1494  http_port = httpAddrPort(http->hostaddr);
1495
1496  DEBUG_printf(("5cups_get_printer_uri: http_hostname=\"%s\"", http_hostname));
1497
1498 /*
1499  * Build an IPP_GET_PRINTER_ATTRIBUTES request, which requires the following
1500  * attributes:
1501  *
1502  *    attributes-charset
1503  *    attributes-natural-language
1504  *    printer-uri
1505  *    requested-attributes
1506  */
1507
1508  request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
1509
1510  ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
1511
1512  ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requested-attributes", sizeof(requested_attrs) / sizeof(requested_attrs[0]), NULL, requested_attrs);
1513
1514 /*
1515  * Do the request and get back a response...
1516  */
1517
1518  snprintf(resource, (size_t)resourcesize, "/printers/%s", name);
1519
1520  if ((response = cupsDoRequest(http, request, resource)) != NULL)
1521  {
1522    const char *device_uri = NULL;	/* device-uri value */
1523
1524    if ((attr = ippFindAttribute(response, "device-uri", IPP_TAG_URI)) != NULL)
1525    {
1526      device_uri = attr->values[0].string.text;
1527      DEBUG_printf(("5cups_get_printer_uri: device-uri=\"%s\"", device_uri));
1528    }
1529
1530    if (device_uri &&
1531        (!strncmp(device_uri, "ipp://", 6) ||
1532         !strncmp(device_uri, "ipps://", 7) ||
1533         ((strstr(device_uri, "._ipp.") != NULL ||
1534           strstr(device_uri, "._ipps.") != NULL) &&
1535          !strcmp(device_uri + strlen(device_uri) - 5, "/cups"))))
1536    {
1537     /*
1538      * Statically-configured shared printer.
1539      */
1540
1541      httpSeparateURI(HTTP_URI_CODING_ALL, _httpResolveURI(device_uri, uri, sizeof(uri), _HTTP_RESOLVE_DEFAULT, NULL, NULL), scheme, sizeof(scheme), username, sizeof(username), host, hostsize, port, resource, resourcesize);
1542      ippDelete(response);
1543
1544      DEBUG_printf(("5cups_get_printer_uri: Resolved to host=\"%s\", port=%d, resource=\"%s\"", host, *port, resource));
1545      return (1);
1546    }
1547    else if ((attr = ippFindAttribute(response, "member-uris", IPP_TAG_URI)) != NULL)
1548    {
1549     /*
1550      * Get the first actual printer name in the class...
1551      */
1552
1553      DEBUG_printf(("5cups_get_printer_uri: Got member-uris with %d values.", ippGetCount(attr)));
1554
1555      for (i = 0; i < attr->num_values; i ++)
1556      {
1557        DEBUG_printf(("5cups_get_printer_uri: member-uris[%d]=\"%s\"", i, ippGetString(attr, i, NULL)));
1558
1559	httpSeparateURI(HTTP_URI_CODING_ALL, attr->values[i].string.text, scheme, sizeof(scheme), username, sizeof(username), host, hostsize, port, resource, resourcesize);
1560	if (!strncmp(resource, "/printers/", 10))
1561	{
1562	 /*
1563	  * Found a printer!
1564	  */
1565
1566          ippDelete(response);
1567
1568	  DEBUG_printf(("5cups_get_printer_uri: Found printer member with host=\"%s\", port=%d, resource=\"%s\"", host, *port, resource));
1569	  return (1);
1570	}
1571      }
1572
1573     /*
1574      * No printers in this class - try recursively looking for a printer,
1575      * but not more than 3 levels deep...
1576      */
1577
1578      if (depth < 3)
1579      {
1580	for (i = 0; i < attr->num_values; i ++)
1581	{
1582	  httpSeparateURI(HTTP_URI_CODING_ALL, attr->values[i].string.text,
1583	                  scheme, sizeof(scheme), username, sizeof(username),
1584			  host, hostsize, port, resource, resourcesize);
1585	  if (!strncmp(resource, "/classes/", 9))
1586	  {
1587	   /*
1588	    * Found a class!  Connect to the right server...
1589	    */
1590
1591	    if (!_cups_strcasecmp(http_hostname, host) && *port == http_port)
1592	      http2 = http;
1593	    else if ((http2 = httpConnect2(host, *port, NULL, AF_UNSPEC, cupsEncryption(), 1, 30000, NULL)) == NULL)
1594	    {
1595	      DEBUG_puts("8cups_get_printer_uri: Unable to connect to server");
1596
1597	      continue;
1598	    }
1599
1600           /*
1601	    * Look up printers on that server...
1602	    */
1603
1604            strlcpy(classname, resource + 9, sizeof(classname));
1605
1606            cups_get_printer_uri(http2, classname, host, hostsize, port,
1607	                         resource, resourcesize, depth + 1);
1608
1609           /*
1610	    * Close the connection as needed...
1611	    */
1612
1613	    if (http2 != http)
1614	      httpClose(http2);
1615
1616            if (*host)
1617	      return (1);
1618	  }
1619	}
1620      }
1621    }
1622    else if ((attr = ippFindAttribute(response, "printer-uri-supported", IPP_TAG_URI)) != NULL)
1623    {
1624      httpSeparateURI(HTTP_URI_CODING_ALL, _httpResolveURI(attr->values[0].string.text, uri, sizeof(uri), _HTTP_RESOLVE_DEFAULT, NULL, NULL), scheme, sizeof(scheme), username, sizeof(username), host, hostsize, port, resource, resourcesize);
1625      ippDelete(response);
1626
1627      DEBUG_printf(("5cups_get_printer_uri: Resolved to host=\"%s\", port=%d, resource=\"%s\"", host, *port, resource));
1628
1629      if (!strncmp(resource, "/classes/", 9))
1630      {
1631        _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No printer-uri found for class"), 1);
1632
1633	*host     = '\0';
1634	*resource = '\0';
1635
1636        DEBUG_puts("5cups_get_printer_uri: Not returning class.");
1637	return (0);
1638      }
1639
1640      return (1);
1641    }
1642
1643    ippDelete(response);
1644  }
1645
1646  if (cupsLastError() != IPP_STATUS_ERROR_NOT_FOUND)
1647    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("No printer-uri found"), 1);
1648
1649  *host     = '\0';
1650  *resource = '\0';
1651
1652  DEBUG_puts("5cups_get_printer_uri: Printer URI not found.");
1653  return (0);
1654}
1655
1656
1657/*
1658 * End of "$Id: util.c 12078 2014-07-31 11:45:57Z msweet $".
1659 */
1660