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