Home | History | Annotate | Download | only in cups
      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 }
   2266