Imported Upstream version 2.2.2
[platform/upstream/cups.git] / cups / dest-options.c
1 /*
2  * Destination option/media support for CUPS.
3  *
4  * Copyright 2012-2016 by Apple Inc.
5  *
6  * These coded instructions, statements, and computer programs are the
7  * property of Apple Inc. and are protected by Federal copyright
8  * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
9  * which should have been included with this file.  If this file is
10  * missing or damaged, see the license at "http://www.cups.org/".
11  *
12  * This file is subject to the Apple OS-Developed Software exception.
13  */
14
15 /*
16  * Include necessary headers...
17  */
18
19 #include "cups-private.h"
20
21
22 /*
23  * Local constants...
24  */
25
26 #define _CUPS_MEDIA_READY_TTL   30      /* Life of xxx-ready values */
27
28
29 /*
30  * Local functions...
31  */
32
33 static void             cups_add_dconstres(cups_array_t *a, ipp_t *collection);
34 static int              cups_compare_dconstres(_cups_dconstres_t *a,
35                                                _cups_dconstres_t *b);
36 static int              cups_compare_media_db(_cups_media_db_t *a,
37                                               _cups_media_db_t *b);
38 static _cups_media_db_t *cups_copy_media_db(_cups_media_db_t *mdb);
39 static void             cups_create_cached(http_t *http, cups_dinfo_t *dinfo,
40                                            unsigned flags);
41 static void             cups_create_constraints(cups_dinfo_t *dinfo);
42 static void             cups_create_defaults(cups_dinfo_t *dinfo);
43 static void             cups_create_media_db(cups_dinfo_t *dinfo,
44                                              unsigned flags);
45 static void             cups_free_media_db(_cups_media_db_t *mdb);
46 static int              cups_get_media_db(http_t *http, cups_dinfo_t *dinfo,
47                                           pwg_media_t *pwg, unsigned flags,
48                                           cups_size_t *size);
49 static int              cups_is_close_media_db(_cups_media_db_t *a,
50                                                _cups_media_db_t *b);
51 static cups_array_t     *cups_test_constraints(cups_dinfo_t *dinfo,
52                                                const char *new_option,
53                                                const char *new_value,
54                                                int num_options,
55                                                cups_option_t *options,
56                                                int *num_conflicts,
57                                                cups_option_t **conflicts);
58 static void             cups_update_ready(http_t *http, cups_dinfo_t *dinfo);
59
60
61 /*
62  * 'cupsCheckDestSupported()' - Check that the option and value are supported
63  *                              by the destination.
64  *
65  * Returns 1 if supported, 0 otherwise.
66  *
67  * @since CUPS 1.6/macOS 10.8@
68  */
69
70 int                                     /* O - 1 if supported, 0 otherwise */
71 cupsCheckDestSupported(
72     http_t       *http,                 /* I - Connection to destination */
73     cups_dest_t  *dest,                 /* I - Destination */
74     cups_dinfo_t *dinfo,                /* I - Destination information */
75     const char   *option,               /* I - Option */
76     const char   *value)                /* I - Value */
77 {
78   int                   i;              /* Looping var */
79   char                  temp[1024];     /* Temporary string */
80   int                   int_value;      /* Integer value */
81   int                   xres_value,     /* Horizontal resolution */
82                         yres_value;     /* Vertical resolution */
83   ipp_res_t             units_value;    /* Resolution units */
84   ipp_attribute_t       *attr;          /* Attribute */
85   _ipp_value_t          *attrval;       /* Current attribute value */
86
87
88  /*
89   * Range check input...
90   */
91
92   if (!http || !dest || !dinfo || !option || !value)
93     return (0);
94
95  /*
96   * Lookup the attribute...
97   */
98
99   if (strstr(option, "-supported"))
100     attr = ippFindAttribute(dinfo->attrs, option, IPP_TAG_ZERO);
101   else
102   {
103     snprintf(temp, sizeof(temp), "%s-supported", option);
104     attr = ippFindAttribute(dinfo->attrs, temp, IPP_TAG_ZERO);
105   }
106
107   if (!attr)
108     return (0);
109
110  /*
111   * Compare values...
112   */
113
114   if (!strcmp(option, "media") && !strncmp(value, "custom_", 7))
115   {
116    /*
117     * Check range of custom media sizes...
118     */
119
120     pwg_media_t *pwg;           /* Current PWG media size info */
121     int                 min_width,      /* Minimum width */
122                         min_length,     /* Minimum length */
123                         max_width,      /* Maximum width */
124                         max_length;     /* Maximum length */
125
126    /*
127     * Get the minimum and maximum size...
128     */
129
130     min_width = min_length = INT_MAX;
131     max_width = max_length = 0;
132
133     for (i = attr->num_values, attrval = attr->values;
134          i > 0;
135          i --, attrval ++)
136     {
137       if (!strncmp(attrval->string.text, "custom_min_", 11) &&
138           (pwg = pwgMediaForPWG(attrval->string.text)) != NULL)
139       {
140         min_width  = pwg->width;
141         min_length = pwg->length;
142       }
143       else if (!strncmp(attrval->string.text, "custom_max_", 11) &&
144                (pwg = pwgMediaForPWG(attrval->string.text)) != NULL)
145       {
146         max_width  = pwg->width;
147         max_length = pwg->length;
148       }
149     }
150
151    /*
152     * Check the range...
153     */
154
155     if (min_width < INT_MAX && max_width > 0 &&
156         (pwg = pwgMediaForPWG(value)) != NULL &&
157         pwg->width >= min_width && pwg->width <= max_width &&
158         pwg->length >= min_length && pwg->length <= max_length)
159       return (1);
160   }
161   else
162   {
163    /*
164     * Check literal values...
165     */
166
167     switch (attr->value_tag)
168     {
169       case IPP_TAG_INTEGER :
170       case IPP_TAG_ENUM :
171           int_value = atoi(value);
172
173           for (i = 0; i < attr->num_values; i ++)
174             if (attr->values[i].integer == int_value)
175               return (1);
176           break;
177
178       case IPP_TAG_BOOLEAN :
179           return (attr->values[0].boolean);
180
181       case IPP_TAG_RANGE :
182           int_value = atoi(value);
183
184           for (i = 0; i < attr->num_values; i ++)
185             if (int_value >= attr->values[i].range.lower &&
186                 int_value <= attr->values[i].range.upper)
187               return (1);
188           break;
189
190       case IPP_TAG_RESOLUTION :
191           if (sscanf(value, "%dx%d%15s", &xres_value, &yres_value, temp) != 3)
192           {
193             if (sscanf(value, "%d%15s", &xres_value, temp) != 2)
194               return (0);
195
196             yres_value = xres_value;
197           }
198
199           if (!strcmp(temp, "dpi"))
200             units_value = IPP_RES_PER_INCH;
201           else if (!strcmp(temp, "dpc") || !strcmp(temp, "dpcm"))
202             units_value = IPP_RES_PER_CM;
203           else
204             return (0);
205
206           for (i = attr->num_values, attrval = attr->values;
207                i > 0;
208                i --, attrval ++)
209           {
210             if (attrval->resolution.xres == xres_value &&
211                 attrval->resolution.yres == yres_value &&
212                 attrval->resolution.units == units_value)
213               return (1);
214           }
215           break;
216
217       case IPP_TAG_TEXT :
218       case IPP_TAG_NAME :
219       case IPP_TAG_KEYWORD :
220       case IPP_TAG_CHARSET :
221       case IPP_TAG_URI :
222       case IPP_TAG_URISCHEME :
223       case IPP_TAG_MIMETYPE :
224       case IPP_TAG_LANGUAGE :
225       case IPP_TAG_TEXTLANG :
226       case IPP_TAG_NAMELANG :
227           for (i = 0; i < attr->num_values; i ++)
228             if (!strcmp(attr->values[i].string.text, value))
229               return (1);
230           break;
231
232       default :
233           break;
234     }
235   }
236
237  /*
238   * If we get there the option+value is not supported...
239   */
240
241   return (0);
242 }
243
244
245 /*
246  * 'cupsCopyDestConflicts()' - Get conflicts and resolutions for a new
247  *                             option/value pair.
248  *
249  * "num_options" and "options" represent the currently selected options by the
250  * user.  "new_option" and "new_value" are the setting the user has just
251  * changed.
252  *
253  * Returns 1 if there is a conflict, 0 if there are no conflicts, and -1 if
254  * there was an unrecoverable error such as a resolver loop.
255  *
256  * If "num_conflicts" and "conflicts" are not @code NULL@, they are set to
257  * contain the list of conflicting option/value pairs.  Similarly, if
258  * "num_resolved" and "resolved" are not @code NULL@ they will be set to the
259  * list of changes needed to resolve the conflict.
260  *
261  * If cupsCopyDestConflicts returns 1 but "num_resolved" and "resolved" are set
262  * to 0 and @code NULL@, respectively, then the conflict cannot be resolved.
263  *
264  * @since CUPS 1.6/macOS 10.8@
265  */
266
267 int                                     /* O - 1 if there is a conflict, 0 if none, -1 on error */
268 cupsCopyDestConflicts(
269     http_t        *http,                /* I - Connection to destination */
270     cups_dest_t   *dest,                /* I - Destination */
271     cups_dinfo_t  *dinfo,               /* I - Destination information */
272     int           num_options,          /* I - Number of current options */
273     cups_option_t *options,             /* I - Current options */
274     const char    *new_option,          /* I - New option */
275     const char    *new_value,           /* I - New value */
276     int           *num_conflicts,       /* O - Number of conflicting options */
277     cups_option_t **conflicts,          /* O - Conflicting options */
278     int           *num_resolved,        /* O - Number of options to resolve */
279     cups_option_t **resolved)           /* O - Resolved options */
280 {
281   int           i,                      /* Looping var */
282                 have_conflicts = 0,     /* Do we have conflicts? */
283                 changed,                /* Did we change something? */
284                 tries,                  /* Number of tries for resolution */
285                 num_myconf = 0,         /* My number of conflicting options */
286                 num_myres = 0;          /* My number of resolved options */
287   cups_option_t *myconf = NULL,         /* My conflicting options */
288                 *myres = NULL,          /* My resolved options */
289                 *myoption,              /* My current option */
290                 *option;                /* Current option */
291   cups_array_t  *active = NULL,         /* Active conflicts */
292                 *pass = NULL,           /* Resolvers for this pass */
293                 *resolvers = NULL,      /* Resolvers we have used */
294                 *test;                  /* Test array for conflicts */
295   _cups_dconstres_t *c,                 /* Current constraint */
296                 *r;                     /* Current resolver */
297   ipp_attribute_t *attr;                /* Current attribute */
298   char          value[2048];            /* Current attribute value as string */
299   const char    *myvalue;               /* Current value of an option */
300
301
302  /*
303   * Clear returned values...
304   */
305
306   if (num_conflicts)
307     *num_conflicts = 0;
308
309   if (conflicts)
310     *conflicts = NULL;
311
312   if (num_resolved)
313     *num_resolved = 0;
314
315   if (resolved)
316     *resolved = NULL;
317
318  /*
319   * Range check input...
320   */
321
322   if (!http || !dest || !dinfo ||
323       (num_conflicts != NULL) != (conflicts != NULL) ||
324       (num_resolved != NULL) != (resolved != NULL))
325     return (0);
326
327  /*
328   * Load constraints as needed...
329   */
330
331   if (!dinfo->constraints)
332     cups_create_constraints(dinfo);
333
334   if (cupsArrayCount(dinfo->constraints) == 0)
335     return (0);
336
337   if (!dinfo->num_defaults)
338     cups_create_defaults(dinfo);
339
340  /*
341   * If we are resolving, create a shadow array...
342   */
343
344   if (num_resolved)
345   {
346     for (i = num_options, option = options; i > 0; i --, option ++)
347       num_myres = cupsAddOption(option->name, option->value, num_myres, &myres);
348
349     if (new_option && new_value)
350       num_myres = cupsAddOption(new_option, new_value, num_myres, &myres);
351   }
352   else
353   {
354     num_myres = num_options;
355     myres     = options;
356   }
357
358  /*
359   * Check for any conflicts...
360   */
361
362   if (num_resolved)
363     pass = cupsArrayNew((cups_array_func_t)cups_compare_dconstres, NULL);
364
365   for (tries = 0; tries < 100; tries ++)
366   {
367    /*
368     * Check for any conflicts...
369     */
370
371     if (num_conflicts || num_resolved)
372     {
373       cupsFreeOptions(num_myconf, myconf);
374
375       num_myconf = 0;
376       myconf     = NULL;
377       active     = cups_test_constraints(dinfo, new_option, new_value,
378                                          num_myres, myres, &num_myconf,
379                                          &myconf);
380     }
381     else
382       active = cups_test_constraints(dinfo, new_option, new_value, num_myres,
383                                      myres, NULL, NULL);
384
385     have_conflicts = (active != NULL);
386
387     if (!active || !num_resolved)
388       break;                            /* All done */
389
390    /*
391     * Scan the constraints that were triggered to apply resolvers...
392     */
393
394     if (!resolvers)
395       resolvers = cupsArrayNew((cups_array_func_t)cups_compare_dconstres, NULL);
396
397     for (c = (_cups_dconstres_t *)cupsArrayFirst(active), changed = 0;
398          c;
399          c = (_cups_dconstres_t *)cupsArrayNext(active))
400     {
401       if (cupsArrayFind(pass, c))
402         continue;                       /* Already applied this resolver... */
403
404       if (cupsArrayFind(resolvers, c))
405       {
406         DEBUG_printf(("1cupsCopyDestConflicts: Resolver loop with %s.",
407                       c->name));
408         have_conflicts = -1;
409         goto cleanup;
410       }
411
412       if ((r = cupsArrayFind(dinfo->resolvers, c)) == NULL)
413       {
414         DEBUG_printf(("1cupsCopyDestConflicts: Resolver %s not found.",
415                       c->name));
416         have_conflicts = -1;
417         goto cleanup;
418       }
419
420      /*
421       * Add the options from the resolver...
422       */
423
424       cupsArrayAdd(pass, r);
425       cupsArrayAdd(resolvers, r);
426
427       for (attr = ippFirstAttribute(r->collection);
428            attr;
429            attr = ippNextAttribute(r->collection))
430       {
431         if (new_option && !strcmp(attr->name, new_option))
432           continue;                     /* Ignore this if we just changed it */
433
434         if (ippAttributeString(attr, value, sizeof(value)) >= sizeof(value))
435           continue;                     /* Ignore if the value is too long */
436
437         if ((test = cups_test_constraints(dinfo, attr->name, value, num_myres,
438                                           myres, NULL, NULL)) == NULL)
439         {
440          /*
441           * That worked, flag it...
442           */
443
444           changed = 1;
445         }
446         else
447           cupsArrayDelete(test);
448
449        /*
450         * Add the option/value from the resolver regardless of whether it
451         * worked; this makes sure that we can cascade several changes to
452         * make things resolve...
453         */
454
455         num_myres = cupsAddOption(attr->name, value, num_myres, &myres);
456       }
457     }
458
459     if (!changed)
460     {
461       DEBUG_puts("1cupsCopyDestConflicts: Unable to resolve constraints.");
462       have_conflicts = -1;
463       goto cleanup;
464     }
465
466     cupsArrayClear(pass);
467
468     cupsArrayDelete(active);
469     active = NULL;
470   }
471
472   if (tries >= 100)
473   {
474     DEBUG_puts("1cupsCopyDestConflicts: Unable to resolve after 100 tries.");
475     have_conflicts = -1;
476     goto cleanup;
477   }
478
479  /*
480   * Copy resolved options as needed...
481   */
482
483   if (num_resolved)
484   {
485     for (i = num_myres, myoption = myres; i > 0; i --, myoption ++)
486     {
487       if ((myvalue = cupsGetOption(myoption->name, num_options,
488                                    options)) == NULL ||
489           strcmp(myvalue, myoption->value))
490       {
491         if (new_option && !strcmp(new_option, myoption->name) &&
492             new_value && !strcmp(new_value, myoption->value))
493           continue;
494
495         *num_resolved = cupsAddOption(myoption->name, myoption->value,
496                                       *num_resolved, resolved);
497       }
498     }
499   }
500
501  /*
502   * Clean up...
503   */
504
505   cleanup:
506
507   cupsArrayDelete(active);
508   cupsArrayDelete(pass);
509   cupsArrayDelete(resolvers);
510
511   if (num_resolved)
512   {
513    /*
514     * Free shadow copy of options...
515     */
516
517     cupsFreeOptions(num_myres, myres);
518   }
519
520   if (num_conflicts)
521   {
522    /*
523     * Return conflicting options to caller...
524     */
525
526     *num_conflicts = num_myconf;
527     *conflicts     = myconf;
528   }
529   else
530   {
531    /*
532     * Free conflicting options...
533     */
534
535     cupsFreeOptions(num_myconf, myconf);
536   }
537
538   return (have_conflicts);
539 }
540
541
542 /*
543  * 'cupsCopyDestInfo()' - Get the supported values/capabilities for the
544  *                        destination.
545  *
546  * The caller is responsible for calling @link cupsFreeDestInfo@ on the return
547  * value. @code NULL@ is returned on error.
548  *
549  * @since CUPS 1.6/macOS 10.8@
550  */
551
552 cups_dinfo_t *                          /* O - Destination information */
553 cupsCopyDestInfo(
554     http_t      *http,                  /* I - Connection to destination */
555     cups_dest_t *dest)                  /* I - Destination */
556 {
557   cups_dinfo_t  *dinfo;                 /* Destination information */
558   ipp_t         *request,               /* Get-Printer-Attributes request */
559                 *response;              /* Supported attributes */
560   int           tries,                  /* Number of tries so far */
561                 delay,                  /* Current retry delay */
562                 prev_delay;             /* Next retry delay */
563   const char    *uri;                   /* Printer URI */
564   char          resource[1024];         /* Resource path */
565   int           version;                /* IPP version */
566   ipp_status_t  status;                 /* Status of request */
567   static const char * const requested_attrs[] =
568   {                                     /* Requested attributes */
569     "job-template",
570     "media-col-database",
571     "printer-description"
572   };
573
574
575   DEBUG_printf(("cupsCopyDestSupported(http=%p, dest=%p(%s))", (void *)http, (void *)dest, dest ? dest->name : ""));
576
577  /*
578   * Range check input...
579   */
580
581   if (!http || !dest)
582     return (NULL);
583
584  /*
585   * Get the printer URI and resource path...
586   */
587
588   if ((uri = _cupsGetDestResource(dest, resource, sizeof(resource))) == NULL)
589     return (NULL);
590
591  /*
592   * Get the supported attributes...
593   */
594
595   delay      = 1;
596   prev_delay = 1;
597   tries      = 0;
598   version    = 20;
599
600   do
601   {
602    /*
603     * Send a Get-Printer-Attributes request...
604     */
605
606     request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
607     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
608                  uri);
609     ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
610                  "requesting-user-name", NULL, cupsUser());
611     ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
612                   "requested-attributes",
613                   (int)(sizeof(requested_attrs) / sizeof(requested_attrs[0])),
614                   NULL, requested_attrs);
615     response = cupsDoRequest(http, request, resource);
616     status   = cupsLastError();
617
618     if (status > IPP_STATUS_OK_IGNORED_OR_SUBSTITUTED)
619     {
620       DEBUG_printf(("cupsCopyDestSupported: Get-Printer-Attributes for '%s' "
621                     "returned %s (%s)", dest->name, ippErrorString(status),
622                     cupsLastErrorString()));
623
624       ippDelete(response);
625       response = NULL;
626
627       if (status == IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED && version > 11)
628         version = 11;
629       else if (status == IPP_STATUS_ERROR_BUSY)
630       {
631         sleep((unsigned)delay);
632
633         delay = _cupsNextDelay(delay, &prev_delay);
634       }
635       else
636         return (NULL);
637     }
638
639     tries ++;
640   }
641   while (!response && tries < 10);
642
643   if (!response)
644     return (NULL);
645
646  /*
647   * Allocate a cups_dinfo_t structure and return it...
648   */
649
650   if ((dinfo = calloc(1, sizeof(cups_dinfo_t))) == NULL)
651   {
652     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(errno), 0);
653     ippDelete(response);
654     return (NULL);
655   }
656
657   dinfo->version  = version;
658   dinfo->uri      = uri;
659   dinfo->resource = _cupsStrAlloc(resource);
660   dinfo->attrs    = response;
661
662   return (dinfo);
663 }
664
665
666 /*
667  * 'cupsFindDestDefault()' - Find the default value(s) for the given option.
668  *
669  * The returned value is an IPP attribute. Use the @code ippGetBoolean@,
670  * @code ippGetCollection@, @code ippGetCount@, @code ippGetDate@,
671  * @code ippGetInteger@, @code ippGetOctetString@, @code ippGetRange@,
672  * @code ippGetResolution@, @code ippGetString@, and @code ippGetValueTag@
673  * functions to inspect the default value(s) as needed.
674  *
675  * @since CUPS 1.7/macOS 10.9@
676  */
677
678 ipp_attribute_t *                       /* O - Default attribute or @code NULL@ for none */
679 cupsFindDestDefault(
680     http_t       *http,                 /* I - Connection to destination */
681     cups_dest_t  *dest,                 /* I - Destination */
682     cups_dinfo_t *dinfo,                /* I - Destination information */
683     const char   *option)               /* I - Option/attribute name */
684 {
685   char  name[IPP_MAX_NAME];             /* Attribute name */
686
687
688  /*
689   * Range check input...
690   */
691
692   if (!http || !dest || !dinfo || !option)
693   {
694     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
695     return (NULL);
696   }
697
698  /*
699   * Find and return the attribute...
700   */
701
702   snprintf(name, sizeof(name), "%s-default", option);
703   return (ippFindAttribute(dinfo->attrs, name, IPP_TAG_ZERO));
704 }
705
706
707 /*
708  * 'cupsFindDestReady()' - Find the default value(s) for the given option.
709  *
710  * The returned value is an IPP attribute. Use the @code ippGetBoolean@,
711  * @code ippGetCollection@, @code ippGetCount@, @code ippGetDate@,
712  * @code ippGetInteger@, @code ippGetOctetString@, @code ippGetRange@,
713  * @code ippGetResolution@, @code ippGetString@, and @code ippGetValueTag@
714  * functions to inspect the default value(s) as needed.
715  *
716  * @since CUPS 1.7/macOS 10.9@
717  */
718
719 ipp_attribute_t *                       /* O - Default attribute or @code NULL@ for none */
720 cupsFindDestReady(
721     http_t       *http,                 /* I - Connection to destination */
722     cups_dest_t  *dest,                 /* I - Destination */
723     cups_dinfo_t *dinfo,                /* I - Destination information */
724     const char   *option)               /* I - Option/attribute name */
725 {
726   char  name[IPP_MAX_NAME];             /* Attribute name */
727
728
729  /*
730   * Range check input...
731   */
732
733   if (!http || !dest || !dinfo || !option)
734   {
735     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
736     return (NULL);
737   }
738
739  /*
740   * Find and return the attribute...
741   */
742
743   cups_update_ready(http, dinfo);
744
745   snprintf(name, sizeof(name), "%s-ready", option);
746   return (ippFindAttribute(dinfo->ready_attrs, name, IPP_TAG_ZERO));
747 }
748
749
750 /*
751  * 'cupsFindDestSupported()' - Find the default value(s) for the given option.
752  *
753  * The returned value is an IPP attribute. Use the @code ippGetBoolean@,
754  * @code ippGetCollection@, @code ippGetCount@, @code ippGetDate@,
755  * @code ippGetInteger@, @code ippGetOctetString@, @code ippGetRange@,
756  * @code ippGetResolution@, @code ippGetString@, and @code ippGetValueTag@
757  * functions to inspect the default value(s) as needed.
758  *
759  * @since CUPS 1.7/macOS 10.9@
760  */
761
762 ipp_attribute_t *                       /* O - Default attribute or @code NULL@ for none */
763 cupsFindDestSupported(
764     http_t       *http,                 /* I - Connection to destination */
765     cups_dest_t  *dest,                 /* I - Destination */
766     cups_dinfo_t *dinfo,                /* I - Destination information */
767     const char   *option)               /* I - Option/attribute name */
768 {
769   char  name[IPP_MAX_NAME];             /* Attribute name */
770
771
772  /*
773   * Range check input...
774   */
775
776   if (!http || !dest || !dinfo || !option)
777   {
778     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
779     return (NULL);
780   }
781
782  /*
783   * Find and return the attribute...
784   */
785
786   snprintf(name, sizeof(name), "%s-supported", option);
787   return (ippFindAttribute(dinfo->attrs, name, IPP_TAG_ZERO));
788 }
789
790
791 /*
792  * 'cupsFreeDestInfo()' - Free destination information obtained using
793  *                        @link cupsCopyDestInfo@.
794  */
795
796 void
797 cupsFreeDestInfo(cups_dinfo_t *dinfo)   /* I - Destination information */
798 {
799  /*
800   * Range check input...
801   */
802
803   if (!dinfo)
804     return;
805
806  /*
807   * Free memory and return...
808   */
809
810   _cupsStrFree(dinfo->resource);
811
812   cupsArrayDelete(dinfo->constraints);
813   cupsArrayDelete(dinfo->resolvers);
814
815   cupsArrayDelete(dinfo->localizations);
816
817   cupsArrayDelete(dinfo->media_db);
818
819   cupsArrayDelete(dinfo->cached_db);
820
821   ippDelete(dinfo->ready_attrs);
822   cupsArrayDelete(dinfo->ready_db);
823
824   ippDelete(dinfo->attrs);
825
826   free(dinfo);
827 }
828
829
830 /*
831  * 'cupsGetDestMediaByIndex()' - Get a media name, dimension, and margins for a
832  *                               specific size.
833  *
834  * The @code flags@ parameter determines which set of media are indexed.  For
835  * example, passing @code CUPS_MEDIA_FLAGS_BORDERLESS@ will get the Nth
836  * borderless size supported by the printer.
837  *
838  * @since CUPS 1.7/macOS 10.9@
839  */
840
841 int                                     /* O - 1 on success, 0 on failure */
842 cupsGetDestMediaByIndex(
843     http_t       *http,                 /* I - Connection to destination */
844     cups_dest_t  *dest,                 /* I - Destination */
845     cups_dinfo_t *dinfo,                /* I - Destination information */
846     int          n,                     /* I - Media size number (0-based) */
847     unsigned     flags,                 /* I - Media flags */
848     cups_size_t  *size)                 /* O - Media size information */
849 {
850   _cups_media_db_t      *nsize;         /* Size for N */
851   pwg_media_t           *pwg;           /* PWG media name for size */
852
853
854  /*
855   * Range check input...
856   */
857
858   if (size)
859     memset(size, 0, sizeof(cups_size_t));
860
861   if (!http || !dest || !dinfo || n < 0 || !size)
862   {
863     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
864     return (0);
865   }
866
867  /*
868   * Load media list as needed...
869   */
870
871   if (flags & CUPS_MEDIA_FLAGS_READY)
872     cups_update_ready(http, dinfo);
873
874   if (!dinfo->cached_db || dinfo->cached_flags != flags)
875     cups_create_cached(http, dinfo, flags);
876
877  /*
878   * Copy the size over and return...
879   */
880
881   if ((nsize = (_cups_media_db_t *)cupsArrayIndex(dinfo->cached_db, n)) == NULL)
882   {
883     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
884     return (0);
885   }
886
887   if (nsize->size_name)
888     strlcpy(size->media, nsize->size_name, sizeof(size->media));
889   else if (nsize->key)
890     strlcpy(size->media, nsize->key, sizeof(size->media));
891   else if ((pwg = pwgMediaForSize(nsize->width, nsize->length)) != NULL)
892     strlcpy(size->media, pwg->pwg, sizeof(size->media));
893   else
894   {
895     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
896     return (0);
897   }
898
899   size->width  = nsize->width;
900   size->length = nsize->length;
901   size->bottom = nsize->bottom;
902   size->left   = nsize->left;
903   size->right  = nsize->right;
904   size->top    = nsize->top;
905
906   return (1);
907 }
908
909
910 /*
911  * 'cupsGetDestMediaByName()' - Get media names, dimensions, and margins.
912  *
913  * The "media" string is a PWG media name.  "Flags" provides some matching
914  * guidance (multiple flags can be combined):
915  *
916  * CUPS_MEDIA_FLAGS_DEFAULT    = find the closest size supported by the printer,
917  * CUPS_MEDIA_FLAGS_BORDERLESS = find a borderless size,
918  * CUPS_MEDIA_FLAGS_DUPLEX     = find a size compatible with 2-sided printing,
919  * CUPS_MEDIA_FLAGS_EXACT      = find an exact match for the size, and
920  * CUPS_MEDIA_FLAGS_READY      = if the printer supports media sensing, find the
921  *                               size amongst the "ready" media.
922  *
923  * The matching result (if any) is returned in the "cups_size_t" structure.
924  *
925  * Returns 1 when there is a match and 0 if there is not a match.
926  *
927  * @since CUPS 1.6/macOS 10.8@
928  */
929
930 int                                     /* O - 1 on match, 0 on failure */
931 cupsGetDestMediaByName(
932     http_t       *http,                 /* I - Connection to destination */
933     cups_dest_t  *dest,                 /* I - Destination */
934     cups_dinfo_t *dinfo,                /* I - Destination information */
935     const char   *media,                /* I - Media name */
936     unsigned     flags,                 /* I - Media matching flags */
937     cups_size_t  *size)                 /* O - Media size information */
938 {
939   pwg_media_t           *pwg;           /* PWG media info */
940
941
942  /*
943   * Range check input...
944   */
945
946   if (size)
947     memset(size, 0, sizeof(cups_size_t));
948
949   if (!http || !dest || !dinfo || !media || !size)
950   {
951     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
952     return (0);
953   }
954
955  /*
956   * Lookup the media size name...
957   */
958
959   if ((pwg = pwgMediaForPWG(media)) == NULL)
960     if ((pwg = pwgMediaForLegacy(media)) == NULL)
961     {
962       DEBUG_printf(("1cupsGetDestMediaByName: Unknown size '%s'.", media));
963       _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Unknown media size name."), 1);
964       return (0);
965     }
966
967  /*
968   * Lookup the size...
969   */
970
971   return (cups_get_media_db(http, dinfo, pwg, flags, size));
972 }
973
974
975 /*
976  * 'cupsGetDestMediaBySize()' - Get media names, dimensions, and margins.
977  *
978  * "Width" and "length" are the dimensions in hundredths of millimeters.
979  * "Flags" provides some matching guidance (multiple flags can be combined):
980  *
981  * CUPS_MEDIA_FLAGS_DEFAULT    = find the closest size supported by the printer,
982  * CUPS_MEDIA_FLAGS_BORDERLESS = find a borderless size,
983  * CUPS_MEDIA_FLAGS_DUPLEX     = find a size compatible with 2-sided printing,
984  * CUPS_MEDIA_FLAGS_EXACT      = find an exact match for the size, and
985  * CUPS_MEDIA_FLAGS_READY      = if the printer supports media sensing, find the
986  *                               size amongst the "ready" media.
987  *
988  * The matching result (if any) is returned in the "cups_size_t" structure.
989  *
990  * Returns 1 when there is a match and 0 if there is not a match.
991  *
992  * @since CUPS 1.6/macOS 10.8@
993  */
994
995 int                                     /* O - 1 on match, 0 on failure */
996 cupsGetDestMediaBySize(
997     http_t       *http,                 /* I - Connection to destination */
998     cups_dest_t  *dest,                 /* I - Destination */
999     cups_dinfo_t *dinfo,                /* I - Destination information */
1000     int         width,                  /* I - Media width in hundredths of
1001                                          *     of millimeters */
1002     int         length,                 /* I - Media length in hundredths of
1003                                          *     of millimeters */
1004     unsigned     flags,                 /* I - Media matching flags */
1005     cups_size_t  *size)                 /* O - Media size information */
1006 {
1007   pwg_media_t           *pwg;           /* PWG media info */
1008
1009
1010  /*
1011   * Range check input...
1012   */
1013
1014   if (size)
1015     memset(size, 0, sizeof(cups_size_t));
1016
1017   if (!http || !dest || !dinfo || width <= 0 || length <= 0 || !size)
1018   {
1019     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
1020     return (0);
1021   }
1022
1023  /*
1024   * Lookup the media size name...
1025   */
1026
1027   if ((pwg = pwgMediaForSize(width, length)) == NULL)
1028   {
1029     DEBUG_printf(("1cupsGetDestMediaBySize: Invalid size %dx%d.", width,
1030                   length));
1031     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, _("Invalid media size."), 1);
1032     return (0);
1033   }
1034
1035  /*
1036   * Lookup the size...
1037   */
1038
1039   return (cups_get_media_db(http, dinfo, pwg, flags, size));
1040 }
1041
1042
1043 /*
1044  * 'cupsGetDestMediaCount()' - Get the number of sizes supported by a
1045  *                             destination.
1046  *
1047  * The @code flags@ parameter determines the set of media sizes that are
1048  * counted.  For example, passing @code CUPS_MEDIA_FLAGS_BORDERLESS@ will return
1049  * the number of borderless sizes.
1050  *
1051  * @since CUPS 1.7/macOS 10.9@
1052  */
1053
1054 int                                     /* O - Number of sizes */
1055 cupsGetDestMediaCount(
1056     http_t       *http,                 /* I - Connection to destination */
1057     cups_dest_t  *dest,                 /* I - Destination */
1058     cups_dinfo_t *dinfo,                /* I - Destination information */
1059     unsigned     flags)                 /* I - Media flags */
1060 {
1061  /*
1062   * Range check input...
1063   */
1064
1065   if (!http || !dest || !dinfo)
1066   {
1067     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
1068     return (0);
1069   }
1070
1071  /*
1072   * Load media list as needed...
1073   */
1074
1075   if (flags & CUPS_MEDIA_FLAGS_READY)
1076     cups_update_ready(http, dinfo);
1077
1078   if (!dinfo->cached_db || dinfo->cached_flags != flags)
1079     cups_create_cached(http, dinfo, flags);
1080
1081   return (cupsArrayCount(dinfo->cached_db));
1082 }
1083
1084
1085 /*
1086  * 'cupsGetDestMediaDefault()' - Get the default size for a destination.
1087  *
1088  * The @code flags@ parameter determines which default size is returned.  For
1089  * example, passing @code CUPS_MEDIA_FLAGS_BORDERLESS@ will return the default
1090  * borderless size, typically US Letter or A4, but sometimes 4x6 photo media.
1091  *
1092  * @since CUPS 1.7/macOS 10.9@
1093  */
1094
1095 int                                     /* O - 1 on success, 0 on failure */
1096 cupsGetDestMediaDefault(
1097     http_t       *http,                 /* I - Connection to destination */
1098     cups_dest_t  *dest,                 /* I - Destination */
1099     cups_dinfo_t *dinfo,                /* I - Destination information */
1100     unsigned     flags,                 /* I - Media flags */
1101     cups_size_t  *size)                 /* O - Media size information */
1102 {
1103   const char    *media;                 /* Default media size */
1104
1105
1106  /*
1107   * Range check input...
1108   */
1109
1110   if (size)
1111     memset(size, 0, sizeof(cups_size_t));
1112
1113   if (!http || !dest || !dinfo || !size)
1114   {
1115     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
1116     return (0);
1117   }
1118
1119  /*
1120   * Get the default media size, if any...
1121   */
1122
1123   if ((media = cupsGetOption("media", dest->num_options,
1124                              dest->options)) == NULL)
1125     media = "na_letter_8.5x11in";
1126
1127   if (cupsGetDestMediaByName(http, dest, dinfo, media, flags, size))
1128     return (1);
1129
1130   if (strcmp(media, "na_letter_8.5x11in") &&
1131       cupsGetDestMediaByName(http, dest, dinfo, "iso_a4_210x297mm", flags,
1132                              size))
1133     return (1);
1134
1135   if (strcmp(media, "iso_a4_210x297mm") &&
1136       cupsGetDestMediaByName(http, dest, dinfo, "na_letter_8.5x11in", flags,
1137                              size))
1138     return (1);
1139
1140   if ((flags & CUPS_MEDIA_FLAGS_BORDERLESS) &&
1141       cupsGetDestMediaByName(http, dest, dinfo, "na_index_4x6in", flags, size))
1142     return (1);
1143
1144  /*
1145   * Fall back to the first matching media size...
1146   */
1147
1148   return (cupsGetDestMediaByIndex(http, dest, dinfo, 0, flags, size));
1149 }
1150
1151
1152 /*
1153  * 'cups_add_dconstres()' - Add a constraint or resolver to an array.
1154  */
1155
1156 static void
1157 cups_add_dconstres(
1158     cups_array_t *a,                    /* I - Array */
1159     ipp_t        *collection)           /* I - Collection value */
1160 {
1161   ipp_attribute_t       *attr;          /* Attribute */
1162   _cups_dconstres_t     *temp;          /* Current constraint/resolver */
1163
1164
1165   if ((attr = ippFindAttribute(collection, "resolver-name",
1166                                IPP_TAG_NAME)) == NULL)
1167     return;
1168
1169   if ((temp = calloc(1, sizeof(_cups_dconstres_t))) == NULL)
1170     return;
1171
1172   temp->name       = attr->values[0].string.text;
1173   temp->collection = collection;
1174
1175   cupsArrayAdd(a, temp);
1176 }
1177
1178
1179 /*
1180  * 'cups_compare_dconstres()' - Compare to resolver entries.
1181  */
1182
1183 static int                              /* O - Result of comparison */
1184 cups_compare_dconstres(
1185     _cups_dconstres_t *a,               /* I - First resolver */
1186     _cups_dconstres_t *b)               /* I - Second resolver */
1187 {
1188   return (strcmp(a->name, b->name));
1189 }
1190
1191
1192 /*
1193  * 'cups_compare_media_db()' - Compare two media entries.
1194  */
1195
1196 static int                              /* O - Result of comparison */
1197 cups_compare_media_db(
1198     _cups_media_db_t *a,                /* I - First media entries */
1199     _cups_media_db_t *b)                /* I - Second media entries */
1200 {
1201   int   result;                         /* Result of comparison */
1202
1203
1204   if ((result = a->width - b->width) == 0)
1205     result = a->length - b->length;
1206
1207   return (result);
1208 }
1209
1210
1211 /*
1212  * 'cups_copy_media_db()' - Copy a media entry.
1213  */
1214
1215 static _cups_media_db_t *               /* O - New media entry */
1216 cups_copy_media_db(
1217     _cups_media_db_t *mdb)              /* I - Media entry to copy */
1218 {
1219   _cups_media_db_t *temp;               /* New media entry */
1220
1221
1222   if ((temp = calloc(1, sizeof(_cups_media_db_t))) == NULL)
1223     return (NULL);
1224
1225   if (mdb->color)
1226     temp->color = _cupsStrAlloc(mdb->color);
1227   if (mdb->key)
1228     temp->key = _cupsStrAlloc(mdb->key);
1229   if (mdb->info)
1230     temp->info = _cupsStrAlloc(mdb->info);
1231   if (mdb->size_name)
1232     temp->size_name = _cupsStrAlloc(mdb->size_name);
1233   if (mdb->source)
1234     temp->source = _cupsStrAlloc(mdb->source);
1235   if (mdb->type)
1236     temp->type = _cupsStrAlloc(mdb->type);
1237
1238   temp->width  = mdb->width;
1239   temp->length = mdb->length;
1240   temp->bottom = mdb->bottom;
1241   temp->left   = mdb->left;
1242   temp->right  = mdb->right;
1243   temp->top    = mdb->top;
1244
1245   return (temp);
1246 }
1247
1248
1249 /*
1250  * 'cups_create_cached()' - Create the media selection cache.
1251  */
1252
1253 static void
1254 cups_create_cached(http_t       *http,  /* I - Connection to destination */
1255                    cups_dinfo_t *dinfo, /* I - Destination information */
1256                    unsigned     flags)  /* I - Media selection flags */
1257 {
1258   cups_array_t          *db;            /* Media database array to use */
1259   _cups_media_db_t      *mdb,           /* Media database entry */
1260                         *first;         /* First entry this size */
1261
1262
1263   DEBUG_printf(("3cups_create_cached(http=%p, dinfo=%p, flags=%u)", (void *)http, (void *)dinfo, flags));
1264
1265   if (dinfo->cached_db)
1266     cupsArrayDelete(dinfo->cached_db);
1267
1268   dinfo->cached_db    = cupsArrayNew(NULL, NULL);
1269   dinfo->cached_flags = flags;
1270
1271   if (flags & CUPS_MEDIA_FLAGS_READY)
1272   {
1273     DEBUG_puts("4cups_create_cached: ready media");
1274
1275     cups_update_ready(http, dinfo);
1276     db = dinfo->ready_db;
1277   }
1278   else
1279   {
1280     DEBUG_puts("4cups_create_cached: supported media");
1281
1282     if (!dinfo->media_db)
1283       cups_create_media_db(dinfo, CUPS_MEDIA_FLAGS_DEFAULT);
1284
1285     db = dinfo->media_db;
1286   }
1287
1288   for (mdb = (_cups_media_db_t *)cupsArrayFirst(db), first = mdb;
1289        mdb;
1290        mdb = (_cups_media_db_t *)cupsArrayNext(db))
1291   {
1292     DEBUG_printf(("4cups_create_cached: %p key=\"%s\", type=\"%s\", %dx%d, B%d L%d R%d T%d", (void *)mdb, mdb->key, mdb->type, mdb->width, mdb->length, mdb->bottom, mdb->left, mdb->right, mdb->top));
1293
1294     if (flags & CUPS_MEDIA_FLAGS_BORDERLESS)
1295     {
1296       if (!mdb->left && !mdb->right && !mdb->top && !mdb->bottom)
1297       {
1298         DEBUG_printf(("4cups_create_cached: add %p", (void *)mdb));
1299         cupsArrayAdd(dinfo->cached_db, mdb);
1300       }
1301     }
1302     else if (flags & CUPS_MEDIA_FLAGS_DUPLEX)
1303     {
1304       if (first->width != mdb->width || first->length != mdb->length)
1305       {
1306         DEBUG_printf(("4cups_create_cached: add %p", (void *)first));
1307         cupsArrayAdd(dinfo->cached_db, first);
1308         first = mdb;
1309       }
1310       else if (mdb->left >= first->left && mdb->right >= first->right && mdb->top >= first->top && mdb->bottom >= first->bottom &&
1311                (mdb->left != first->left || mdb->right != first->right || mdb->top != first->top || mdb->bottom != first->bottom))
1312         first = mdb;
1313     }
1314     else
1315     {
1316       DEBUG_printf(("4cups_create_cached: add %p", (void *)mdb));
1317       cupsArrayAdd(dinfo->cached_db, mdb);
1318     }
1319   }
1320
1321   if (flags & CUPS_MEDIA_FLAGS_DUPLEX)
1322   {
1323     DEBUG_printf(("4cups_create_cached: add %p", (void *)first));
1324     cupsArrayAdd(dinfo->cached_db, first);
1325   }
1326 }
1327
1328
1329 /*
1330  * 'cups_create_constraints()' - Create the constraints and resolvers arrays.
1331  */
1332
1333 static void
1334 cups_create_constraints(
1335     cups_dinfo_t *dinfo)                /* I - Destination information */
1336 {
1337   int                   i;              /* Looping var */
1338   ipp_attribute_t       *attr;          /* Attribute */
1339   _ipp_value_t          *val;           /* Current value */
1340
1341
1342   dinfo->constraints = cupsArrayNew3(NULL, NULL, NULL, 0, NULL,
1343                                      (cups_afree_func_t)free);
1344   dinfo->resolvers   = cupsArrayNew3((cups_array_func_t)cups_compare_dconstres,
1345                                      NULL, NULL, 0, NULL,
1346                                      (cups_afree_func_t)free);
1347
1348   if ((attr = ippFindAttribute(dinfo->attrs, "job-constraints-supported",
1349                                IPP_TAG_BEGIN_COLLECTION)) != NULL)
1350   {
1351     for (i = attr->num_values, val = attr->values; i > 0; i --, val ++)
1352       cups_add_dconstres(dinfo->constraints, val->collection);
1353   }
1354
1355   if ((attr = ippFindAttribute(dinfo->attrs, "job-resolvers-supported",
1356                                IPP_TAG_BEGIN_COLLECTION)) != NULL)
1357   {
1358     for (i = attr->num_values, val = attr->values; i > 0; i --, val ++)
1359       cups_add_dconstres(dinfo->resolvers, val->collection);
1360   }
1361 }
1362
1363
1364 /*
1365  * 'cups_create_defaults()' - Create the -default option array.
1366  *
1367  * TODO: Need to support collection defaults...
1368  */
1369
1370 static void
1371 cups_create_defaults(
1372     cups_dinfo_t *dinfo)                /* I - Destination information */
1373 {
1374   ipp_attribute_t       *attr;          /* Current attribute */
1375   char                  name[IPP_MAX_NAME + 1],
1376                                         /* Current name */
1377                         *nameptr,       /* Pointer into current name */
1378                         value[2048];    /* Current value */
1379
1380
1381  /*
1382   * Iterate through the printer attributes looking for xxx-default and adding
1383   * xxx=value to the defaults option array.
1384   */
1385
1386   for (attr = ippFirstAttribute(dinfo->attrs);
1387        attr;
1388        attr = ippNextAttribute(dinfo->attrs))
1389   {
1390     if (!attr->name || attr->group_tag != IPP_TAG_PRINTER)
1391       continue;
1392
1393     if (attr->value_tag == IPP_TAG_BEGIN_COLLECTION)
1394       continue;                         /* TODO: STR #4096 */
1395
1396     if ((nameptr = attr->name + strlen(attr->name) - 8) <= attr->name ||
1397         strcmp(nameptr, "-default"))
1398       continue;
1399
1400     strlcpy(name, attr->name, sizeof(name));
1401     if ((nameptr = name + strlen(name) - 8) <= name ||
1402         strcmp(nameptr, "-default"))
1403       continue;
1404
1405     *nameptr = '\0';
1406
1407     if (ippAttributeString(attr, value, sizeof(value)) >= sizeof(value))
1408       continue;
1409
1410     dinfo->num_defaults = cupsAddOption(name, value, dinfo->num_defaults,
1411                                         &dinfo->defaults);
1412   }
1413 }
1414
1415
1416 /*
1417  * 'cups_create_media_db()' - Create the media database.
1418  */
1419
1420 static void
1421 cups_create_media_db(
1422     cups_dinfo_t *dinfo,                /* I - Destination information */
1423     unsigned     flags)                 /* I - Media flags */
1424 {
1425   int                   i;              /* Looping var */
1426   _ipp_value_t          *val;           /* Current value */
1427   ipp_attribute_t       *media_col_db,  /* media-col-database */
1428                         *media_attr,    /* media-xxx */
1429                         *x_dimension,   /* x-dimension */
1430                         *y_dimension;   /* y-dimension */
1431   pwg_media_t           *pwg;           /* PWG media info */
1432   cups_array_t          *db;            /* New media database array */
1433   _cups_media_db_t      mdb;            /* Media entry */
1434
1435
1436   db = cupsArrayNew3((cups_array_func_t)cups_compare_media_db,
1437                      NULL, NULL, 0,
1438                      (cups_acopy_func_t)cups_copy_media_db,
1439                      (cups_afree_func_t)cups_free_media_db);
1440
1441   if (flags == CUPS_MEDIA_FLAGS_READY)
1442   {
1443     dinfo->ready_db = db;
1444
1445     media_col_db = ippFindAttribute(dinfo->ready_attrs, "media-col-ready",
1446                                     IPP_TAG_BEGIN_COLLECTION);
1447     media_attr   = ippFindAttribute(dinfo->ready_attrs, "media-ready",
1448                                     IPP_TAG_ZERO);
1449   }
1450   else
1451   {
1452     dinfo->media_db        = db;
1453     dinfo->min_size.width  = INT_MAX;
1454     dinfo->min_size.length = INT_MAX;
1455     dinfo->max_size.width  = 0;
1456     dinfo->max_size.length = 0;
1457
1458     media_col_db = ippFindAttribute(dinfo->attrs, "media-col-database",
1459                                     IPP_TAG_BEGIN_COLLECTION);
1460     media_attr   = ippFindAttribute(dinfo->attrs, "media-supported",
1461                                     IPP_TAG_ZERO);
1462   }
1463
1464   if (media_col_db)
1465   {
1466     _ipp_value_t        *custom = NULL; /* Custom size range value */
1467
1468     for (i = media_col_db->num_values, val = media_col_db->values;
1469          i > 0;
1470          i --, val ++)
1471     {
1472       memset(&mdb, 0, sizeof(mdb));
1473
1474       if ((media_attr = ippFindAttribute(val->collection, "media-size",
1475                                          IPP_TAG_BEGIN_COLLECTION)) != NULL)
1476       {
1477         ipp_t   *media_size = media_attr->values[0].collection;
1478                                         /* media-size collection value */
1479
1480         if ((x_dimension = ippFindAttribute(media_size, "x-dimension",
1481                                             IPP_TAG_INTEGER)) != NULL &&
1482             (y_dimension = ippFindAttribute(media_size, "y-dimension",
1483                                             IPP_TAG_INTEGER)) != NULL)
1484         {
1485          /*
1486           * Fixed size...
1487           */
1488
1489           mdb.width  = x_dimension->values[0].integer;
1490           mdb.length = y_dimension->values[0].integer;
1491         }
1492         else if ((x_dimension = ippFindAttribute(media_size, "x-dimension",
1493                                                  IPP_TAG_INTEGER)) != NULL &&
1494                  (y_dimension = ippFindAttribute(media_size, "y-dimension",
1495                                                  IPP_TAG_RANGE)) != NULL)
1496         {
1497          /*
1498           * Roll limits...
1499           */
1500
1501           mdb.width  = x_dimension->values[0].integer;
1502           mdb.length = y_dimension->values[0].range.upper;
1503         }
1504         else if (flags != CUPS_MEDIA_FLAGS_READY &&
1505                  (x_dimension = ippFindAttribute(media_size, "x-dimension",
1506                                                  IPP_TAG_RANGE)) != NULL &&
1507                  (y_dimension = ippFindAttribute(media_size, "y-dimension",
1508                                                  IPP_TAG_RANGE)) != NULL)
1509         {
1510          /*
1511           * Custom size range; save this as the custom size value with default
1512           * margins, then continue; we'll capture the real margins below...
1513           */
1514
1515           custom = val;
1516
1517           dinfo->min_size.width  = x_dimension->values[0].range.lower;
1518           dinfo->min_size.length = y_dimension->values[0].range.lower;
1519           dinfo->min_size.left   =
1520           dinfo->min_size.right  = 635; /* Default 1/4" side margins */
1521           dinfo->min_size.top    =
1522           dinfo->min_size.bottom = 1270; /* Default 1/2" top/bottom margins */
1523
1524           dinfo->max_size.width  = x_dimension->values[0].range.upper;
1525           dinfo->max_size.length = y_dimension->values[0].range.upper;
1526           dinfo->max_size.left   =
1527           dinfo->max_size.right  = 635; /* Default 1/4" side margins */
1528           dinfo->max_size.top    =
1529           dinfo->max_size.bottom = 1270; /* Default 1/2" top/bottom margins */
1530           continue;
1531         }
1532       }
1533
1534       if ((media_attr = ippFindAttribute(val->collection, "media-color",
1535                                          IPP_TAG_ZERO)) != NULL &&
1536           (media_attr->value_tag == IPP_TAG_NAME ||
1537            media_attr->value_tag == IPP_TAG_NAMELANG ||
1538            media_attr->value_tag == IPP_TAG_KEYWORD))
1539         mdb.color = media_attr->values[0].string.text;
1540
1541       if ((media_attr = ippFindAttribute(val->collection, "media-info",
1542                                          IPP_TAG_TEXT)) != NULL)
1543         mdb.info = media_attr->values[0].string.text;
1544
1545       if ((media_attr = ippFindAttribute(val->collection, "media-key",
1546                                          IPP_TAG_ZERO)) != NULL &&
1547           (media_attr->value_tag == IPP_TAG_NAME ||
1548            media_attr->value_tag == IPP_TAG_NAMELANG ||
1549            media_attr->value_tag == IPP_TAG_KEYWORD))
1550         mdb.key = media_attr->values[0].string.text;
1551
1552       if ((media_attr = ippFindAttribute(val->collection, "media-size-name",
1553                                          IPP_TAG_ZERO)) != NULL &&
1554           (media_attr->value_tag == IPP_TAG_NAME ||
1555            media_attr->value_tag == IPP_TAG_NAMELANG ||
1556            media_attr->value_tag == IPP_TAG_KEYWORD))
1557         mdb.size_name = media_attr->values[0].string.text;
1558
1559       if ((media_attr = ippFindAttribute(val->collection, "media-source",
1560                                          IPP_TAG_ZERO)) != NULL &&
1561           (media_attr->value_tag == IPP_TAG_NAME ||
1562            media_attr->value_tag == IPP_TAG_NAMELANG ||
1563            media_attr->value_tag == IPP_TAG_KEYWORD))
1564         mdb.source = media_attr->values[0].string.text;
1565
1566       if ((media_attr = ippFindAttribute(val->collection, "media-type",
1567                                          IPP_TAG_ZERO)) != NULL &&
1568           (media_attr->value_tag == IPP_TAG_NAME ||
1569            media_attr->value_tag == IPP_TAG_NAMELANG ||
1570            media_attr->value_tag == IPP_TAG_KEYWORD))
1571         mdb.type = media_attr->values[0].string.text;
1572
1573       if ((media_attr = ippFindAttribute(val->collection, "media-bottom-margin",
1574                                          IPP_TAG_INTEGER)) != NULL)
1575         mdb.bottom = media_attr->values[0].integer;
1576
1577       if ((media_attr = ippFindAttribute(val->collection, "media-left-margin",
1578                                          IPP_TAG_INTEGER)) != NULL)
1579         mdb.left = media_attr->values[0].integer;
1580
1581       if ((media_attr = ippFindAttribute(val->collection, "media-right-margin",
1582                                          IPP_TAG_INTEGER)) != NULL)
1583         mdb.right = media_attr->values[0].integer;
1584
1585       if ((media_attr = ippFindAttribute(val->collection, "media-top-margin",
1586                                          IPP_TAG_INTEGER)) != NULL)
1587         mdb.top = media_attr->values[0].integer;
1588
1589       cupsArrayAdd(db, &mdb);
1590     }
1591
1592     if (custom)
1593     {
1594       if ((media_attr = ippFindAttribute(custom->collection,
1595                                          "media-bottom-margin",
1596                                          IPP_TAG_INTEGER)) != NULL)
1597       {
1598         dinfo->min_size.top =
1599         dinfo->max_size.top = media_attr->values[0].integer;
1600       }
1601
1602       if ((media_attr = ippFindAttribute(custom->collection,
1603                                          "media-left-margin",
1604                                          IPP_TAG_INTEGER)) != NULL)
1605       {
1606         dinfo->min_size.left =
1607         dinfo->max_size.left = media_attr->values[0].integer;
1608       }
1609
1610       if ((media_attr = ippFindAttribute(custom->collection,
1611                                          "media-right-margin",
1612                                          IPP_TAG_INTEGER)) != NULL)
1613       {
1614         dinfo->min_size.right =
1615         dinfo->max_size.right = media_attr->values[0].integer;
1616       }
1617
1618       if ((media_attr = ippFindAttribute(custom->collection,
1619                                          "media-top-margin",
1620                                          IPP_TAG_INTEGER)) != NULL)
1621       {
1622         dinfo->min_size.top =
1623         dinfo->max_size.top = media_attr->values[0].integer;
1624       }
1625     }
1626   }
1627   else if (media_attr &&
1628            (media_attr->value_tag == IPP_TAG_NAME ||
1629             media_attr->value_tag == IPP_TAG_NAMELANG ||
1630             media_attr->value_tag == IPP_TAG_KEYWORD))
1631   {
1632     memset(&mdb, 0, sizeof(mdb));
1633
1634     mdb.left   =
1635     mdb.right  = 635; /* Default 1/4" side margins */
1636     mdb.top    =
1637     mdb.bottom = 1270; /* Default 1/2" top/bottom margins */
1638
1639     for (i = media_attr->num_values, val = media_attr->values;
1640          i > 0;
1641          i --, val ++)
1642     {
1643       if ((pwg = pwgMediaForPWG(val->string.text)) == NULL)
1644         if ((pwg = pwgMediaForLegacy(val->string.text)) == NULL)
1645         {
1646           DEBUG_printf(("3cups_create_media_db: Ignoring unknown size '%s'.",
1647                         val->string.text));
1648           continue;
1649         }
1650
1651       mdb.width  = pwg->width;
1652       mdb.length = pwg->length;
1653
1654       if (flags != CUPS_MEDIA_FLAGS_READY &&
1655           !strncmp(val->string.text, "custom_min_", 11))
1656       {
1657         mdb.size_name   = NULL;
1658         dinfo->min_size = mdb;
1659       }
1660       else if (flags != CUPS_MEDIA_FLAGS_READY &&
1661                !strncmp(val->string.text, "custom_max_", 11))
1662       {
1663         mdb.size_name   = NULL;
1664         dinfo->max_size = mdb;
1665       }
1666       else
1667       {
1668         mdb.size_name = val->string.text;
1669
1670         cupsArrayAdd(db, &mdb);
1671       }
1672     }
1673   }
1674 }
1675
1676
1677 /*
1678  * 'cups_free_media_cb()' - Free a media entry.
1679  */
1680
1681 static void
1682 cups_free_media_db(
1683     _cups_media_db_t *mdb)              /* I - Media entry to free */
1684 {
1685   if (mdb->color)
1686     _cupsStrFree(mdb->color);
1687   if (mdb->key)
1688     _cupsStrFree(mdb->key);
1689   if (mdb->info)
1690     _cupsStrFree(mdb->info);
1691   if (mdb->size_name)
1692     _cupsStrFree(mdb->size_name);
1693   if (mdb->source)
1694     _cupsStrFree(mdb->source);
1695   if (mdb->type)
1696     _cupsStrFree(mdb->type);
1697
1698   free(mdb);
1699 }
1700
1701
1702 /*
1703  * 'cups_get_media_db()' - Lookup the media entry for a given size.
1704  */
1705
1706 static int                              /* O - 1 on match, 0 on failure */
1707 cups_get_media_db(http_t       *http,   /* I - Connection to destination */
1708                   cups_dinfo_t *dinfo,  /* I - Destination information */
1709                   pwg_media_t  *pwg,    /* I - PWG media info */
1710                   unsigned     flags,   /* I - Media matching flags */
1711                   cups_size_t  *size)   /* O - Media size/margin/name info */
1712 {
1713   cups_array_t          *db;            /* Which media database to query */
1714   _cups_media_db_t      *mdb,           /* Current media database entry */
1715                         *best = NULL,   /* Best matching entry */
1716                         key;            /* Search key */
1717
1718
1719  /*
1720   * Create the media database as needed...
1721   */
1722
1723   if (flags & CUPS_MEDIA_FLAGS_READY)
1724   {
1725     cups_update_ready(http, dinfo);
1726     db = dinfo->ready_db;
1727   }
1728   else
1729   {
1730     if (!dinfo->media_db)
1731       cups_create_media_db(dinfo, CUPS_MEDIA_FLAGS_DEFAULT);
1732
1733     db = dinfo->media_db;
1734   }
1735
1736  /*
1737   * Find a match...
1738   */
1739
1740   memset(&key, 0, sizeof(key));
1741   key.width  = pwg->width;
1742   key.length = pwg->length;
1743
1744   if ((mdb = cupsArrayFind(db, &key)) != NULL)
1745   {
1746    /*
1747     * Found an exact match, let's figure out the best margins for the flags
1748     * supplied...
1749     */
1750
1751     best = mdb;
1752
1753     if (flags & CUPS_MEDIA_FLAGS_BORDERLESS)
1754     {
1755      /*
1756       * Look for the smallest margins...
1757       */
1758
1759       if (best->left != 0 || best->right != 0 || best->top != 0 || best->bottom != 0)
1760       {
1761         for (mdb = (_cups_media_db_t *)cupsArrayNext(db);
1762              mdb && !cups_compare_media_db(mdb, &key);
1763              mdb = (_cups_media_db_t *)cupsArrayNext(db))
1764         {
1765           if (mdb->left <= best->left && mdb->right <= best->right &&
1766               mdb->top <= best->top && mdb->bottom <= best->bottom)
1767           {
1768             best = mdb;
1769             if (mdb->left == 0 && mdb->right == 0 && mdb->bottom == 0 &&
1770                 mdb->top == 0)
1771               break;
1772           }
1773         }
1774       }
1775
1776      /*
1777       * If we need an exact match, return no-match if the size is not
1778       * borderless.
1779       */
1780
1781       if ((flags & CUPS_MEDIA_FLAGS_EXACT) &&
1782           (best->left || best->right || best->top || best->bottom))
1783         return (0);
1784     }
1785     else if (flags & CUPS_MEDIA_FLAGS_DUPLEX)
1786     {
1787      /*
1788       * Look for the largest margins...
1789       */
1790
1791       for (mdb = (_cups_media_db_t *)cupsArrayNext(db);
1792            mdb && !cups_compare_media_db(mdb, &key);
1793            mdb = (_cups_media_db_t *)cupsArrayNext(db))
1794       {
1795         if (mdb->left >= best->left && mdb->right >= best->right &&
1796             mdb->top >= best->top && mdb->bottom >= best->bottom &&
1797             (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
1798           best = mdb;
1799       }
1800     }
1801     else
1802     {
1803      /*
1804       * Look for the smallest non-zero margins...
1805       */
1806
1807       for (mdb = (_cups_media_db_t *)cupsArrayNext(db);
1808            mdb && !cups_compare_media_db(mdb, &key);
1809            mdb = (_cups_media_db_t *)cupsArrayNext(db))
1810       {
1811         if (((mdb->left > 0 && mdb->left <= best->left) || best->left == 0) &&
1812             ((mdb->right > 0 && mdb->right <= best->right) || best->right == 0) &&
1813             ((mdb->top > 0 && mdb->top <= best->top) || best->top == 0) &&
1814             ((mdb->bottom > 0 && mdb->bottom <= best->bottom) || best->bottom == 0) &&
1815             (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
1816           best = mdb;
1817       }
1818     }
1819   }
1820   else if (flags & CUPS_MEDIA_FLAGS_EXACT)
1821   {
1822    /*
1823     * See if we can do this as a custom size...
1824     */
1825
1826     if (pwg->width < dinfo->min_size.width ||
1827         pwg->width > dinfo->max_size.width ||
1828         pwg->length < dinfo->min_size.length ||
1829         pwg->length > dinfo->max_size.length)
1830       return (0);                       /* Out of range */
1831
1832     if ((flags & CUPS_MEDIA_FLAGS_BORDERLESS) &&
1833         (dinfo->min_size.left > 0 || dinfo->min_size.right > 0 ||
1834          dinfo->min_size.top > 0 || dinfo->min_size.bottom > 0))
1835       return (0);                       /* Not borderless */
1836
1837     key.size_name = (char *)pwg->pwg;
1838     key.bottom    = dinfo->min_size.bottom;
1839     key.left      = dinfo->min_size.left;
1840     key.right     = dinfo->min_size.right;
1841     key.top       = dinfo->min_size.top;
1842
1843     best = &key;
1844   }
1845   else if (pwg->width >= dinfo->min_size.width &&
1846            pwg->width <= dinfo->max_size.width &&
1847            pwg->length >= dinfo->min_size.length &&
1848            pwg->length <= dinfo->max_size.length)
1849   {
1850    /*
1851     * Map to custom size...
1852     */
1853
1854     key.size_name = (char *)pwg->pwg;
1855     key.bottom    = dinfo->min_size.bottom;
1856     key.left      = dinfo->min_size.left;
1857     key.right     = dinfo->min_size.right;
1858     key.top       = dinfo->min_size.top;
1859
1860     best = &key;
1861   }
1862   else
1863   {
1864    /*
1865     * Find a close size...
1866     */
1867
1868     for (mdb = (_cups_media_db_t *)cupsArrayFirst(db);
1869          mdb;
1870          mdb = (_cups_media_db_t *)cupsArrayNext(db))
1871       if (cups_is_close_media_db(mdb, &key))
1872         break;
1873
1874     if (!mdb)
1875       return (0);
1876
1877     best = mdb;
1878
1879     if (flags & CUPS_MEDIA_FLAGS_BORDERLESS)
1880     {
1881      /*
1882       * Look for the smallest margins...
1883       */
1884
1885       if (best->left != 0 || best->right != 0 || best->top != 0 ||
1886           best->bottom != 0)
1887       {
1888         for (mdb = (_cups_media_db_t *)cupsArrayNext(db);
1889              mdb && cups_is_close_media_db(mdb, &key);
1890              mdb = (_cups_media_db_t *)cupsArrayNext(db))
1891         {
1892           if (mdb->left <= best->left && mdb->right <= best->right &&
1893               mdb->top <= best->top && mdb->bottom <= best->bottom &&
1894               (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
1895           {
1896             best = mdb;
1897             if (mdb->left == 0 && mdb->right == 0 && mdb->bottom == 0 &&
1898                 mdb->top == 0)
1899               break;
1900           }
1901         }
1902       }
1903     }
1904     else if (flags & CUPS_MEDIA_FLAGS_DUPLEX)
1905     {
1906      /*
1907       * Look for the largest margins...
1908       */
1909
1910       for (mdb = (_cups_media_db_t *)cupsArrayNext(db);
1911            mdb && cups_is_close_media_db(mdb, &key);
1912            mdb = (_cups_media_db_t *)cupsArrayNext(db))
1913       {
1914         if (mdb->left >= best->left && mdb->right >= best->right &&
1915             mdb->top >= best->top && mdb->bottom >= best->bottom &&
1916             (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
1917           best = mdb;
1918       }
1919     }
1920     else
1921     {
1922      /*
1923       * Look for the smallest non-zero margins...
1924       */
1925
1926       for (mdb = (_cups_media_db_t *)cupsArrayNext(db);
1927            mdb && cups_is_close_media_db(mdb, &key);
1928            mdb = (_cups_media_db_t *)cupsArrayNext(db))
1929       {
1930         if (((mdb->left > 0 && mdb->left <= best->left) || best->left == 0) &&
1931             ((mdb->right > 0 && mdb->right <= best->right) ||
1932              best->right == 0) &&
1933             ((mdb->top > 0 && mdb->top <= best->top) || best->top == 0) &&
1934             ((mdb->bottom > 0 && mdb->bottom <= best->bottom) ||
1935              best->bottom == 0) &&
1936             (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
1937           best = mdb;
1938       }
1939     }
1940   }
1941
1942   if (best)
1943   {
1944    /*
1945     * Return the matching size...
1946     */
1947
1948     if (best->size_name)
1949       strlcpy(size->media, best->size_name, sizeof(size->media));
1950     else if (best->key)
1951       strlcpy(size->media, best->key, sizeof(size->media));
1952     else
1953       strlcpy(size->media, pwg->pwg, sizeof(size->media));
1954
1955     size->width  = best->width;
1956     size->length = best->length;
1957     size->bottom = best->bottom;
1958     size->left   = best->left;
1959     size->right  = best->right;
1960     size->top    = best->top;
1961
1962     return (1);
1963   }
1964
1965   return (0);
1966 }
1967
1968
1969 /*
1970  * 'cups_is_close_media_db()' - Compare two media entries to see if they are
1971  *                              close to the same size.
1972  *
1973  * Currently we use 5 points (from PostScript) as the matching range...
1974  */
1975
1976 static int                              /* O - 1 if the sizes are close */
1977 cups_is_close_media_db(
1978     _cups_media_db_t *a,                /* I - First media entries */
1979     _cups_media_db_t *b)                /* I - Second media entries */
1980 {
1981   int   dwidth,                         /* Difference in width */
1982         dlength;                        /* Difference in length */
1983
1984
1985   dwidth  = a->width - b->width;
1986   dlength = a->length - b->length;
1987
1988   return (dwidth >= -176 && dwidth <= 176 &&
1989           dlength >= -176 && dlength <= 176);
1990 }
1991
1992
1993 /*
1994  * 'cups_test_constraints()' - Test constraints.
1995  *
1996  * TODO: STR #4096 - Need to properly support media-col contraints...
1997  */
1998
1999 static cups_array_t *                   /* O - Active constraints */
2000 cups_test_constraints(
2001     cups_dinfo_t  *dinfo,               /* I - Destination information */
2002     const char    *new_option,          /* I - Newly selected option */
2003     const char    *new_value,           /* I - Newly selected value */
2004     int           num_options,          /* I - Number of options */
2005     cups_option_t *options,             /* I - Options */
2006     int           *num_conflicts,       /* O - Number of conflicting options */
2007     cups_option_t **conflicts)          /* O - Conflicting options */
2008 {
2009   int                   i,              /* Looping var */
2010                         match;          /* Value matches? */
2011   int                   num_matching;   /* Number of matching options */
2012   cups_option_t         *matching;      /* Matching options */
2013   _cups_dconstres_t     *c;             /* Current constraint */
2014   cups_array_t          *active = NULL; /* Active constraints */
2015   ipp_attribute_t       *attr;          /* Current attribute */
2016   _ipp_value_t          *attrval;       /* Current attribute value */
2017   const char            *value;         /* Current value */
2018   char                  temp[1024];     /* Temporary string */
2019   int                   int_value;      /* Integer value */
2020   int                   xres_value,     /* Horizontal resolution */
2021                         yres_value;     /* Vertical resolution */
2022   ipp_res_t             units_value;    /* Resolution units */
2023
2024
2025   for (c = (_cups_dconstres_t *)cupsArrayFirst(dinfo->constraints);
2026        c;
2027        c = (_cups_dconstres_t *)cupsArrayNext(dinfo->constraints))
2028   {
2029     num_matching = 0;
2030     matching     = NULL;
2031
2032     for (attr = ippFirstAttribute(c->collection);
2033          attr;
2034          attr = ippNextAttribute(c->collection))
2035     {
2036       if (attr->value_tag == IPP_TAG_BEGIN_COLLECTION)
2037         break;                          /* TODO: STR #4096 */
2038
2039      /*
2040       * Get the value for the current attribute in the constraint...
2041       */
2042
2043       if (new_option && new_value && !strcmp(attr->name, new_option))
2044         value = new_value;
2045       else if ((value = cupsGetOption(attr->name, num_options,
2046                                       options)) == NULL)
2047         value = cupsGetOption(attr->name, dinfo->num_defaults, dinfo->defaults);
2048
2049       if (!value)
2050       {
2051        /*
2052         * Not set so this constraint does not apply...
2053         */
2054
2055         break;
2056       }
2057
2058       match = 0;
2059
2060       switch (attr->value_tag)
2061       {
2062         case IPP_TAG_INTEGER :
2063         case IPP_TAG_ENUM :
2064             int_value = atoi(value);
2065
2066             for (i = attr->num_values, attrval = attr->values;
2067                  i > 0;
2068                  i --, attrval ++)
2069             {
2070               if (attrval->integer == int_value)
2071               {
2072                 match = 1;
2073                 break;
2074               }
2075             }
2076             break;
2077
2078         case IPP_TAG_BOOLEAN :
2079             int_value = !strcmp(value, "true");
2080
2081             for (i = attr->num_values, attrval = attr->values;
2082                  i > 0;
2083                  i --, attrval ++)
2084             {
2085               if (attrval->boolean == int_value)
2086               {
2087                 match = 1;
2088                 break;
2089               }
2090             }
2091             break;
2092
2093         case IPP_TAG_RANGE :
2094             int_value = atoi(value);
2095
2096             for (i = attr->num_values, attrval = attr->values;
2097                  i > 0;
2098                  i --, attrval ++)
2099             {
2100               if (int_value >= attrval->range.lower &&
2101                   int_value <= attrval->range.upper)
2102               {
2103                 match = 1;
2104                 break;
2105               }
2106             }
2107             break;
2108
2109         case IPP_TAG_RESOLUTION :
2110             if (sscanf(value, "%dx%d%15s", &xres_value, &yres_value, temp) != 3)
2111             {
2112               if (sscanf(value, "%d%15s", &xres_value, temp) != 2)
2113                 break;
2114
2115               yres_value = xres_value;
2116             }
2117
2118             if (!strcmp(temp, "dpi"))
2119               units_value = IPP_RES_PER_INCH;
2120             else if (!strcmp(temp, "dpc") || !strcmp(temp, "dpcm"))
2121               units_value = IPP_RES_PER_CM;
2122             else
2123               break;
2124
2125             for (i = attr->num_values, attrval = attr->values;
2126                  i > 0;
2127                  i --, attrval ++)
2128             {
2129               if (attrval->resolution.xres == xres_value &&
2130                   attrval->resolution.yres == yres_value &&
2131                   attrval->resolution.units == units_value)
2132               {
2133                 match = 1;
2134                 break;
2135               }
2136             }
2137             break;
2138
2139         case IPP_TAG_TEXT :
2140         case IPP_TAG_NAME :
2141         case IPP_TAG_KEYWORD :
2142         case IPP_TAG_CHARSET :
2143         case IPP_TAG_URI :
2144         case IPP_TAG_URISCHEME :
2145         case IPP_TAG_MIMETYPE :
2146         case IPP_TAG_LANGUAGE :
2147         case IPP_TAG_TEXTLANG :
2148         case IPP_TAG_NAMELANG :
2149             for (i = attr->num_values, attrval = attr->values;
2150                  i > 0;
2151                  i --, attrval ++)
2152             {
2153               if (!strcmp(attrval->string.text, value))
2154               {
2155                 match = 1;
2156                 break;
2157               }
2158             }
2159             break;
2160
2161         default :
2162             break;
2163       }
2164
2165       if (!match)
2166         break;
2167
2168       num_matching = cupsAddOption(attr->name, value, num_matching, &matching);
2169     }
2170
2171     if (!attr)
2172     {
2173       if (!active)
2174         active = cupsArrayNew(NULL, NULL);
2175
2176       cupsArrayAdd(active, c);
2177
2178       if (num_conflicts && conflicts)
2179       {
2180         cups_option_t   *moption;       /* Matching option */
2181
2182         for (i = num_matching, moption = matching; i > 0; i --, moption ++)
2183           *num_conflicts = cupsAddOption(moption->name, moption->value,
2184                                          *num_conflicts, conflicts);
2185       }
2186     }
2187
2188     cupsFreeOptions(num_matching, matching);
2189   }
2190
2191   return (active);
2192 }
2193
2194
2195 /*
2196  * 'cups_update_ready()' - Update xxx-ready attributes for the printer.
2197  */
2198
2199 static void
2200 cups_update_ready(http_t       *http,   /* I - Connection to destination */
2201                   cups_dinfo_t *dinfo)  /* I - Destination information */
2202 {
2203   ipp_t *request;                       /* Get-Printer-Attributes request */
2204   static const char * const pattrs[] =  /* Printer attributes we want */
2205   {
2206     "finishings-col-ready",
2207     "finishings-ready",
2208     "job-finishings-col-ready",
2209     "job-finishings-ready",
2210     "media-col-ready",
2211     "media-ready"
2212   };
2213
2214
2215  /*
2216   * Don't update more than once every 30 seconds...
2217   */
2218
2219   if ((time(NULL) - dinfo->ready_time) < _CUPS_MEDIA_READY_TTL)
2220     return;
2221
2222  /*
2223   * Free any previous results...
2224   */
2225
2226   if (dinfo->cached_flags & CUPS_MEDIA_FLAGS_READY)
2227   {
2228     cupsArrayDelete(dinfo->cached_db);
2229     dinfo->cached_db    = NULL;
2230     dinfo->cached_flags = CUPS_MEDIA_FLAGS_DEFAULT;
2231   }
2232
2233   ippDelete(dinfo->ready_attrs);
2234   dinfo->ready_attrs = NULL;
2235
2236   cupsArrayDelete(dinfo->ready_db);
2237   dinfo->ready_db = NULL;
2238
2239  /*
2240   * Query the xxx-ready values...
2241   */
2242
2243   request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
2244   ippSetVersion(request, dinfo->version / 10, dinfo->version % 10);
2245
2246   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
2247                dinfo->uri);
2248   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
2249                NULL, cupsUser());
2250   ippAddStrings(request, IPP_TAG_OPERATION, IPP_CONST_TAG(IPP_TAG_KEYWORD), "requested-attributes", (int)(sizeof(pattrs) / sizeof(pattrs[0])), NULL, pattrs);
2251
2252   dinfo->ready_attrs = cupsDoRequest(http, request, dinfo->resource);
2253
2254  /*
2255   * Update the ready media database...
2256   */
2257
2258   cups_create_media_db(dinfo, CUPS_MEDIA_FLAGS_READY);
2259
2260  /*
2261   * Update last lookup time and return...
2262   */
2263
2264   dinfo->ready_time = time(NULL);
2265 }