Home | History | Annotate | Download | only in cups
      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 
     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 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 
    277 int					/* O - 1 if there is a conflict, 0 if none, -1 on error */
    278 cupsCopyDestConflicts(
    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 
    569 cups_dinfo_t *				/* O - Destination information */
    570 cupsCopyDestInfo(
    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 
    702 ipp_attribute_t	*			/* O - Default attribute or @code NULL@ for none */
    703 cupsFindDestDefault(
    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 
    750 ipp_attribute_t	*			/* O - Default attribute or @code NULL@ for none */
    751 cupsFindDestReady(
    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 
    800 ipp_attribute_t	*			/* O - Default attribute or @code NULL@ for none */
    801 cupsFindDestSupported(
    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 
    843 void
    844 cupsFreeDestInfo(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 
    888 int					/* O - 1 on success, 0 on failure */
    889 cupsGetDestMediaByIndex(
    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 
    984 int					/* O - 1 on match, 0 on failure */
    985 cupsGetDestMediaByName(
    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 
   1056 int					/* O - 1 on match, 0 on failure */
   1057 cupsGetDestMediaBySize(
   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 
   1122 int					/* O - Number of sizes */
   1123 cupsGetDestMediaCount(
   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 
   1170 int					/* O - 1 on success, 0 on failure */
   1171 cupsGetDestMediaDefault(
   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 
   1238 static void
   1239 cups_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 
   1265 static int				/* O - Result of comparison */
   1266 cups_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 
   1278 static int				/* O - Result of comparison */
   1279 cups_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 
   1297 static _cups_media_db_t *		/* O - New media entry */
   1298 cups_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 
   1335 static void
   1336 cups_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 
   1415 static void
   1416 cups_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 
   1452 static void
   1453 cups_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 
   1502 static void
   1503 cups_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 
   1763 static void
   1764 cups_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 
   1788 static int				/* O - 1 on match, 0 on failure */
   1789 cups_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 
   2058 static int				/* O - 1 if the sizes are close */
   2059 cups_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 
   2081 static cups_array_t *			/* O - Active constraints */
   2082 cups_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 
   2281 static void
   2282 cups_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