]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - cups/dest-options.c
Move debug printfs to internal usage only.
[thirdparty/cups.git] / cups / dest-options.c
index 5201302a85054121369fcc88e0e0efd8326276a0..308ee33597cb0d6ae7881d29281f547d8108dd15 100644 (file)
@@ -1,55 +1,10 @@
 /*
- * "$Id$"
+ * Destination option/media support for CUPS.
  *
- *   Destination option/media support for CUPS.
+ * Copyright © 2012-2018 by Apple Inc.
  *
- *   Copyright 2012-2013 by Apple Inc.
- *
- *   These coded instructions, statements, and computer programs are the
- *   property of Apple Inc. and are protected by Federal copyright
- *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
- *   which should have been included with this file.  If this file is
- *   file is missing or damaged, see the license at "http://www.cups.org/".
- *
- *   This file is subject to the Apple OS-Developed Software exception.
- *
- * Contents:
- *
- *   cupsCheckDestSupported()  - Check that the option and value are supported
- *                              by the destination.
- *   cupsCopyDestConflicts()   - Get conflicts and resolutions for a new
- *                              option/value pair.
- *   cupsCopyDestInfo()        - Get the supported values/capabilities for the
- *                              destination.
- *   cupsFindDestDefault()     - Find the default value(s) for the given
- *                              option.
- *   cupsFindDestReady()       - Find the default value(s) for the given
- *                              option.
- *   cupsFindDestSupported()   - Find the default value(s) for the given
- *                              option.
- *   cupsFreeDestInfo()        - Free destination information obtained using
- *                              @link cupsCopyDestInfo@.
- *   cupsGetDestMediaByIndex() - Get a media name, dimension, and margins for a
- *                              specific size.
- *   cupsGetDestMediaByName()  - Get media names, dimensions, and margins.
- *   cupsGetDestMediaBySize()  - Get media names, dimensions, and margins.
- *   cupsGetDestMediaCount()   - Get the number of sizes supported by a
- *                              destination.
- *   cupsGetDestMediaDefault() - Get the default size for a destination.
- *   cups_add_dconstres()      - Add a constraint or resolver to an array.
- *   cups_compare_dconstres()  - Compare to resolver entries.
- *   cups_compare_media_db()   - Compare two media entries.
- *   cups_copy_media_db()      - Copy a media entry.
- *   cups_create_cached()      - Create the media selection cache.
- *   cups_create_constraints() - Create the constraints and resolvers arrays.
- *   cups_create_defaults()    - Create the -default option array.
- *   cups_create_media_db()    - Create the media database.
- *   cups_free_media_cb()      - Free a media entry.
- *   cups_get_media_db()       - Lookup the media entry for a given size.
- *   cups_is_close_media_db()  - Compare two media entries to see if they are
- *                              close to the same size.
- *   cups_test_constraints()   - Test constraints.
- *   cups_update_ready()       - Update xxx-ready attributes for the printer.
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more
+ * information.
  */
 
 /*
@@ -57,6 +12,7 @@
  */
 
 #include "cups-private.h"
+#include "debug-internal.h"
 
 
 /*
@@ -71,6 +27,8 @@
  */
 
 static void            cups_add_dconstres(cups_array_t *a, ipp_t *collection);
+static int             cups_collection_contains(ipp_t *test, ipp_t *match);
+static size_t          cups_collection_string(ipp_attribute_t *attr, char *buffer, size_t bufsize);
 static int             cups_compare_dconstres(_cups_dconstres_t *a,
                                               _cups_dconstres_t *b);
 static int             cups_compare_media_db(_cups_media_db_t *a,
@@ -98,13 +56,111 @@ static cups_array_t        *cups_test_constraints(cups_dinfo_t *dinfo,
 static void            cups_update_ready(http_t *http, cups_dinfo_t *dinfo);
 
 
+/*
+ * 'cupsAddDestMediaOptions()' - Add the option corresponding to the specified media size.
+ *
+ * @since CUPS 2.3@
+ */
+
+int                                    /* O  - New number of options */
+cupsAddDestMediaOptions(
+    http_t        *http,               /* I  - Connection to destination */
+    cups_dest_t   *dest,               /* I  - Destination */
+    cups_dinfo_t  *dinfo,              /* I  - Destination information */
+    unsigned      flags,               /* I  - Media matching flags */
+    cups_size_t   *size,               /* I  - Media size */
+    int           num_options,         /* I  - Current number of options */
+    cups_option_t **options)           /* IO - Options */
+{
+  cups_array_t         *db;            /* Media database */
+  _cups_media_db_t     *mdb;           /* Media database entry */
+  char                 value[2048];    /* Option value */
+
+
+ /*
+  * Range check input...
+  */
+
+  if (!http || !dest || !dinfo || !size || !options)
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
+    return (num_options);
+  }
+
+ /*
+  * Find the matching media size...
+  */
+
+  if (flags & CUPS_MEDIA_FLAGS_READY)
+    db = dinfo->ready_db;
+  else
+    db = dinfo->media_db;
+
+  DEBUG_printf(("1cupsAddDestMediaOptions: size->media=\"%s\"", size->media));
+
+  for (mdb = (_cups_media_db_t *)cupsArrayFirst(db); mdb; mdb = (_cups_media_db_t *)cupsArrayNext(db))
+  {
+    if (mdb->key && !strcmp(mdb->key, size->media))
+      break;
+    else if (mdb->size_name && !strcmp(mdb->size_name, size->media))
+      break;
+  }
+
+  if (!mdb)
+  {
+    for (mdb = (_cups_media_db_t *)cupsArrayFirst(db); mdb; mdb = (_cups_media_db_t *)cupsArrayNext(db))
+    {
+      if (mdb->width == size->width && mdb->length == size->length && mdb->bottom == size->bottom && mdb->left == size->left && mdb->right == size->right && mdb->top == size->top)
+       break;
+    }
+  }
+
+  if (!mdb)
+  {
+    for (mdb = (_cups_media_db_t *)cupsArrayFirst(db); mdb; mdb = (_cups_media_db_t *)cupsArrayNext(db))
+    {
+      if (mdb->width == size->width && mdb->length == size->length)
+       break;
+    }
+  }
+
+  if (!mdb)
+  {
+    DEBUG_puts("1cupsAddDestMediaOptions: Unable to find matching size.");
+    return (num_options);
+  }
+
+  DEBUG_printf(("1cupsAddDestMediaOptions: MATCH mdb%p [key=\"%s\" size_name=\"%s\" source=\"%s\" type=\"%s\" width=%d length=%d B%d L%d R%d T%d]", (void *)mdb, mdb->key, mdb->size_name, mdb->source, mdb->type, mdb->width, mdb->length, mdb->bottom, mdb->left, mdb->right, mdb->top));
+
+  if (mdb->source)
+  {
+    if (mdb->type)
+      snprintf(value, sizeof(value), "{media-size={x-dimension=%d y-dimension=%d} media-bottom-margin=%d media-left-margin=%d media-right-margin=%d media-top-margin=%d media-source=\"%s\" media-type=\"%s\"}", mdb->width, mdb->length, mdb->bottom, mdb->left, mdb->right, mdb->top, mdb->source, mdb->type);
+    else
+      snprintf(value, sizeof(value), "{media-size={x-dimension=%d y-dimension=%d} media-bottom-margin=%d media-left-margin=%d media-right-margin=%d media-top-margin=%d media-source=\"%s\"}", mdb->width, mdb->length, mdb->bottom, mdb->left, mdb->right, mdb->top, mdb->source);
+  }
+  else if (mdb->type)
+  {
+    snprintf(value, sizeof(value), "{media-size={x-dimension=%d y-dimension=%d} media-bottom-margin=%d media-left-margin=%d media-right-margin=%d media-top-margin=%d media-type=\"%s\"}", mdb->width, mdb->length, mdb->bottom, mdb->left, mdb->right, mdb->top, mdb->type);
+  }
+  else
+  {
+    snprintf(value, sizeof(value), "{media-size={x-dimension=%d y-dimension=%d} media-bottom-margin=%d media-left-margin=%d media-right-margin=%d media-top-margin=%d}", mdb->width, mdb->length, mdb->bottom, mdb->left, mdb->right, mdb->top);
+  }
+
+  num_options = cupsAddOption("media-col", value, num_options, options);
+
+  return (num_options);
+}
+
+
 /*
  * 'cupsCheckDestSupported()' - Check that the option and value are supported
  *                              by the destination.
  *
  * Returns 1 if supported, 0 otherwise.
  *
- * @since CUPS 1.6/OS X 10.8@
+ * @since CUPS 1.6/macOS 10.8@
  */
 
 int                                    /* O - 1 if supported, 0 otherwise */
@@ -113,7 +169,7 @@ cupsCheckDestSupported(
     cups_dest_t  *dest,                        /* I - Destination */
     cups_dinfo_t *dinfo,               /* I - Destination information */
     const char   *option,              /* I - Option */
-    const char   *value)               /* I - Value */
+    const char   *value)               /* I - Value or @code NULL@ */
 {
   int                  i;              /* Looping var */
   char                 temp[1024];     /* Temporary string */
@@ -125,11 +181,18 @@ cupsCheckDestSupported(
   _ipp_value_t         *attrval;       /* Current attribute value */
 
 
+ /*
+  * Get the default connection as needed...
+  */
+
+  if (!http)
+    http = _cupsConnect();
+
  /*
   * Range check input...
   */
 
-  if (!http || !dest || !dinfo || !option || !value)
+  if (!http || !dest || !dinfo || !option)
     return (0);
 
  /*
@@ -147,7 +210,10 @@ cupsCheckDestSupported(
   if (!attr)
     return (0);
 
- /*
+  if (!value)
+    return (1);
+
+/*
   * Compare values...
   */
 
@@ -158,10 +224,10 @@ cupsCheckDestSupported(
     */
 
     pwg_media_t        *pwg;           /* Current PWG media size info */
-    int                        min_width,      /* Minimum width */
-                       min_length,     /* Minimum length */
-                       max_width,      /* Maximum width */
-                       max_length;     /* Maximum length */
+    int                min_width,      /* Minimum width */
+               min_length,     /* Minimum length */
+               max_width,      /* Maximum width */
+               max_length;     /* Maximum length */
 
    /*
     * Get the minimum and maximum size...
@@ -301,7 +367,7 @@ cupsCheckDestSupported(
  * If cupsCopyDestConflicts returns 1 but "num_resolved" and "resolved" are set
  * to 0 and @code NULL@, respectively, then the conflict cannot be resolved.
  *
- * @since CUPS 1.6/OS X 10.8@
+ * @since CUPS 1.6/macOS 10.8@
  */
 
 int                                    /* O - 1 if there is a conflict, 0 if none, -1 on error */
@@ -328,7 +394,7 @@ cupsCopyDestConflicts(
                *myres = NULL,          /* My resolved options */
                *myoption,              /* My current option */
                *option;                /* Current option */
-  cups_array_t *active,                /* Active conflicts */
+  cups_array_t *active = NULL,         /* Active conflicts */
                *pass = NULL,           /* Resolvers for this pass */
                *resolvers = NULL,      /* Resolvers we have used */
                *test;                  /* Test array for conflicts */
@@ -355,6 +421,13 @@ cupsCopyDestConflicts(
   if (resolved)
     *resolved = NULL;
 
+ /*
+  * Get the default connection as needed...
+  */
+
+  if (!http)
+    http = _cupsConnect();
+
  /*
   * Range check input...
   */
@@ -586,7 +659,7 @@ cupsCopyDestConflicts(
  * The caller is responsible for calling @link cupsFreeDestInfo@ on the return
  * value. @code NULL@ is returned on error.
  *
- * @since CUPS 1.6/OS X 10.8@
+ * @since CUPS 1.6/macOS 10.8@
  */
 
 cups_dinfo_t *                         /* O - Destination information */
@@ -595,6 +668,7 @@ cupsCopyDestInfo(
     cups_dest_t *dest)                 /* I - Destination */
 {
   cups_dinfo_t *dinfo;                 /* Destination information */
+  unsigned     dflags;                 /* Destination flags */
   ipp_t                *request,               /* Get-Printer-Attributes request */
                *response;              /* Supported attributes */
   int          tries,                  /* Number of tries so far */
@@ -604,6 +678,7 @@ cupsCopyDestInfo(
   char         resource[1024];         /* Resource path */
   int          version;                /* IPP version */
   ipp_status_t status;                 /* Status of request */
+  _cups_globals_t *cg = _cupsGlobals();        /* Pointer to library globals */
   static const char * const requested_attrs[] =
   {                                    /* Requested attributes */
     "job-template",
@@ -612,8 +687,35 @@ cupsCopyDestInfo(
   };
 
 
-  DEBUG_printf(("cupsCopyDestSupported(http=%p, dest=%p(%s))", http, dest,
-                dest ? dest->name : ""));
+  DEBUG_printf(("cupsCopyDestInfo(http=%p, dest=%p(%s))", (void *)http, (void *)dest, dest ? dest->name : ""));
+
+ /*
+  * Get the default connection as needed...
+  */
+
+  if (!http)
+  {
+    DEBUG_puts("1cupsCopyDestInfo: Default server connection.");
+    http   = _cupsConnect();
+    dflags = CUPS_DEST_FLAGS_NONE;
+  }
+#ifdef AF_LOCAL
+  else if (httpAddrFamily(http->hostaddr) == AF_LOCAL)
+  {
+    DEBUG_puts("1cupsCopyDestInfo: Connection to server (domain socket).");
+    dflags = CUPS_DEST_FLAGS_NONE;
+  }
+#endif /* AF_LOCAL */
+  else if ((strcmp(http->hostname, cg->server) && cg->server[0] != '/') || cg->ipp_port != httpAddrPort(http->hostaddr))
+  {
+    DEBUG_printf(("1cupsCopyDestInfo: Connection to device (%s).", http->hostname));
+    dflags = CUPS_DEST_FLAGS_DEVICE;
+  }
+  else
+  {
+    DEBUG_printf(("1cupsCopyDestInfo: Connection to server (%s).", http->hostname));
+    dflags = CUPS_DEST_FLAGS_NONE;
+  }
 
  /*
   * Range check input...
@@ -626,8 +728,11 @@ cupsCopyDestInfo(
   * Get the printer URI and resource path...
   */
 
-  if ((uri = _cupsGetDestResource(dest, resource, sizeof(resource))) == NULL)
+  if ((uri = _cupsGetDestResource(dest, dflags, resource, sizeof(resource))) == NULL)
+  {
+    DEBUG_puts("1cupsCopyDestInfo: Unable to get resource.");
     return (NULL);
+  }
 
  /*
   * Get the supported attributes...
@@ -645,31 +750,28 @@ cupsCopyDestInfo(
     */
 
     request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
-    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
-                uri);
-    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
-                 "requesting-user-name", NULL, cupsUser());
-    ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
-                 "requested-attributes",
-                 (int)(sizeof(requested_attrs) / sizeof(requested_attrs[0])),
-                 NULL, requested_attrs);
+
+    ippSetVersion(request, version / 10, version % 10);
+    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
+    ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name", NULL, cupsUser());
+    ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD, "requested-attributes", (int)(sizeof(requested_attrs) / sizeof(requested_attrs[0])), NULL, requested_attrs);
     response = cupsDoRequest(http, request, resource);
     status   = cupsLastError();
 
     if (status > IPP_STATUS_OK_IGNORED_OR_SUBSTITUTED)
     {
-      DEBUG_printf(("cupsCopyDestSupported: Get-Printer-Attributes for '%s' "
-                   "returned %s (%s)", dest->name, ippErrorString(status),
-                   cupsLastErrorString()));
+      DEBUG_printf(("1cupsCopyDestInfo: Get-Printer-Attributes for '%s' returned %s (%s)", dest->name, ippErrorString(status), cupsLastErrorString()));
 
       ippDelete(response);
       response = NULL;
 
-      if (status == IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED && version > 11)
+      if ((status == IPP_STATUS_ERROR_BAD_REQUEST || status == IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED) && version > 11)
+      {
         version = 11;
+      }
       else if (status == IPP_STATUS_ERROR_BUSY)
       {
-        sleep(delay);
+        sleep((unsigned)delay);
 
         delay = _cupsNextDelay(delay, &prev_delay);
       }
@@ -682,7 +784,10 @@ cupsCopyDestInfo(
   while (!response && tries < 10);
 
   if (!response)
+  {
+    DEBUG_puts("1cupsCopyDestInfo: Unable to get printer attributes.");
     return (NULL);
+  }
 
  /*
   * Allocate a cups_dinfo_t structure and return it...
@@ -695,6 +800,8 @@ cupsCopyDestInfo(
     return (NULL);
   }
 
+  DEBUG_printf(("1cupsCopyDestInfo: version=%d, uri=\"%s\", resource=\"%s\".", version, uri, resource));
+
   dinfo->version  = version;
   dinfo->uri      = uri;
   dinfo->resource = _cupsStrAlloc(resource);
@@ -713,7 +820,7 @@ cupsCopyDestInfo(
  * @code ippGetResolution@, @code ippGetString@, and @code ippGetValueTag@
  * functions to inspect the default value(s) as needed.
  *
- * @since CUPS 1.7@
+ * @since CUPS 1.7/macOS 10.9@
  */
 
 ipp_attribute_t        *                       /* O - Default attribute or @code NULL@ for none */
@@ -726,6 +833,13 @@ cupsFindDestDefault(
   char name[IPP_MAX_NAME];             /* Attribute name */
 
 
+ /*
+  * Get the default connection as needed...
+  */
+
+  if (!http)
+    http = _cupsConnect();
+
  /*
   * Range check input...
   */
@@ -744,6 +858,7 @@ cupsFindDestDefault(
   return (ippFindAttribute(dinfo->attrs, name, IPP_TAG_ZERO));
 }
 
+
 /*
  * 'cupsFindDestReady()' - Find the default value(s) for the given option.
  *
@@ -753,7 +868,7 @@ cupsFindDestDefault(
  * @code ippGetResolution@, @code ippGetString@, and @code ippGetValueTag@
  * functions to inspect the default value(s) as needed.
  *
- * @since CUPS 1.7@
+ * @since CUPS 1.7/macOS 10.9@
  */
 
 ipp_attribute_t        *                       /* O - Default attribute or @code NULL@ for none */
@@ -766,6 +881,13 @@ cupsFindDestReady(
   char name[IPP_MAX_NAME];             /* Attribute name */
 
 
+ /*
+  * Get the default connection as needed...
+  */
+
+  if (!http)
+    http = _cupsConnect();
+
  /*
   * Range check input...
   */
@@ -786,6 +908,7 @@ cupsFindDestReady(
   return (ippFindAttribute(dinfo->ready_attrs, name, IPP_TAG_ZERO));
 }
 
+
 /*
  * 'cupsFindDestSupported()' - Find the default value(s) for the given option.
  *
@@ -795,7 +918,7 @@ cupsFindDestReady(
  * @code ippGetResolution@, @code ippGetString@, and @code ippGetValueTag@
  * functions to inspect the default value(s) as needed.
  *
- * @since CUPS 1.7@
+ * @since CUPS 1.7/macOS 10.9@
  */
 
 ipp_attribute_t        *                       /* O - Default attribute or @code NULL@ for none */
@@ -808,6 +931,13 @@ cupsFindDestSupported(
   char name[IPP_MAX_NAME];             /* Attribute name */
 
 
+ /*
+  * Get the default connection as needed...
+  */
+
+  if (!http)
+    http = _cupsConnect();
+
  /*
   * Range check input...
   */
@@ -830,6 +960,8 @@ cupsFindDestSupported(
 /*
  * 'cupsFreeDestInfo()' - Free destination information obtained using
  *                        @link cupsCopyDestInfo@.
+ *
+ * @since CUPS 1.6/macOS 10.8@
  */
 
 void
@@ -874,7 +1006,7 @@ cupsFreeDestInfo(cups_dinfo_t *dinfo)      /* I - Destination information */
  * example, passing @code CUPS_MEDIA_FLAGS_BORDERLESS@ will get the Nth
  * borderless size supported by the printer.
  *
- * @since CUPS 1.7@
+ * @since CUPS 1.7/macOS 10.9@
  */
 
 int                                    /* O - 1 on success, 0 on failure */
@@ -886,8 +1018,16 @@ cupsGetDestMediaByIndex(
     unsigned     flags,                        /* I - Media flags */
     cups_size_t  *size)                        /* O - Media size information */
 {
-  cups_size_t  *nsize;                 /* Size for N */
+  _cups_media_db_t     *nsize;         /* Size for N */
+  pwg_media_t          *pwg;           /* PWG media name for size */
+
+
+ /*
+  * Get the default connection as needed...
+  */
 
+  if (!http)
+    http = _cupsConnect();
 
  /*
   * Range check input...
@@ -916,13 +1056,30 @@ cupsGetDestMediaByIndex(
   * Copy the size over and return...
   */
 
-  if ((nsize = (cups_size_t *)cupsArrayIndex(dinfo->cached_db, n)) == NULL)
+  if ((nsize = (_cups_media_db_t *)cupsArrayIndex(dinfo->cached_db, n)) == NULL)
+  {
+    _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
+    return (0);
+  }
+
+  if (nsize->key)
+    strlcpy(size->media, nsize->key, sizeof(size->media));
+  else if (nsize->size_name)
+    strlcpy(size->media, nsize->size_name, sizeof(size->media));
+  else if ((pwg = pwgMediaForSize(nsize->width, nsize->length)) != NULL)
+    strlcpy(size->media, pwg->pwg, sizeof(size->media));
+  else
   {
     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
     return (0);
   }
 
-  memcpy(size, nsize, sizeof(cups_size_t));
+  size->width  = nsize->width;
+  size->length = nsize->length;
+  size->bottom = nsize->bottom;
+  size->left   = nsize->left;
+  size->right  = nsize->right;
+  size->top    = nsize->top;
 
   return (1);
 }
@@ -945,7 +1102,7 @@ cupsGetDestMediaByIndex(
  *
  * Returns 1 when there is a match and 0 if there is not a match.
  *
- * @since CUPS 1.6/OS X 10.8@
+ * @since CUPS 1.6/macOS 10.8@
  */
 
 int                                    /* O - 1 on match, 0 on failure */
@@ -960,6 +1117,13 @@ cupsGetDestMediaByName(
   pwg_media_t          *pwg;           /* PWG media info */
 
 
+ /*
+  * Get the default connection as needed...
+  */
+
+  if (!http)
+    http = _cupsConnect();
+
  /*
   * Range check input...
   */
@@ -1010,7 +1174,7 @@ cupsGetDestMediaByName(
  *
  * Returns 1 when there is a match and 0 if there is not a match.
  *
- * @since CUPS 1.6/OS X 10.8@
+ * @since CUPS 1.6/macOS 10.8@
  */
 
 int                                    /* O - 1 on match, 0 on failure */
@@ -1028,6 +1192,13 @@ cupsGetDestMediaBySize(
   pwg_media_t          *pwg;           /* PWG media info */
 
 
+ /*
+  * Get the default connection as needed...
+  */
+
+  if (!http)
+    http = _cupsConnect();
+
  /*
   * Range check input...
   */
@@ -1069,7 +1240,7 @@ cupsGetDestMediaBySize(
  * counted.  For example, passing @code CUPS_MEDIA_FLAGS_BORDERLESS@ will return
  * the number of borderless sizes.
  *
- * @since CUPS 1.7@
+ * @since CUPS 1.7/macOS 10.9@
  */
 
 int                                    /* O - Number of sizes */
@@ -1079,6 +1250,13 @@ cupsGetDestMediaCount(
     cups_dinfo_t *dinfo,               /* I - Destination information */
     unsigned     flags)                        /* I - Media flags */
 {
+ /*
+  * Get the default connection as needed...
+  */
+
+  if (!http)
+    http = _cupsConnect();
+
  /*
   * Range check input...
   */
@@ -1110,7 +1288,7 @@ cupsGetDestMediaCount(
  * example, passing @code CUPS_MEDIA_FLAGS_BORDERLESS@ will return the default
  * borderless size, typically US Letter or A4, but sometimes 4x6 photo media.
  *
- * @since CUPS 1.7@
+ * @since CUPS 1.7/macOS 10.9@
  */
 
 int                                    /* O - 1 on success, 0 on failure */
@@ -1124,6 +1302,13 @@ cupsGetDestMediaDefault(
   const char   *media;                 /* Default media size */
 
 
+ /*
+  * Get the default connection as needed...
+  */
+
+  if (!http)
+    http = _cupsConnect();
+
  /*
   * Range check input...
   */
@@ -1141,32 +1326,26 @@ cupsGetDestMediaDefault(
   * Get the default media size, if any...
   */
 
-  if ((media = cupsGetOption("media", dest->num_options,
-                             dest->options)) == NULL)
+  if ((media = cupsGetOption("media", dest->num_options, dest->options)) == NULL)
     media = "na_letter_8.5x11in";
 
   if (cupsGetDestMediaByName(http, dest, dinfo, media, flags, size))
     return (1);
 
-  if (strcmp(media, "na_letter_8.5x11in") &&
-      cupsGetDestMediaByName(http, dest, dinfo, "iso_a4_210x297mm", flags,
-                             size))
+  if (strcmp(media, "na_letter_8.5x11in") && cupsGetDestMediaByName(http, dest, dinfo, "iso_a4_210x297mm", flags, size))
     return (1);
 
-  if (strcmp(media, "iso_a4_210x297mm") &&
-      cupsGetDestMediaByName(http, dest, dinfo, "na_letter_8.5x11in", flags,
-                             size))
+  if (strcmp(media, "iso_a4_210x297mm") && cupsGetDestMediaByName(http, dest, dinfo, "na_letter_8.5x11in", flags, size))
     return (1);
 
-  if ((flags & CUPS_MEDIA_FLAGS_BORDERLESS) &&
-      cupsGetDestMediaByName(http, dest, dinfo, "na_index_4x6in", flags, size))
+  if ((flags & CUPS_MEDIA_FLAGS_BORDERLESS) && cupsGetDestMediaByName(http, dest, dinfo, "na_index_4x6in", flags, size))
     return (1);
 
  /*
   * Fall back to the first matching media size...
   */
 
-  return (cupsGetDestMediaByIndex(http, dest, dinfo, flags, 0, size));
+  return (cupsGetDestMediaByIndex(http, dest, dinfo, 0, flags, size));
 }
 
 
@@ -1197,6 +1376,346 @@ cups_add_dconstres(
 }
 
 
+/*
+ * 'cups_collection_contains()' - Check whether test collection is contained in the matching collection.
+ */
+
+static int                             /* O - 1 on a match, 0 on a non-match */
+cups_collection_contains(ipp_t *test,  /* I - Collection to test */
+                         ipp_t *match) /* I - Matching values */
+{
+  int                  i, j,           /* Looping vars */
+                       mcount,         /* Number of match values */
+                       tcount;         /* Number of test values */
+  ipp_attribute_t      *tattr,         /* Testing attribute */
+                       *mattr;         /* Matching attribute */
+  const char           *tval;          /* Testing string value */
+
+
+  for (mattr = ippFirstAttribute(match); mattr; mattr = ippNextAttribute(match))
+  {
+    if ((tattr = ippFindAttribute(test, ippGetName(mattr), IPP_TAG_ZERO)) == NULL)
+      return (0);
+
+    tcount = ippGetCount(tattr);
+
+    switch (ippGetValueTag(mattr))
+    {
+      case IPP_TAG_INTEGER :
+      case IPP_TAG_ENUM :
+          if (ippGetValueTag(tattr) != ippGetValueTag(mattr))
+            return (0);
+
+          for (i = 0; i < tcount; i ++)
+          {
+            if (!ippContainsInteger(mattr, ippGetInteger(tattr, i)))
+              return (0);
+          }
+          break;
+
+      case IPP_TAG_RANGE :
+          if (ippGetValueTag(tattr) != IPP_TAG_INTEGER)
+            return (0);
+
+          for (i = 0; i < tcount; i ++)
+          {
+            if (!ippContainsInteger(mattr, ippGetInteger(tattr, i)))
+              return (0);
+          }
+          break;
+
+      case IPP_TAG_BOOLEAN :
+          if (ippGetValueTag(tattr) != IPP_TAG_BOOLEAN || ippGetBoolean(tattr, 0) != ippGetBoolean(mattr, 0))
+            return (0);
+          break;
+
+      case IPP_TAG_TEXTLANG :
+      case IPP_TAG_NAMELANG :
+      case IPP_TAG_TEXT :
+      case IPP_TAG_NAME :
+      case IPP_TAG_KEYWORD :
+      case IPP_TAG_URI :
+      case IPP_TAG_URISCHEME :
+      case IPP_TAG_CHARSET :
+      case IPP_TAG_LANGUAGE :
+      case IPP_TAG_MIMETYPE :
+          for (i = 0; i < tcount; i ++)
+          {
+            if ((tval = ippGetString(tattr, i, NULL)) == NULL || !ippContainsString(mattr, tval))
+              return (0);
+          }
+          break;
+
+      case IPP_TAG_BEGIN_COLLECTION :
+          for (i = 0; i < tcount; i ++)
+          {
+            ipp_t *tcol = ippGetCollection(tattr, i);
+                                       /* Testing collection */
+
+            for (j = 0, mcount = ippGetCount(mattr); j < mcount; j ++)
+              if (!cups_collection_contains(tcol, ippGetCollection(mattr, j)))
+                return (0);
+          }
+          break;
+
+      default :
+          return (0);
+    }
+  }
+
+  return (1);
+}
+
+
+/*
+ * 'cups_collection_string()' - Convert an IPP collection to an option string.
+ */
+
+static size_t                          /* O - Number of bytes needed */
+cups_collection_string(
+    ipp_attribute_t *attr,             /* I - Collection attribute */
+    char            *buffer,           /* I - String buffer */
+    size_t          bufsize)           /* I - Size of buffer */
+{
+  int                  i, j,           /* Looping vars */
+                       count,          /* Number of collection values */
+                       mcount;         /* Number of member values */
+  ipp_t                        *col;           /* Collection */
+  ipp_attribute_t      *first,         /* First member attribute */
+                       *member;        /* Member attribute */
+  char                 *bufptr,        /* Pointer into buffer */
+                       *bufend,        /* End of buffer */
+                       temp[100];      /* Temporary string */
+  const char           *mptr;          /* Pointer into member value */
+  int                  mlen;           /* Length of octetString */
+
+
+  bufptr = buffer;
+  bufend = buffer + bufsize - 1;
+
+  for (i = 0, count = ippGetCount(attr); i < count; i ++)
+  {
+    col = ippGetCollection(attr, i);
+
+    if (i)
+    {
+      if (bufptr < bufend)
+        *bufptr++ = ',';
+      else
+        bufptr ++;
+    }
+
+    if (bufptr < bufend)
+      *bufptr++ = '{';
+    else
+      bufptr ++;
+
+    for (member = first = ippFirstAttribute(col); member; member = ippNextAttribute(col))
+    {
+      const char *mname = ippGetName(member);
+
+      if (member != first)
+      {
+       if (bufptr < bufend)
+         *bufptr++ = ' ';
+       else
+         bufptr ++;
+      }
+
+      if (ippGetValueTag(member) == IPP_TAG_BOOLEAN)
+      {
+        if (!ippGetBoolean(member, 0))
+        {
+         if (bufptr < bufend)
+           strlcpy(bufptr, "no", (size_t)(bufend - bufptr + 1));
+         bufptr += 2;
+        }
+
+       if (bufptr < bufend)
+         strlcpy(bufptr, mname, (size_t)(bufend - bufptr + 1));
+       bufptr += strlen(mname);
+        continue;
+      }
+
+      if (bufptr < bufend)
+        strlcpy(bufptr, mname, (size_t)(bufend - bufptr + 1));
+      bufptr += strlen(mname);
+
+      if (bufptr < bufend)
+        *bufptr++ = '=';
+      else
+        bufptr ++;
+
+      if (ippGetValueTag(member) == IPP_TAG_BEGIN_COLLECTION)
+      {
+       /*
+       * Convert sub-collection...
+       */
+
+       bufptr += cups_collection_string(member, bufptr, bufptr < bufend ? (size_t)(bufend - bufptr + 1) : 0);
+      }
+      else
+      {
+       /*
+        * Convert simple type...
+        */
+
+       for (j = 0, mcount = ippGetCount(member); j < mcount; j ++)
+       {
+         if (j)
+         {
+           if (bufptr < bufend)
+             *bufptr++ = ',';
+           else
+             bufptr ++;
+         }
+
+          switch (ippGetValueTag(member))
+          {
+            case IPP_TAG_INTEGER :
+            case IPP_TAG_ENUM :
+                bufptr += snprintf(bufptr, bufptr < bufend ? (size_t)(bufend - bufptr + 1) : 0, "%d", ippGetInteger(member, j));
+                break;
+
+           case IPP_TAG_STRING :
+               if (bufptr < bufend)
+                 *bufptr++ = '\"';
+               else
+                 bufptr ++;
+
+               for (mptr = (const char *)ippGetOctetString(member, j, &mlen); mlen > 0; mlen --, mptr ++)
+               {
+                 if (*mptr == '\"' || *mptr == '\\')
+                 {
+                   if (bufptr < bufend)
+                     *bufptr++ = '\\';
+                   else
+                     bufptr ++;
+                 }
+
+                 if (bufptr < bufend)
+                   *bufptr++ = *mptr;
+                 else
+                   bufptr ++;
+                }
+
+               if (bufptr < bufend)
+                 *bufptr++ = '\"';
+               else
+                 bufptr ++;
+               break;
+
+            case IPP_TAG_DATE :
+               {
+                 unsigned year;        /* Year */
+                 const ipp_uchar_t *date = ippGetDate(member, j);
+                                       /* Date value */
+
+                 year = ((unsigned)date[0] << 8) + (unsigned)date[1];
+
+                 if (date[9] == 0 && date[10] == 0)
+                   snprintf(temp, sizeof(temp), "%04u-%02u-%02uT%02u:%02u:%02uZ", year, date[2], date[3], date[4], date[5], date[6]);
+                 else
+                   snprintf(temp, sizeof(temp), "%04u-%02u-%02uT%02u:%02u:%02u%c%02u%02u", year, date[2], date[3], date[4], date[5], date[6], date[8], date[9], date[10]);
+
+                 if (buffer && bufptr < bufend)
+                   strlcpy(bufptr, temp, (size_t)(bufend - bufptr + 1));
+
+                 bufptr += strlen(temp);
+               }
+                break;
+
+            case IPP_TAG_RESOLUTION :
+                {
+                  int          xres,   /* Horizontal resolution */
+                               yres;   /* Vertical resolution */
+                  ipp_res_t    units;  /* Resolution units */
+
+                  xres = ippGetResolution(member, j, &yres, &units);
+
+                  if (xres == yres)
+                    snprintf(temp, sizeof(temp), "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+                 else
+                    snprintf(temp, sizeof(temp), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+
+                 if (buffer && bufptr < bufend)
+                   strlcpy(bufptr, temp, (size_t)(bufend - bufptr + 1));
+
+                 bufptr += strlen(temp);
+                }
+                break;
+
+            case IPP_TAG_RANGE :
+                {
+                  int          lower,  /* Lower bound */
+                               upper;  /* Upper bound */
+
+                  lower = ippGetRange(member, j, &upper);
+
+                 snprintf(temp, sizeof(temp), "%d-%d", lower, upper);
+
+                 if (buffer && bufptr < bufend)
+                   strlcpy(bufptr, temp, (size_t)(bufend - bufptr + 1));
+
+                 bufptr += strlen(temp);
+                }
+                break;
+
+            case IPP_TAG_TEXTLANG :
+            case IPP_TAG_NAMELANG :
+            case IPP_TAG_TEXT :
+            case IPP_TAG_NAME :
+            case IPP_TAG_KEYWORD :
+            case IPP_TAG_URI :
+            case IPP_TAG_URISCHEME :
+            case IPP_TAG_CHARSET :
+            case IPP_TAG_LANGUAGE :
+            case IPP_TAG_MIMETYPE :
+               if (bufptr < bufend)
+                 *bufptr++ = '\"';
+               else
+                 bufptr ++;
+
+               for (mptr = ippGetString(member, j, NULL); *mptr; mptr ++)
+               {
+                 if (*mptr == '\"' || *mptr == '\\')
+                 {
+                   if (bufptr < bufend)
+                     *bufptr++ = '\\';
+                   else
+                     bufptr ++;
+                 }
+
+                 if (bufptr < bufend)
+                   *bufptr++ = *mptr;
+                 else
+                   bufptr ++;
+                }
+
+               if (bufptr < bufend)
+                 *bufptr++ = '\"';
+               else
+                 bufptr ++;
+                break;
+
+            default :
+                break;
+          }
+       }
+      }
+    }
+
+    if (bufptr < bufend)
+      *bufptr++ = '}';
+    else
+      bufptr ++;
+  }
+
+  *bufptr = '\0';
+  return ((size_t)(bufptr - buffer + 1));
+}
+
+
 /*
  * 'cups_compare_dconstres()' - Compare to resolver entries.
  */
@@ -1281,6 +1800,8 @@ cups_create_cached(http_t       *http,    /* I - Connection to destination */
                        *first;         /* First entry this size */
 
 
+  DEBUG_printf(("3cups_create_cached(http=%p, dinfo=%p, flags=%u)", (void *)http, (void *)dinfo, flags));
+
   if (dinfo->cached_db)
     cupsArrayDelete(dinfo->cached_db);
 
@@ -1289,11 +1810,15 @@ cups_create_cached(http_t       *http,  /* I - Connection to destination */
 
   if (flags & CUPS_MEDIA_FLAGS_READY)
   {
+    DEBUG_puts("4cups_create_cached: ready media");
+
     cups_update_ready(http, dinfo);
     db = dinfo->ready_db;
   }
   else
   {
+    DEBUG_puts("4cups_create_cached: supported media");
+
     if (!dinfo->media_db)
       cups_create_media_db(dinfo, CUPS_MEDIA_FLAGS_DEFAULT);
 
@@ -1304,26 +1829,40 @@ cups_create_cached(http_t       *http,  /* I - Connection to destination */
        mdb;
        mdb = (_cups_media_db_t *)cupsArrayNext(db))
   {
+    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));
+
     if (flags & CUPS_MEDIA_FLAGS_BORDERLESS)
     {
       if (!mdb->left && !mdb->right && !mdb->top && !mdb->bottom)
+      {
+        DEBUG_printf(("4cups_create_cached: add %p", (void *)mdb));
         cupsArrayAdd(dinfo->cached_db, mdb);
+      }
     }
     else if (flags & CUPS_MEDIA_FLAGS_DUPLEX)
     {
       if (first->width != mdb->width || first->length != mdb->length)
       {
+       DEBUG_printf(("4cups_create_cached: add %p", (void *)first));
         cupsArrayAdd(dinfo->cached_db, first);
         first = mdb;
       }
-      else if (mdb->left >= first->left && mdb->right >= first->right &&
-               mdb->top >= first->top && mdb->bottom >= first->bottom)
+      else if (mdb->left >= first->left && mdb->right >= first->right && mdb->top >= first->top && mdb->bottom >= first->bottom &&
+              (mdb->left != first->left || mdb->right != first->right || mdb->top != first->top || mdb->bottom != first->bottom))
         first = mdb;
     }
+    else
+    {
+      DEBUG_printf(("4cups_create_cached: add %p", (void *)mdb));
+      cupsArrayAdd(dinfo->cached_db, mdb);
+    }
   }
 
   if (flags & CUPS_MEDIA_FLAGS_DUPLEX)
+  {
+    DEBUG_printf(("4cups_create_cached: add %p", (void *)first));
     cupsArrayAdd(dinfo->cached_db, first);
+  }
 }
 
 
@@ -1364,8 +1903,6 @@ cups_create_constraints(
 
 /*
  * 'cups_create_defaults()' - Create the -default option array.
- *
- * TODO: Need to support collection defaults...
  */
 
 static void
@@ -1384,32 +1921,26 @@ cups_create_defaults(
   * xxx=value to the defaults option array.
   */
 
-  for (attr = ippFirstAttribute(dinfo->attrs);
-       attr;
-       attr = ippNextAttribute(dinfo->attrs))
+  for (attr = ippFirstAttribute(dinfo->attrs); attr; attr = ippNextAttribute(dinfo->attrs))
   {
-    if (!attr->name || attr->group_tag != IPP_TAG_PRINTER)
-      continue;
-
-    if (attr->value_tag == IPP_TAG_BEGIN_COLLECTION)
-      continue;                                /* TODO: STR #4096 */
-
-    if ((nameptr = attr->name + strlen(attr->name) - 8) <= attr->name ||
-        strcmp(nameptr, "-default"))
+    if (!ippGetName(attr) || ippGetGroupTag(attr) != IPP_TAG_PRINTER)
       continue;
 
-    strlcpy(name, attr->name, sizeof(name));
-    if ((nameptr = name + strlen(name) - 8) <= name ||
-        strcmp(nameptr, "-default"))
+    strlcpy(name, ippGetName(attr), sizeof(name));
+    if ((nameptr = name + strlen(name) - 8) <= name || strcmp(nameptr, "-default"))
       continue;
 
     *nameptr = '\0';
 
-    if (ippAttributeString(attr, value, sizeof(value)) >= sizeof(value))
+    if (ippGetValueTag(attr) == IPP_TAG_BEGIN_COLLECTION)
+    {
+      if (cups_collection_string(attr, value, sizeof(value)) >= sizeof(value))
+        continue;
+    }
+    else if (ippAttributeString(attr, value, sizeof(value)) >= sizeof(value))
       continue;
 
-    dinfo->num_defaults = cupsAddOption(name, value, dinfo->num_defaults,
-                                        &dinfo->defaults);
+    dinfo->num_defaults = cupsAddOption(name, value, dinfo->num_defaults, &dinfo->defaults);
   }
 }
 
@@ -1432,6 +1963,7 @@ cups_create_media_db(
   pwg_media_t          *pwg;           /* PWG media info */
   cups_array_t         *db;            /* New media database array */
   _cups_media_db_t     mdb;            /* Media entry */
+  char                 media_key[256]; /* Synthesized media-key value */
 
 
   db = cupsArrayNew3((cups_array_func_t)cups_compare_media_db,
@@ -1532,61 +2064,92 @@ cups_create_media_db(
        }
       }
 
-      if ((media_attr = ippFindAttribute(val->collection, "media-color",
-                                         IPP_TAG_ZERO)) != NULL &&
-          (media_attr->value_tag == IPP_TAG_NAME ||
-           media_attr->value_tag == IPP_TAG_NAMELANG ||
-           media_attr->value_tag == IPP_TAG_KEYWORD))
+      if ((media_attr = ippFindAttribute(val->collection, "media-color", IPP_TAG_ZERO)) != NULL && (media_attr->value_tag == IPP_TAG_NAME || media_attr->value_tag == IPP_TAG_NAMELANG || media_attr->value_tag == IPP_TAG_KEYWORD))
         mdb.color = media_attr->values[0].string.text;
 
-      if ((media_attr = ippFindAttribute(val->collection, "media-info",
-                                         IPP_TAG_TEXT)) != NULL)
+      if ((media_attr = ippFindAttribute(val->collection, "media-info", IPP_TAG_TEXT)) != NULL)
         mdb.info = media_attr->values[0].string.text;
 
-      if ((media_attr = ippFindAttribute(val->collection, "media-key",
-                                         IPP_TAG_ZERO)) != NULL &&
-          (media_attr->value_tag == IPP_TAG_NAME ||
-           media_attr->value_tag == IPP_TAG_NAMELANG ||
-           media_attr->value_tag == IPP_TAG_KEYWORD))
+      if ((media_attr = ippFindAttribute(val->collection, "media-key", IPP_TAG_ZERO)) != NULL && (media_attr->value_tag == IPP_TAG_NAME || media_attr->value_tag == IPP_TAG_NAMELANG || media_attr->value_tag == IPP_TAG_KEYWORD))
         mdb.key = media_attr->values[0].string.text;
 
-      if ((media_attr = ippFindAttribute(val->collection, "media-size-name",
-                                         IPP_TAG_ZERO)) != NULL &&
-          (media_attr->value_tag == IPP_TAG_NAME ||
-           media_attr->value_tag == IPP_TAG_NAMELANG ||
-           media_attr->value_tag == IPP_TAG_KEYWORD))
+      if ((media_attr = ippFindAttribute(val->collection, "media-size-name", IPP_TAG_ZERO)) != NULL && (media_attr->value_tag == IPP_TAG_NAME || media_attr->value_tag == IPP_TAG_NAMELANG || media_attr->value_tag == IPP_TAG_KEYWORD))
         mdb.size_name = media_attr->values[0].string.text;
 
-      if ((media_attr = ippFindAttribute(val->collection, "media-source",
-                                         IPP_TAG_ZERO)) != NULL &&
-          (media_attr->value_tag == IPP_TAG_NAME ||
-           media_attr->value_tag == IPP_TAG_NAMELANG ||
-           media_attr->value_tag == IPP_TAG_KEYWORD))
+      if ((media_attr = ippFindAttribute(val->collection, "media-source", IPP_TAG_ZERO)) != NULL && (media_attr->value_tag == IPP_TAG_NAME || media_attr->value_tag == IPP_TAG_NAMELANG || media_attr->value_tag == IPP_TAG_KEYWORD))
         mdb.source = media_attr->values[0].string.text;
 
-      if ((media_attr = ippFindAttribute(val->collection, "media-type",
-                                         IPP_TAG_ZERO)) != NULL &&
-          (media_attr->value_tag == IPP_TAG_NAME ||
-           media_attr->value_tag == IPP_TAG_NAMELANG ||
-           media_attr->value_tag == IPP_TAG_KEYWORD))
+      if ((media_attr = ippFindAttribute(val->collection, "media-type", IPP_TAG_ZERO)) != NULL && (media_attr->value_tag == IPP_TAG_NAME || media_attr->value_tag == IPP_TAG_NAMELANG || media_attr->value_tag == IPP_TAG_KEYWORD))
         mdb.type = media_attr->values[0].string.text;
 
-      if ((media_attr = ippFindAttribute(val->collection, "media-bottom-margin",
-                                         IPP_TAG_INTEGER)) != NULL)
+      if ((media_attr = ippFindAttribute(val->collection, "media-bottom-margin", IPP_TAG_INTEGER)) != NULL)
         mdb.bottom = media_attr->values[0].integer;
 
-      if ((media_attr = ippFindAttribute(val->collection, "media-left-margin",
-                                         IPP_TAG_INTEGER)) != NULL)
+      if ((media_attr = ippFindAttribute(val->collection, "media-left-margin", IPP_TAG_INTEGER)) != NULL)
         mdb.left = media_attr->values[0].integer;
 
-      if ((media_attr = ippFindAttribute(val->collection, "media-right-margin",
-                                         IPP_TAG_INTEGER)) != NULL)
+      if ((media_attr = ippFindAttribute(val->collection, "media-right-margin", IPP_TAG_INTEGER)) != NULL)
         mdb.right = media_attr->values[0].integer;
 
-      if ((media_attr = ippFindAttribute(val->collection, "media-top-margin",
-                                         IPP_TAG_INTEGER)) != NULL)
+      if ((media_attr = ippFindAttribute(val->collection, "media-top-margin", IPP_TAG_INTEGER)) != NULL)
         mdb.top = media_attr->values[0].integer;
 
+      if (!mdb.key)
+      {
+        if (!mdb.size_name && (pwg = pwgMediaForSize(mdb.width, mdb.length)) != NULL)
+         mdb.size_name = (char *)pwg->pwg;
+
+        if (!mdb.size_name)
+        {
+         /*
+          * Use a CUPS-specific identifier if we don't have a size name...
+          */
+
+         if (flags & CUPS_MEDIA_FLAGS_READY)
+           snprintf(media_key, sizeof(media_key), "cups-media-ready-%d", i + 1);
+         else
+           snprintf(media_key, sizeof(media_key), "cups-media-%d", i + 1);
+        }
+        else if (mdb.source)
+        {
+         /*
+          * Generate key using size name, source, and type (if set)...
+          */
+
+          if (mdb.type)
+            snprintf(media_key, sizeof(media_key), "%s_%s_%s", mdb.size_name, mdb.source, mdb.type);
+         else
+            snprintf(media_key, sizeof(media_key), "%s_%s", mdb.size_name, mdb.source);
+        }
+        else if (mdb.type)
+        {
+         /*
+          * Generate key using size name and type...
+          */
+
+         snprintf(media_key, sizeof(media_key), "%s_%s", mdb.size_name, mdb.type);
+        }
+        else
+        {
+         /*
+          * Key is just the size name...
+          */
+
+          strlcpy(media_key, mdb.size_name, sizeof(media_key));
+        }
+
+       /*
+        * Append "_borderless" for borderless media...
+        */
+
+        if (!mdb.bottom && !mdb.left && !mdb.right && !mdb.top)
+          strlcat(media_key, "_borderless", sizeof(media_key));
+
+        mdb.key = media_key;
+      }
+
+      DEBUG_printf(("1cups_create_media_db: Adding media: key=\"%s\", width=%d, length=%d, source=\"%s\", type=\"%s\".", mdb.key, mdb.width, mdb.length, mdb.source, mdb.type));
+
       cupsArrayAdd(db, &mdb);
     }
 
@@ -1757,8 +2320,7 @@ cups_get_media_db(http_t       *http,     /* I - Connection to destination */
       * Look for the smallest margins...
       */
 
-      if (best->left != 0 || best->right != 0 || best->top != 0 ||
-          best->bottom != 0)
+      if (best->left != 0 || best->right != 0 || best->top != 0 || best->bottom != 0)
       {
        for (mdb = (_cups_media_db_t *)cupsArrayNext(db);
             mdb && !cups_compare_media_db(mdb, &key);
@@ -1795,7 +2357,8 @@ cups_get_media_db(http_t       *http,     /* I - Connection to destination */
           mdb = (_cups_media_db_t *)cupsArrayNext(db))
       {
        if (mdb->left >= best->left && mdb->right >= best->right &&
-           mdb->top >= best->top && mdb->bottom >= best->bottom)
+           mdb->top >= best->top && mdb->bottom >= best->bottom &&
+           (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
          best = mdb;
       }
     }
@@ -1810,11 +2373,10 @@ cups_get_media_db(http_t       *http,   /* I - Connection to destination */
           mdb = (_cups_media_db_t *)cupsArrayNext(db))
       {
        if (((mdb->left > 0 && mdb->left <= best->left) || best->left == 0) &&
-           ((mdb->right > 0 && mdb->right <= best->right) ||
-            best->right == 0) &&
+           ((mdb->right > 0 && mdb->right <= best->right) || best->right == 0) &&
            ((mdb->top > 0 && mdb->top <= best->top) || best->top == 0) &&
-           ((mdb->bottom > 0 && mdb->bottom <= best->bottom) ||
-            best->bottom == 0))
+           ((mdb->bottom > 0 && mdb->bottom <= best->bottom) || best->bottom == 0) &&
+           (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
          best = mdb;
       }
     }
@@ -1892,7 +2454,8 @@ cups_get_media_db(http_t       *http,     /* I - Connection to destination */
             mdb = (_cups_media_db_t *)cupsArrayNext(db))
        {
          if (mdb->left <= best->left && mdb->right <= best->right &&
-             mdb->top <= best->top && mdb->bottom <= best->bottom)
+             mdb->top <= best->top && mdb->bottom <= best->bottom &&
+             (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
          {
            best = mdb;
            if (mdb->left == 0 && mdb->right == 0 && mdb->bottom == 0 &&
@@ -1913,7 +2476,8 @@ cups_get_media_db(http_t       *http,     /* I - Connection to destination */
           mdb = (_cups_media_db_t *)cupsArrayNext(db))
       {
        if (mdb->left >= best->left && mdb->right >= best->right &&
-           mdb->top >= best->top && mdb->bottom >= best->bottom)
+           mdb->top >= best->top && mdb->bottom >= best->bottom &&
+           (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
          best = mdb;
       }
     }
@@ -1932,7 +2496,8 @@ cups_get_media_db(http_t       *http,     /* I - Connection to destination */
             best->right == 0) &&
            ((mdb->top > 0 && mdb->top <= best->top) || best->top == 0) &&
            ((mdb->bottom > 0 && mdb->bottom <= best->bottom) ||
-            best->bottom == 0))
+            best->bottom == 0) &&
+           (mdb->bottom != best->bottom || mdb->left != best->left || mdb->right != best->right || mdb->top != best->top))
          best = mdb;
       }
     }
@@ -1944,10 +2509,10 @@ cups_get_media_db(http_t       *http,   /* I - Connection to destination */
     * Return the matching size...
     */
 
-    if (best->size_name)
-      strlcpy(size->media, best->size_name, sizeof(size->media));
-    else if (best->key)
+    if (best->key)
       strlcpy(size->media, best->key, sizeof(size->media));
+    else if (best->size_name)
+      strlcpy(size->media, best->size_name, sizeof(size->media));
     else
       strlcpy(size->media, pwg->pwg, sizeof(size->media));
 
@@ -1991,8 +2556,6 @@ cups_is_close_media_db(
 
 /*
  * 'cups_test_constraints()' - Test constraints.
- *
- * TODO: STR #4096 - Need to properly support media-col contraints...
  */
 
 static cups_array_t *                  /* O - Active constraints */
@@ -2006,11 +2569,13 @@ cups_test_constraints(
     cups_option_t **conflicts)         /* O - Conflicting options */
 {
   int                  i,              /* Looping var */
+                       count,          /* Number of values */
                        match;          /* Value matches? */
   int                  num_matching;   /* Number of matching options */
   cups_option_t                *matching;      /* Matching options */
   _cups_dconstres_t    *c;             /* Current constraint */
   cups_array_t         *active = NULL; /* Active constraints */
+  ipp_t                        *col;           /* Collection value */
   ipp_attribute_t      *attr;          /* Current attribute */
   _ipp_value_t         *attrval;       /* Current attribute value */
   const char           *value;         /* Current value */
@@ -2032,17 +2597,13 @@ cups_test_constraints(
          attr;
          attr = ippNextAttribute(c->collection))
     {
-      if (attr->value_tag == IPP_TAG_BEGIN_COLLECTION)
-        break;                         /* TODO: STR #4096 */
-
      /*
       * Get the value for the current attribute in the constraint...
       */
 
       if (new_option && new_value && !strcmp(attr->name, new_option))
         value = new_value;
-      else if ((value = cupsGetOption(attr->name, num_options,
-                                      options)) == NULL)
+      else if ((value = cupsGetOption(attr->name, num_options, options)) == NULL)
         value = cupsGetOption(attr->name, dinfo->num_defaults, dinfo->defaults);
 
       if (!value)
@@ -2157,6 +2718,22 @@ cups_test_constraints(
             }
            break;
 
+        case IPP_TAG_BEGIN_COLLECTION :
+            col = ippNew();
+            _cupsEncodeOption(col, IPP_TAG_ZERO, NULL, ippGetName(attr), value);
+
+            for (i = 0, count = ippGetCount(attr); i < count; i ++)
+            {
+              if (cups_collection_contains(col, ippGetCollection(attr, i)))
+              {
+                match = 1;
+                break;
+             }
+            }
+
+            ippDelete(col);
+            break;
+
         default :
             break;
       }
@@ -2179,8 +2756,7 @@ cups_test_constraints(
         cups_option_t  *moption;       /* Matching option */
 
         for (i = num_matching, moption = matching; i > 0; i --, moption ++)
-          *num_conflicts = cupsAddOption(moption->name, moption->value,
-                                        *num_conflicts, conflicts);
+          *num_conflicts = cupsAddOption(moption->name, moption->value, *num_conflicts, conflicts);
       }
     }
 
@@ -2246,9 +2822,7 @@ cups_update_ready(http_t       *http,     /* I - Connection to destination */
                dinfo->uri);
   ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
                NULL, cupsUser());
-  ippAddStrings(request, IPP_TAG_OPERATION,
-                IPP_TAG_KEYWORD | IPP_TAG_CUPS_CONST, "requested-attributes",
-                (int)(sizeof(pattrs) / sizeof(pattrs[0])), NULL, pattrs);
+  ippAddStrings(request, IPP_TAG_OPERATION, IPP_CONST_TAG(IPP_TAG_KEYWORD), "requested-attributes", (int)(sizeof(pattrs) / sizeof(pattrs[0])), NULL, pattrs);
 
   dinfo->ready_attrs = cupsDoRequest(http, request, dinfo->resource);
 
@@ -2264,8 +2838,3 @@ cups_update_ready(http_t       *http,     /* I - Connection to destination */
 
   dinfo->ready_time = time(NULL);
 }
-
-
-/*
- * End of "$Id$".
- */