]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - test/ippserver.c
Remove all of the Subversion keywords from various source files.
[thirdparty/cups.git] / test / ippserver.c
index 05aabe44f4e3f48c8115a50292d7c878d7916ad4..8f58c9a712b3d5421ae7b4e42428c4fa4b1c067b 100644 (file)
@@ -1,9 +1,7 @@
 /*
- * "$Id$"
- *
  * Sample IPP Everywhere server for CUPS.
  *
- * Copyright 2010-2014 by Apple Inc.
+ * Copyright 2010-2015 by Apple Inc.
  *
  * These coded instructions, statements, and computer programs are the
  * property of Apple Inc. and are protected by Federal copyright
@@ -108,6 +106,26 @@ enum _ipp_preason_e                        /* printer-state-reasons bit values */
   _IPP_PREASON_TONER_LOW = 0x8000      /* toner-low */
 };
 typedef unsigned int _ipp_preason_t;   /* Bitfield for printer-state-reasons */
+static const char * const _ipp_preason_strings[] =
+{                                      /* Strings for each bit */
+  /* "none" is implied for no bits set */
+  "other",
+  "cover-open",
+  "input-tray-missing",
+  "marker-supply-empty",
+  "marker-supply-low",
+  "marker-waste-almost-full",
+  "marker-waste-full",
+  "media-empty",
+  "media-jam",
+  "media-low",
+  "media-needed",
+  "moving-to-paused",
+  "paused",
+  "spool-area-full",
+  "toner-empty",
+  "toner-low"
+};
 
 typedef enum _ipp_media_class_e
 {
@@ -231,6 +249,16 @@ static const char * const printer_supplies[] =
   "Toner Waste"
 };
 
+/*
+ * URL scheme for web resources...
+ */
+
+#ifdef HAVE_SSL
+#  define WEB_SCHEME "https"
+#else
+#  define WEB_SCHEME "http"
+#endif /* HAVE_SSL */
+
 
 /*
  * Structures...
@@ -354,14 +382,15 @@ static _ipp_printer_t     *create_printer(const char *servername,
                                        int ppm_color, int duplex, int port,
                                        int pin, const char *subtype,
                                        const char *directory,
-                                       const char *command);
+                                       const char *command,
+                                       const char *attrfile);
 static void            debug_attributes(const char *title, ipp_t *ipp,
                                         int response);
 static void            delete_client(_ipp_client_t *client);
 static void            delete_job(_ipp_job_t *job);
 static void            delete_printer(_ipp_printer_t *printer);
 #ifdef HAVE_DNSSD
-static void            dnssd_callback(DNSServiceRef sdRef,
+static void DNSSD_API  dnssd_callback(DNSServiceRef sdRef,
                                       DNSServiceFlags flags,
                                       DNSServiceErrorType errorCode,
                                       const char *name,
@@ -375,6 +404,8 @@ static void         dnssd_client_cb(AvahiClient *c, AvahiClientState state, void *userd
 static void            dnssd_init(void);
 static int             filter_cb(_ipp_filter_t *filter, ipp_t *dst, ipp_attribute_t *attr);
 static _ipp_job_t      *find_job(_ipp_client_t *client);
+static ipp_t           *get_collection(FILE *fp, const char *filename, int *linenum);
+static char            *get_token(FILE *fp, char *buf, int buflen, int *linenum);
 static void            html_escape(_ipp_client_t *client, const char *s,
                                    size_t slen);
 static void            html_footer(_ipp_client_t *client);
@@ -394,11 +425,14 @@ static void               ipp_print_uri(_ipp_client_t *client);
 static void            ipp_send_document(_ipp_client_t *client);
 static void            ipp_send_uri(_ipp_client_t *client);
 static void            ipp_validate_job(_ipp_client_t *client);
+static void            load_attributes(const char *filename, ipp_t *attrs);
 static int             parse_options(_ipp_client_t *client, cups_option_t **options);
+static void            process_attr_message(_ipp_job_t *job, char *message);
 static void            *process_client(_ipp_client_t *client);
 static int             process_http(_ipp_client_t *client);
 static int             process_ipp(_ipp_client_t *client);
 static void            *process_job(_ipp_job_t *job);
+static void            process_state_message(_ipp_job_t *job, char *message);
 static int             register_printer(_ipp_printer_t *printer, const char *location, const char *make, const char *model, const char *formats, const char *adminurl, const char *uuid, int color, int duplex, const char *regtype);
 static int             respond_http(_ipp_client_t *client, http_status_t code,
                                     const char *content_coding,
@@ -419,12 +453,12 @@ static int                valid_job_attributes(_ipp_client_t *client);
  * Globals...
  */
 
-#  ifdef HAVE_DNSSD
+#ifdef HAVE_DNSSD
 static DNSServiceRef   DNSSDMaster = NULL;
-#  else /* HAVE_AVAHI */
+#elif defined(HAVE_AVAHI)
 static AvahiThreadedPoll *DNSSDMaster = NULL;
 static AvahiClient     *DNSSDClient = NULL;
-#  endif /* HAVE_DNSSD */
+#endif /* HAVE_DNSSD */
 
 static int             KeepFiles = 0,
                        Verbosity = 0;
@@ -440,6 +474,7 @@ main(int  argc,                             /* I - Number of command-line args */
 {
   int          i;                      /* Looping var */
   const char   *opt,                   /* Current option character */
+               *attrfile = NULL,       /* Attributes file */
                *command = NULL,        /* Command to run with job files */
                *servername = NULL,     /* Server host name */
                *name = NULL,           /* Printer name */
@@ -498,6 +533,14 @@ main(int  argc,                            /* I - Number of command-line args */
               pin = 1;
               break;
 
+         case 'a' : /* -a attributes-file */
+             i ++;
+             if (i >= argc)
+               usage(1);
+
+             attrfile = argv[i];
+             break;
+
           case 'c' : /* -c command */
               i ++;
              if (i >= argc)
@@ -630,9 +673,22 @@ main(int  argc,                            /* I - Number of command-line args */
 
   if (!directory[0])
   {
-    snprintf(directory, sizeof(directory), "/tmp/ippserver.%d", (int)getpid());
+    const char *tmpdir;                        /* Temporary directory */
+
+#ifdef WIN32
+    if ((tmpdir = getenv("TEMP")) == NULL)
+      tmpdir = "C:/TEMP";
+#elif defined(__APPLE__) && !TARGET_OS_IOS
+    if ((tmpdir = getenv("TMPDIR")) == NULL)
+      tmpdir = "/private/tmp";
+#else
+    if ((tmpdir = getenv("TMPDIR")) == NULL)
+      tmpdir = "/tmp";
+#endif /* WIN32 */
+
+    snprintf(directory, sizeof(directory), "%s/ippserver.%d", tmpdir, (int)getpid());
 
-    if (mkdir(directory, 0777) && errno != EEXIST)
+    if (mkdir(directory, 0755) && errno != EEXIST)
     {
       fprintf(stderr, "Unable to create spool directory \"%s\": %s\n",
              directory, strerror(errno));
@@ -659,7 +715,7 @@ main(int  argc,                             /* I - Number of command-line args */
 
   if ((printer = create_printer(servername, name, location, make, model, icon,
                                 formats, ppm, ppm_color, duplex, port, pin,
-                               subtype, directory, command)) == NULL)
+                               subtype, directory, command, attrfile)) == NULL)
     return (1);
 
  /*
@@ -1028,7 +1084,10 @@ create_job(_ipp_client_t *client)        /* I - Client */
   ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id);
   ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL, uri);
   ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-uuid", NULL, uuid);
-  ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, client->printer->uri);
+  if ((attr = ippFindAttribute(client->request, "printer-uri", IPP_TAG_URI)) != NULL)
+    ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, ippGetString(attr, 0, NULL));
+  else
+    ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, client->printer->uri);
   ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "time-at-creation", (int)(job->created - client->printer->start_time));
 
   cupsArrayAdd(client->printer->jobs, job);
@@ -1216,7 +1275,8 @@ create_printer(const char *servername,    /* I - Server hostname (NULL for default)
               int        pin,          /* I - Require PIN printing */
               const char *subtype,     /* I - Bonjour service subtype */
               const char *directory,   /* I - Spool directory */
-              const char *command)     /* I - Command to run on job files */
+              const char *command,     /* I - Command to run on job files */
+              const char *attrfile)    /* I - Attributes file */
 {
   int                  i, j;           /* Looping vars */
   _ipp_printer_t       *printer;       /* Printer */
@@ -1224,6 +1284,10 @@ create_printer(const char *servername,   /* I - Server hostname (NULL for default)
   char                 path[1024];     /* Full path to command */
 #endif /* !WIN32 */
   char                 uri[1024],      /* Printer URI */
+#ifdef HAVE_SSL
+                       securi[1024],   /* Secure printer URI */
+                       *uris[2],       /* All URIs */
+#endif /* HAVE_SSL */
                        icons[1024],    /* printer-icons URI */
                        adminurl[1024], /* printer-more-info URI */
                        supplyurl[1024],/* printer-supply-info-uri URI */
@@ -1358,16 +1422,15 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
   static const int     pwg_raster_document_resolution_supported[] =
   {
     150,
-    300,
-    600
+    300
   };
   static const char * const pwg_raster_document_type_supported[] =
   {
-    "black-1",
-    "cmyk-8",
-    "sgray-8",
-    "srgb-8",
-    "srgb-16"
+    "black_1",
+    "cmyk_8",
+    "sgray_8",
+    "srgb_8",
+    "srgb_16"
   };
   static const char * const reference_uri_schemes_supported[] =
   {                                    /* reference-uri-schemes-supported */
@@ -1389,12 +1452,24 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
     "CP1",
     "IS1-5-7",
     "MT1-2-3-4-5-6-8-9-10-11-12-13",
-    "RS600",
+    "RS300",
     "SRGB24",
     "V1.4",
     "W8",
     "DM1"
   };
+#ifdef HAVE_SSL
+  static const char * const uri_authentication_supported[] =
+  {                                    /* uri-authentication-supported values */
+    "none",
+    "none"
+  };
+  static const char * const uri_security_supported[] =
+  {                                    /* uri-security-supported values */
+    "none",
+    "tls"
+  };
+#endif /* HAVE_SSL */
   static const char * const which_jobs[] =
   {                                    /* which-jobs-supported values */
     "completed",
@@ -1463,11 +1538,14 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
   printer->jobs          = cupsArrayNew((cups_array_func_t)compare_jobs, NULL);
   printer->next_job_id   = 1;
 
-  httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
-                 printer->hostname, printer->port, "/ipp/print");
+  httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL, printer->hostname, printer->port, "/ipp/print");
   printer->uri    = strdup(uri);
   printer->urilen = strlen(uri);
 
+#ifdef HAVE_SSL
+  httpAssembleURI(HTTP_URI_CODING_ALL, securi, sizeof(securi), "ipps", NULL, printer->hostname, printer->port, "/ipp/print");
+#endif /* HAVE_SSL */
+
   if (icon)
     printer->icon = strdup(icon);
 
@@ -1510,10 +1588,9 @@ create_printer(const char *servername,   /* I - Server hostname (NULL for default)
   * Prepare values for the printer attributes...
   */
 
-  httpAssembleURI(HTTP_URI_CODING_ALL, icons, sizeof(icons), "http", NULL,
-                  printer->hostname, printer->port, "/icon.png");
-  httpAssembleURI(HTTP_URI_CODING_ALL, adminurl, sizeof(adminurl), "http", NULL, printer->hostname, printer->port, "/");
-  httpAssembleURI(HTTP_URI_CODING_ALL, supplyurl, sizeof(supplyurl), "http", NULL, printer->hostname, printer->port, "/supplies");
+  httpAssembleURI(HTTP_URI_CODING_ALL, icons, sizeof(icons), WEB_SCHEME, NULL, printer->hostname, printer->port, "/icon.png");
+  httpAssembleURI(HTTP_URI_CODING_ALL, adminurl, sizeof(adminurl), WEB_SCHEME, NULL, printer->hostname, printer->port, "/");
+  httpAssembleURI(HTTP_URI_CODING_ALL, supplyurl, sizeof(supplyurl), WEB_SCHEME, NULL, printer->hostname, printer->port, "/supplies");
 
   if (Verbosity)
   {
@@ -1598,34 +1675,30 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
 
   printer->attrs = ippNew();
 
+  if (attrfile)
+    load_attributes(attrfile, printer->attrs);
+
   /* charset-configured */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER,
-               IPP_CONST_TAG(IPP_TAG_CHARSET),
-               "charset-configured", NULL, "utf-8");
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_CHARSET), "charset-configured", NULL, "utf-8");
 
   /* charset-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER,
-                IPP_CONST_TAG(IPP_TAG_CHARSET),
-                "charset-supported", sizeof(charsets) / sizeof(charsets[0]),
-               NULL, charsets);
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_CHARSET), "charset-supported", sizeof(charsets) / sizeof(charsets[0]), NULL, charsets);
 
   /* color-supported */
-  ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "color-supported",
-                ppm_color > 0);
+  if (!ippFindAttribute(printer->attrs, "color-supported", IPP_TAG_ZERO))
+    ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "color-supported", ppm_color > 0);
 
   /* compression-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER,
-                IPP_CONST_TAG(IPP_TAG_KEYWORD),
-               "compression-supported",
-               (int)(sizeof(compressions) / sizeof(compressions[0])), NULL,
-               compressions);
+  if (!ippFindAttribute(printer->attrs, "compression-supported", IPP_TAG_ZERO))
+    ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "compression-supported", (int)(sizeof(compressions) / sizeof(compressions[0])), NULL, compressions);
 
   /* copies-default */
-  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
-                "copies-default", 1);
+  if (!ippFindAttribute(printer->attrs, "copies-default", IPP_TAG_ZERO))
+    ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "copies-default", 1);
 
   /* copies-supported */
-  ippAddRange(printer->attrs, IPP_TAG_PRINTER, "copies-supported", 1, 999);
+  if (!ippFindAttribute(printer->attrs, "copies-supported", IPP_TAG_ZERO))
+    ippAddRange(printer->attrs, IPP_TAG_PRINTER, "copies-supported", 1, 999);
 
   /* document-format-default */
   ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE,
@@ -1637,47 +1710,55 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
                (const char * const *)formats);
 
   /* document-password-supported */
-  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "document-password-supported", 127);
+  if (!ippFindAttribute(printer->attrs, "document-password-supported", IPP_TAG_ZERO))
+    ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "document-password-supported", 127);
 
   /* finishings-default */
-  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
-                "finishings-default", IPP_FINISHINGS_NONE);
+  if (!ippFindAttribute(printer->attrs, "finishings-default", IPP_TAG_ZERO))
+    ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-default", IPP_FINISHINGS_NONE);
 
   /* finishings-supported */
-  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
-                "finishings-supported", IPP_FINISHINGS_NONE);
+  if (!ippFindAttribute(printer->attrs, "finishings-supported", IPP_TAG_ZERO))
+    ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "finishings-supported", IPP_FINISHINGS_NONE);
 
   /* generated-natural-language-supported */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER,
-               IPP_CONST_TAG(IPP_TAG_LANGUAGE),
-               "generated-natural-language-supported", NULL, "en");
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_LANGUAGE), "generated-natural-language-supported", NULL, "en");
 
   /* identify-actions-default */
-  ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "identify-actions-default", NULL, "sound");
+  if (!ippFindAttribute(printer->attrs, "identify-actions-default", IPP_TAG_ZERO))
+    ippAddString (printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "identify-actions-default", NULL, "sound");
 
   /* identify-actions-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "identify-actions-supported", sizeof(identify_actions) / sizeof(identify_actions[0]), NULL, identify_actions);
+  if (!ippFindAttribute(printer->attrs, "identify-actions-supported", IPP_TAG_ZERO))
+    ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "identify-actions-supported", sizeof(identify_actions) / sizeof(identify_actions[0]), NULL, identify_actions);
 
   /* ipp-features-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "ipp-features-supported", sizeof(features) / sizeof(features[0]), NULL, features);
+  if (!ippFindAttribute(printer->attrs, "ipp-features-supported", IPP_TAG_ZERO))
+    ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "ipp-features-supported", sizeof(features) / sizeof(features[0]), NULL, features);
 
   /* ipp-versions-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "ipp-versions-supported", sizeof(versions) / sizeof(versions[0]), NULL, versions);
+  if (!ippFindAttribute(printer->attrs, "ipp-versions-supported", IPP_TAG_ZERO))
+    ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "ipp-versions-supported", sizeof(versions) / sizeof(versions[0]), NULL, versions);
 
   /* job-account-id-default */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_NAME), "job-account-id-default", NULL, "");
+  if (!ippFindAttribute(printer->attrs, "job-account-id-default", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_NAME), "job-account-id-default", NULL, "");
 
   /* job-account-id-supported */
-  ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "job-account-id-supported", 1);
+  if (!ippFindAttribute(printer->attrs, "job-account-id-supported", IPP_TAG_ZERO))
+    ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "job-account-id-supported", 1);
 
   /* job-accounting-user-id-default */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_NAME), "job-accounting-user-id-default", NULL, "");
+  if (!ippFindAttribute(printer->attrs, "job-accounting-user-id-default", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_NAME), "job-accounting-user-id-default", NULL, "");
 
   /* job-accounting-user-id-supported */
-  ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "job-accounting-user-id-supported", 1);
+  if (!ippFindAttribute(printer->attrs, "job-accounting-user-id-supported", IPP_TAG_ZERO))
+    ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "job-accounting-user-id-supported", 1);
 
   /* job-creation-attributes-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-creation-attributes-supported", sizeof(job_creation) / sizeof(job_creation[0]), NULL, job_creation);
+  if (!ippFindAttribute(printer->attrs, "job-creation-attributes-supported", IPP_TAG_ZERO))
+    ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-creation-attributes-supported", sizeof(job_creation) / sizeof(job_creation[0]), NULL, job_creation);
 
   /* job-ids-supported */
   ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "job-ids-supported", 1);
@@ -1687,171 +1768,150 @@ create_printer(const char *servername,        /* I - Server hostname (NULL for default)
              k_supported);
 
   /* job-password-supported */
-  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
-                "job-password-supported", 4);
-
-  /* job-preferred-attributes-supported */
-  ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "job-preferred-attributes-supported", 0);
+  if (!ippFindAttribute(printer->attrs, "job-password-supported", IPP_TAG_ZERO))
+    ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "job-password-supported", 4);
 
   /* job-priority-default */
-  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
-                "job-priority-default", 50);
+  if (!ippFindAttribute(printer->attrs, "job-priority-default", IPP_TAG_ZERO))
+    ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "job-priority-default", 50);
 
   /* job-priority-supported */
-  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
-                "job-priority-supported", 100);
+  if (!ippFindAttribute(printer->attrs, "job-priority-supported", IPP_TAG_ZERO))
+    ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "job-priority-supported", 100);
 
   /* job-sheets-default */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER,
-               IPP_CONST_TAG(IPP_TAG_NAME),
-               "job-sheets-default", NULL, "none");
+  if (!ippFindAttribute(printer->attrs, "job-sheets-default", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_NAME), "job-sheets-default", NULL, "none");
 
   /* job-sheets-supported */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER,
-               IPP_CONST_TAG(IPP_TAG_NAME),
-               "job-sheets-supported", NULL, "none");
+  if (!ippFindAttribute(printer->attrs, "job-sheets-supported", IPP_TAG_ZERO))
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_NAME), "job-sheets-supported", NULL, "none");
 
   /* media-bottom-margin-supported */
-  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
-                 "media-bottom-margin-supported",
-                (int)(sizeof(media_xxx_margin_supported) /
-                      sizeof(media_xxx_margin_supported[0])),
-                media_xxx_margin_supported);
+  if (!ippFindAttribute(printer->attrs, "media-bottom-margin-supported", IPP_TAG_ZERO))
+    ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-bottom-margin-supported", (int)(sizeof(media_xxx_margin_supported) / sizeof(media_xxx_margin_supported[0])), media_xxx_margin_supported);
 
   /* media-col-database */
-  for (num_database = 0, i = 0;
-       i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0]));
-       i ++)
-  {
-    if (media_col_sizes[i][2] == _IPP_ENV_ONLY)
-      num_database += 3;               /* auto + manual + envelope */
-    else if (media_col_sizes[i][2] == _IPP_PHOTO_ONLY)
-      num_database += 6 * 3;           /* auto + photographic-* from auto, manual, and photo */
-    else
-      num_database += 2;               /* Regular + borderless */
-  }
-
-  media_col_database = ippAddCollections(printer->attrs, IPP_TAG_PRINTER,
-                                         "media-col-database", num_database,
-                                        NULL);
-  for (media_col_index = 0, i = 0;
-       i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0]));
-       i ++)
+  if (!ippFindAttribute(printer->attrs, "media-col-database", IPP_TAG_ZERO))
   {
-    switch (media_col_sizes[i][2])
+    for (num_database = 0, i = 0;
+        i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0]));
+        i ++)
     {
-      case _IPP_GENERAL :
-         /*
-         * Regular + borderless for the general class; no source/type
-         * selectors...
-         */
+      if (media_col_sizes[i][2] == _IPP_ENV_ONLY)
+       num_database += 3;              /* auto + manual + envelope */
+      else if (media_col_sizes[i][2] == _IPP_PHOTO_ONLY)
+       num_database += 6 * 3;          /* auto + photographic-* from auto, manual, and photo */
+      else
+       num_database += 2;              /* Regular + borderless */
+    }
 
-         ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], NULL, NULL, media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[1]));
-         ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], NULL, NULL, media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[0]));
-          break;
+    media_col_database = ippAddCollections(printer->attrs, IPP_TAG_PRINTER, "media-col-database", num_database, NULL);
+    for (media_col_index = 0, i = 0;
+        i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0]));
+        i ++)
+    {
+      switch (media_col_sizes[i][2])
+      {
+       case _IPP_GENERAL :
+          /*
+           * Regular + borderless for the general class; no source/type
+           * selectors...
+           */
 
-      case _IPP_ENV_ONLY :
-        /*
-         * Regular margins for "auto", "manual", and "envelope" sources.
-         */
+           ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], NULL, NULL, media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[1]));
+           ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], NULL, NULL, media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[0]));
+           break;
 
-         ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], "auto", "envelope", media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[1]));
-         ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], "manual", "envelope", media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[1]));
-         ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], "envelope", "envelope", media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[1]));
-         break;
-      case _IPP_PHOTO_ONLY :
-         /*
-         * Photos have specific media types and can only be printed via
-         * the auto, manual, and photo sources...
-         */
+       case _IPP_ENV_ONLY :
+          /*
+           * Regular margins for "auto", "manual", and "envelope" sources.
+           */
 
-         for (j = 0;
-              j < (int)(sizeof(media_type_supported) /
-                        sizeof(media_type_supported[0]));
-              j ++)
-         {
-           if (strcmp(media_type_supported[j], "auto") && strncmp(media_type_supported[j], "photographic-", 13))
-             continue;
+           ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], "auto", "envelope", media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[1]));
+           ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], "manual", "envelope", media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[1]));
+           ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], "envelope", "envelope", media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[1]));
+           break;
+       case _IPP_PHOTO_ONLY :
+          /*
+           * Photos have specific media types and can only be printed via
+           * the auto, manual, and photo sources...
+           */
 
-           ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], "auto", media_type_supported[j], media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[0]));
-           ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], "manual", media_type_supported[j], media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[0]));
-           ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], "photo", media_type_supported[j], media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[0]));
-         }
-         break;
+           for (j = 0;
+                j < (int)(sizeof(media_type_supported) /
+                          sizeof(media_type_supported[0]));
+                j ++)
+           {
+             if (strcmp(media_type_supported[j], "auto") && strncmp(media_type_supported[j], "photographic-", 13))
+               continue;
+
+             ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], "auto", media_type_supported[j], media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[0]));
+             ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], "manual", media_type_supported[j], media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[0]));
+             ippSetCollection(printer->attrs, &media_col_database, media_col_index ++, create_media_col(media_supported[i], "photo", media_type_supported[j], media_col_sizes[i][0], media_col_sizes[i][1], media_xxx_margin_supported[0]));
+           }
+           break;
+      }
     }
   }
 
   /* media-col-default */
-  media_col_default = create_media_col(media_supported[0],
-                                       media_source_supported[0],
-                                       media_type_supported[0],
-                                      media_col_sizes[0][0],
-                                      media_col_sizes[0][1],
-                                      media_xxx_margin_supported[1]);
+  if (!ippFindAttribute(printer->attrs, "media-col-default", IPP_TAG_ZERO))
+  {
+    media_col_default = create_media_col(media_supported[0], media_source_supported[0], media_type_supported[0], media_col_sizes[0][0], media_col_sizes[0][1],media_xxx_margin_supported[1]);
 
-  ippAddCollection(printer->attrs, IPP_TAG_PRINTER, "media-col-default",
-                   media_col_default);
-  ippDelete(media_col_default);
+    ippAddCollection(printer->attrs, IPP_TAG_PRINTER, "media-col-default",
+                    media_col_default);
+    ippDelete(media_col_default);
+  }
 
   /* media-col-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER,
-                IPP_CONST_TAG(IPP_TAG_KEYWORD),
-                "media-col-supported",
-               (int)(sizeof(media_col_supported) /
-                     sizeof(media_col_supported[0])), NULL,
-               media_col_supported);
+  if (!ippFindAttribute(printer->attrs, "media-col-supported", IPP_TAG_ZERO))
+    ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-col-supported", (int)(sizeof(media_col_supported) / sizeof(media_col_supported[0])), NULL, media_col_supported);
 
   /* media-default */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER,
-               IPP_CONST_TAG(IPP_TAG_KEYWORD),
-               "media-default", NULL, media_supported[0]);
+  if (!ippFindAttribute(printer->attrs, "media-default", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-default", NULL, media_supported[0]);
 
   /* media-left-margin-supported */
-  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
-                 "media-left-margin-supported",
-                (int)(sizeof(media_xxx_margin_supported) /
-                      sizeof(media_xxx_margin_supported[0])),
-                media_xxx_margin_supported);
+  if (!ippFindAttribute(printer->attrs, "media-left-margin-supported", IPP_TAG_ZERO))
+    ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-left-margin-supported", (int)(sizeof(media_xxx_margin_supported) / sizeof(media_xxx_margin_supported[0])), media_xxx_margin_supported);
 
   /* media-right-margin-supported */
-  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
-                 "media-right-margin-supported",
-                (int)(sizeof(media_xxx_margin_supported) /
-                      sizeof(media_xxx_margin_supported[0])),
-                media_xxx_margin_supported);
+  if (!ippFindAttribute(printer->attrs, "media-right-margin-supported", IPP_TAG_ZERO))
+    ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-right-margin-supported", (int)(sizeof(media_xxx_margin_supported) / sizeof(media_xxx_margin_supported[0])), media_xxx_margin_supported);
 
   /* media-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER,
-                IPP_CONST_TAG(IPP_TAG_KEYWORD),
-                "media-supported",
-               (int)(sizeof(media_supported) / sizeof(media_supported[0])),
-               NULL, media_supported);
+  if (!ippFindAttribute(printer->attrs, "media-supported", IPP_TAG_ZERO))
+    ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-supported", (int)(sizeof(media_supported) / sizeof(media_supported[0])), NULL, media_supported);
 
   /* media-size-supported */
-  media_size_supported = ippAddCollections(printer->attrs, IPP_TAG_PRINTER,
-                                           "media-size-supported",
-                                           (int)(sizeof(media_col_sizes) /
-                                                 sizeof(media_col_sizes[0])),
-                                           NULL);
-  for (i = 0;
-       i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0]));
-       i ++)
-    ippSetCollection(printer->attrs, &media_size_supported, i,
-                    create_media_size(media_col_sizes[i][0],
-                                      media_col_sizes[i][1]));
+  if (!ippFindAttribute(printer->attrs, "media-size-supported", IPP_TAG_ZERO))
+  {
+    media_size_supported = ippAddCollections(printer->attrs, IPP_TAG_PRINTER, "media-size-supported", (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0])), NULL);
+
+    for (i = 0;
+        i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0]));
+        i ++)
+    {
+      ipp_t *size = create_media_size(media_col_sizes[i][0], media_col_sizes[i][1]);
+
+      ippSetCollection(printer->attrs, &media_size_supported, i, size);
+      ippDelete(size);
+    }
+  }
 
   /* media-source-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-source-supported", (int)(sizeof(media_source_supported) / sizeof(media_source_supported[0])), NULL, media_source_supported);
+  if (!ippFindAttribute(printer->attrs, "media-source-supported", IPP_TAG_ZERO))
+    ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-source-supported", (int)(sizeof(media_source_supported) / sizeof(media_source_supported[0])), NULL, media_source_supported);
 
   /* media-top-margin-supported */
-  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
-                 "media-top-margin-supported",
-                (int)(sizeof(media_xxx_margin_supported) /
-                      sizeof(media_xxx_margin_supported[0])),
-                media_xxx_margin_supported);
+  if (!ippFindAttribute(printer->attrs, "media-top-margin-supported", IPP_TAG_ZERO))
+    ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "media-top-margin-supported", (int)(sizeof(media_xxx_margin_supported) / sizeof(media_xxx_margin_supported[0])), media_xxx_margin_supported);
 
   /* media-type-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-type-supported", (int)(sizeof(media_type_supported) / sizeof(media_type_supported[0])), NULL, media_type_supported);
+  if (!ippFindAttribute(printer->attrs, "media-type-supported", IPP_TAG_ZERO))
+    ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "media-type-supported", (int)(sizeof(media_type_supported) / sizeof(media_type_supported[0])), NULL, media_type_supported);
 
   /* multiple-document-handling-supported */
   ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "multiple-document-handling-supported", sizeof(multiple_document_handling) / sizeof(multiple_document_handling[0]), NULL, multiple_document_handling);
@@ -1871,40 +1931,39 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
                "natural-language-configured", NULL, "en");
 
   /* number-up-default */
-  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
-                "number-up-default", 1);
+  if (!ippFindAttribute(printer->attrs, "number-up-default", IPP_TAG_ZERO))
+    ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "number-up-default", 1);
 
   /* number-up-supported */
-  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
-                "number-up-supported", 1);
+  if (!ippFindAttribute(printer->attrs, "number-up-supported", IPP_TAG_ZERO))
+    ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "number-up-supported", 1);
 
   /* operations-supported */
-  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
-                "operations-supported", sizeof(ops) / sizeof(ops[0]), ops);
+  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "operations-supported", sizeof(ops) / sizeof(ops[0]), ops);
 
   /* orientation-requested-default */
-  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE,
-                "orientation-requested-default", 0);
+  if (!ippFindAttribute(printer->attrs, "orientation-requested-default", IPP_TAG_ZERO))
+    ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, "orientation-requested-default", 0);
 
   /* orientation-requested-supported */
-  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
-                 "orientation-requested-supported", 4, orients);
+  if (!ippFindAttribute(printer->attrs, "orientation-requested-supported", IPP_TAG_ZERO))
+    ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "orientation-requested-supported", 4, orients);
 
   /* output-bin-default */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER,
-               IPP_CONST_TAG(IPP_TAG_KEYWORD),
-               "output-bin-default", NULL, "face-down");
+  if (!ippFindAttribute(printer->attrs, "output-bin-default", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-default", NULL, "face-down");
 
   /* output-bin-supported */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER,
-               IPP_CONST_TAG(IPP_TAG_KEYWORD),
-               "output-bin-supported", NULL, "face-down");
+  if (!ippFindAttribute(printer->attrs, "output-bin-supported", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "output-bin-supported", NULL, "face-down");
 
   /* overrides-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "overrides-supported", (int)(sizeof(overrides) / sizeof(overrides[0])), NULL, overrides);
+  if (!ippFindAttribute(printer->attrs, "overrides-supported", IPP_TAG_ZERO))
+    ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "overrides-supported", (int)(sizeof(overrides) / sizeof(overrides[0])), NULL, overrides);
 
   /* page-ranges-supported */
-  ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "page-ranges-supported", 1);
+  if (!ippFindAttribute(printer->attrs, "page-ranges-supported", IPP_TAG_ZERO))
+    ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "page-ranges-supported", 1);
 
   /* pages-per-minute */
   ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
@@ -1916,33 +1975,43 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
                   "pages-per-minute-color", ppm_color);
 
   /* pdl-override-supported */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER,
-               IPP_CONST_TAG(IPP_TAG_KEYWORD),
-               "pdl-override-supported", NULL, "attempted");
+  if (!ippFindAttribute(printer->attrs, "pdl-override-supported", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "pdl-override-supported", NULL, "attempted");
+
+  /* preferred-attributes-supported */
+  ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "preferred-attributes-supported", 0);
 
   /* print-color-mode-default */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-default", NULL, "auto");
+  if (!ippFindAttribute(printer->attrs, "print-color-mode-default", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-default", NULL, "auto");
 
   /* print-color-mode-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported) / sizeof(print_color_mode_supported[0])), NULL, print_color_mode_supported);
+  if (!ippFindAttribute(printer->attrs, "print-color-mode-supported", IPP_TAG_ZERO))
+    ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-color-mode-supported", (int)(sizeof(print_color_mode_supported) / sizeof(print_color_mode_supported[0])), NULL, print_color_mode_supported);
 
   /* print-content-optimize-default */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-default", NULL, "auto");
+  if (!ippFindAttribute(printer->attrs, "print-content-optimize-default", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-default", NULL, "auto");
 
   /* print-content-optimize-supported */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-supported", NULL, "auto");
+  if (!ippFindAttribute(printer->attrs, "print-content-optimize-supported", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-content-optimize-supported", NULL, "auto");
 
   /* print-rendering-intent-default */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-default", NULL, "auto");
+  if (!ippFindAttribute(printer->attrs, "print-rendering-intent-default", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-default", NULL, "auto");
 
   /* print-rendering-intent-supported */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-supported", NULL, "auto");
+  if (!ippFindAttribute(printer->attrs, "print-rendering-intent-supported", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "print-rendering-intent-supported", NULL, "auto");
 
   /* print-quality-default */
-  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-default", IPP_QUALITY_NORMAL);
+  if (!ippFindAttribute(printer->attrs, "print-quality-default", IPP_TAG_ZERO))
+    ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-default", IPP_QUALITY_NORMAL);
 
   /* print-quality-supported */
-  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-supported", (int)(sizeof(print_quality_supported) / sizeof(print_quality_supported[0])), print_quality_supported);
+  if (!ippFindAttribute(printer->attrs, "print-quality-supported", IPP_TAG_ZERO))
+    ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM, "print-quality-supported", (int)(sizeof(print_quality_supported) / sizeof(print_quality_supported[0])), print_quality_supported);
 
   /* printer-device-id */
   ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
@@ -1952,19 +2021,18 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
   ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "printer-get-attributes-supported", NULL, "document-format");
 
   /* printer-geo-location */
-  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_UNKNOWN, "printer-geo-location", 0);
+  if (!ippFindAttribute(printer->attrs, "printer-geo-location", IPP_TAG_ZERO))
+    ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_UNKNOWN, "printer-geo-location", 0);
 
   /* printer-icons */
   ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI,
                "printer-icons", NULL, icons);
 
   /* printer-is-accepting-jobs */
-  ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "printer-is-accepting-jobs",
-                1);
+  ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "printer-is-accepting-jobs", 1);
 
   /* printer-info */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info",
-               NULL, name);
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info", NULL, name);
 
   /* printer-location */
   ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
@@ -1975,10 +2043,11 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
                "printer-make-and-model", NULL, make_model);
 
   /* printer-mandatory-job-attributes */
-  if (pin)
+  if (pin && !ippFindAttribute(printer->attrs, "", IPP_TAG_ZERO))
   {
     static const char * const names[] =
     {
+      "job-account-id",
       "job-accounting-user-id",
       "job-password"
     };
@@ -1992,22 +2061,23 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
   ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-more-info", NULL, adminurl);
 
   /* printer-name */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-name",
-               NULL, name);
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-name", NULL, name);
 
   /* printer-organization */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-organization", NULL, "Apple Inc.");
+  if (!ippFindAttribute(printer->attrs, "printer-organization", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-organization", NULL, "Apple Inc.");
 
   /* printer-organizational-unit */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-organizational-unit", NULL, "Printing Engineering");
+  if (!ippFindAttribute(printer->attrs, "printer-organizational-unit", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-organizational-unit", NULL, "Printing Engineering");
 
   /* printer-resolution-default */
-  ippAddResolution(printer->attrs, IPP_TAG_PRINTER,
-                   "printer-resolution-default", IPP_RES_PER_INCH, 600, 600);
+  if (!ippFindAttribute(printer->attrs, "printer-resolution-default", IPP_TAG_ZERO))
+    ippAddResolution(printer->attrs, IPP_TAG_PRINTER, "printer-resolution-default", IPP_RES_PER_INCH, 600, 600);
 
   /* printer-resolution-supported */
-  ippAddResolution(printer->attrs, IPP_TAG_PRINTER,
-                   "printer-resolution-supported", IPP_RES_PER_INCH, 600, 600);
+  if (!ippFindAttribute(printer->attrs, "printer-resolutions-supported", IPP_TAG_ZERO))
+    ippAddResolution(printer->attrs, IPP_TAG_PRINTER, "printer-resolution-supported", IPP_RES_PER_INCH, 600, 600);
 
   /* printer-supply-description */
   ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_TEXT), "printer-supply-description", (int)(sizeof(printer_supplies) / sizeof(printer_supplies[0])), NULL, printer_supplies);
@@ -2016,7 +2086,15 @@ create_printer(const char *servername,   /* I - Server hostname (NULL for default)
   ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-supply-info-uri", NULL, supplyurl);
 
   /* printer-uri-supported */
+#ifdef HAVE_SSL
+  uris[0] = uri;
+  uris[1] = securi;
+
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-uri-supported", 2, NULL, (const char **)uris);
+
+#else
   ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-uri-supported", NULL, uri);
+#endif /* HAVE_SSL */
 
   /* printer-uuid */
   httpAssembleUUID(printer->hostname, port, name, 0, uuid, sizeof(uuid));
@@ -2029,63 +2107,49 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
 
   if (i < num_formats)
   {
-    ippAddResolutions(printer->attrs, IPP_TAG_PRINTER,
-                      "pwg-raster-document-resolution-supported",
-                      (int)(sizeof(pwg_raster_document_resolution_supported) /
-                            sizeof(pwg_raster_document_resolution_supported[0])),
-                      IPP_RES_PER_INCH,
-                      pwg_raster_document_resolution_supported,
-                      pwg_raster_document_resolution_supported);
-    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
-                 "pwg-raster-document-sheet-back", NULL, "normal");
-    ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
-                  "pwg-raster-document-type-supported",
-                  (int)(sizeof(pwg_raster_document_type_supported) /
-                        sizeof(pwg_raster_document_type_supported[0])), NULL,
-                  pwg_raster_document_type_supported);
+    if (!ippFindAttribute(printer->attrs, "pwg-raster-document-resolution-supported", IPP_TAG_ZERO))
+      ippAddResolutions(printer->attrs, IPP_TAG_PRINTER, "pwg-raster-document-resolution-supported", (int)(sizeof(pwg_raster_document_resolution_supported) / sizeof(pwg_raster_document_resolution_supported[0])), IPP_RES_PER_INCH, pwg_raster_document_resolution_supported, pwg_raster_document_resolution_supported);
+    if (!ippFindAttribute(printer->attrs, "pwg-raster-document-sheet-back", IPP_TAG_ZERO))
+      ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "pwg-raster-document-sheet-back", NULL, "normal");
+    if (!ippFindAttribute(printer->attrs, "pwg-raster-document-type-supported", IPP_TAG_ZERO))
+      ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "pwg-raster-document-type-supported", (int)(sizeof(pwg_raster_document_type_supported) / sizeof(pwg_raster_document_type_supported[0])), NULL, pwg_raster_document_type_supported);
   }
 
   /* reference-uri-scheme-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER,
-                IPP_CONST_TAG(IPP_TAG_URISCHEME),
-                "reference-uri-schemes-supported",
-                (int)(sizeof(reference_uri_schemes_supported) /
-                      sizeof(reference_uri_schemes_supported[0])),
-                NULL, reference_uri_schemes_supported);
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_URISCHEME), "reference-uri-schemes-supported", (int)(sizeof(reference_uri_schemes_supported) / sizeof(reference_uri_schemes_supported[0])), NULL, reference_uri_schemes_supported);
 
   /* sides-default */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER,
-               IPP_CONST_TAG(IPP_TAG_KEYWORD),
-               "sides-default", NULL, "one-sided");
+  if (!ippFindAttribute(printer->attrs, "sides-default", IPP_TAG_ZERO))
+    ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-default", NULL, "one-sided");
 
   /* sides-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER,
-                IPP_CONST_TAG(IPP_TAG_KEYWORD),
-                "sides-supported", duplex ? 3 : 1, NULL, sides_supported);
+  if (!ippFindAttribute(printer->attrs, "sides-supported", IPP_TAG_ZERO))
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "sides-supported", duplex ? 3 : 1, NULL, sides_supported);
 
   /* urf-supported */
   for (i = 0; i < num_formats; i ++)
     if (!strcasecmp(formats[i], "image/urf"))
       break;
 
-  if (i < num_formats)
+  if (i < num_formats && !ippFindAttribute(printer->attrs, "urf-supported", IPP_TAG_ZERO))
     ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "urf-supported", (int)(sizeof(urf_supported) / sizeof(urf_supported[0])) - !duplex, NULL, urf_supported);
 
   /* uri-authentication-supported */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER,
-               IPP_CONST_TAG(IPP_TAG_KEYWORD),
-               "uri-authentication-supported", NULL, "none");
+#ifdef HAVE_SSL
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-authentication-supported", 2, NULL, uri_authentication_supported);
+#else
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-authentication-supported", NULL, "none");
+#endif /* HAVE_SSL */
 
   /* uri-security-supported */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER,
-               IPP_CONST_TAG(IPP_TAG_KEYWORD),
-               "uri-security-supported", NULL, "none");
+#ifdef HAVE_SSL
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-security-supported", 2, NULL, uri_security_supported);
+#else
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "uri-security-supported", NULL, "none");
+#endif /* HAVE_SSL */
 
   /* which-jobs-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER,
-                IPP_CONST_TAG(IPP_TAG_KEYWORD),
-                "which-jobs-supported",
-                sizeof(which_jobs) / sizeof(which_jobs[0]), NULL, which_jobs);
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_CONST_TAG(IPP_TAG_KEYWORD), "which-jobs-supported", sizeof(which_jobs) / sizeof(which_jobs[0]), NULL, which_jobs);
 
   free(formats[0]);
 
@@ -2286,7 +2350,7 @@ delete_printer(_ipp_printer_t *printer)   /* I - Printer */
  * 'dnssd_callback()' - Handle Bonjour registration events.
  */
 
-static void
+static void DNSSD_API
 dnssd_callback(
     DNSServiceRef       sdRef,         /* I - Service reference */
     DNSServiceFlags     flags,         /* I - Status flags */
@@ -2465,149 +2529,468 @@ find_job(_ipp_client_t *client)               /* I - Client */
 
 
 /*
- * 'html_escape()' - Write a HTML-safe string.
+ * 'get_collection()' - Get a collection value from a file.
  */
 
-static void
-html_escape(_ipp_client_t *client,     /* I - Client */
-           const char    *s,           /* I - String to write */
-           size_t        slen)         /* I - Number of characters to write */
+static ipp_t *                         /* O  - Collection value */
+get_collection(FILE       *fp,         /* I  - File to read from */
+               const char *filename,   /* I  - Attributes filename */
+              int        *linenum)     /* IO - Line number */
 {
-  const char   *start,                 /* Start of segment */
-               *end;                   /* End of string */
+  char         token[1024],            /* Token from file */
+               attr[128];              /* Attribute name */
+  ipp_tag_t    value;                  /* Current value type */
+  ipp_t                *col = ippNew();        /* Collection value */
+  ipp_attribute_t *lastcol = NULL;     /* Last collection attribute */
 
 
-  start = s;
-  end   = s + (slen > 0 ? slen : strlen(s));
-
-  while (*s && s < end)
+  while (get_token(fp, token, sizeof(token), linenum) != NULL)
   {
-    if (*s == '&' || *s == '<')
+    if (!strcmp(token, "}"))
+      break;
+    else if (!strcmp(token, "{") && lastcol)
     {
-      if (s > start)
-        httpWrite2(client->http, start, (size_t)(s - start));
+     /*
+      * Another collection value
+      */
 
-      if (*s == '&')
-        httpWrite2(client->http, "&amp;", 5);
-      else
-        httpWrite2(client->http, "&lt;", 4);
+      ipp_t    *subcol = get_collection(fp, filename, linenum);
+                                       /* Collection value */
 
-      start = s + 1;
+      if (subcol)
+        ippSetCollection(col, &lastcol, ippGetCount(lastcol), subcol);
+      else
+       goto col_error;
     }
+    else if (!_cups_strcasecmp(token, "MEMBER"))
+    {
+     /*
+      * Attribute...
+      */
 
-    s ++;
-  }
-
-  if (s > start)
-    httpWrite2(client->http, start, (size_t)(s - start));
-}
+      lastcol = NULL;
 
+      if (!get_token(fp, token, sizeof(token), linenum))
+      {
+       fprintf(stderr, "ippserver: Missing MEMBER value tag on line %d of \"%s\".\n", *linenum, filename);
+       goto col_error;
+      }
 
-/*
- * 'html_footer()' - Show the web interface footer.
- *
- * This function also writes the trailing 0-length chunk.
- */
+      if ((value = ippTagValue(token)) == IPP_TAG_ZERO)
+      {
+       fprintf(stderr, "ippserver: Bad MEMBER value tag \"%s\" on line %d of \"%s\".\n", token, *linenum, filename);
+       goto col_error;
+      }
 
-static void
-html_footer(_ipp_client_t *client)     /* I - Client */
-{
-  html_printf(client,
-             "</div>\n"
-             "</body>\n"
-             "</html>\n");
-  httpWrite2(client->http, "", 0);
-}
+      if (!get_token(fp, attr, sizeof(attr), linenum))
+      {
+       fprintf(stderr, "ippserver: Missing MEMBER name on line %d of \"%s\".\n", *linenum, filename);
+       goto col_error;
+      }
 
+      if (!get_token(fp, token, sizeof(token), linenum))
+      {
+       fprintf(stderr, "ippserver: Missing MEMBER value on line %d of \"%s\".\n", *linenum, filename);
+       goto col_error;
+      }
 
-/*
- * 'html_header()' - Show the web interface header and title.
- */
+      switch (value)
+      {
+       case IPP_TAG_BOOLEAN :
+           if (!_cups_strcasecmp(token, "true"))
+             ippAddBoolean(col, IPP_TAG_ZERO, attr, 1);
+           else
+             ippAddBoolean(col, IPP_TAG_ZERO, attr, (char)atoi(token));
+           break;
 
-static void
-html_header(_ipp_client_t *client,     /* I - Client */
-            const char    *title)      /* I - Title */
-{
-  html_printf(client,
-             "<!doctype html>\n"
-             "<html>\n"
-             "<head>\n"
-             "<title>%s</title>\n"
-             "<link rel=\"shortcut icon\" href=\"/icon.png\" type=\"image/png\">\n"
-             "<link rel=\"apple-touch-icon\" href=\"/icon.png\" type=\"image/png\">\n"
-             "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=9\">\n"
-             "<meta name=\"viewport\" content=\"width=device-width\">\n"
-             "<style>\n"
-             "body { font-family: sans-serif; margin: 0; }\n"
-             "div.body { padding: 0px 10px 10px; }\n"
-             "blockquote { background: #dfd; border-radius: 5px; color: #006; padding: 10px; }\n"
-             "table.form { border-collapse: collapse; margin-top: 10px; width: 100%%; }\n"
-             "table.form td, table.form th { padding: 5px 2px; width: 50%%; }\n"
-             "table.form th { text-align: right; }\n"
-             "table.striped { border-bottom: solid thin black; border-collapse: collapse; width: 100%%; }\n"
-             "table.striped tr:nth-child(even) { background: #fcfcfc; }\n"
-             "table.striped tr:nth-child(odd) { background: #f0f0f0; }\n"
-             "table.striped th { background: white; border-bottom: solid thin black; text-align: left; vertical-align: bottom; }\n"
-             "table.striped td { margin: 0; padding: 5px; vertical-align: top; }\n"
-             "table.nav { border-collapse: collapse; width: 100%%; }\n"
-             "table.nav td { margin: 0; text-align: center; }\n"
-             "td.nav a, td.nav a:active, td.nav a:hover, td.nav a:hover:link, td.nav a:hover:link:visited, td.nav a:link, td.nav a:link:visited, td.nav a:visited { background: inherit; color: inherit; font-size: 80%%; text-decoration: none; }\n"
-             "td.nav { background: #333; color: #fff; padding: 4px 8px; width: 33%%; }\n"
-             "td.nav.sel { background: #fff; color: #000; font-weight: bold; }\n"
-             "td.nav:hover { background: #666; color: #fff; }\n"
-             "td.nav:active { background: #000; color: #ff0; }\n"
-             "</style>\n"
-             "</head>\n"
-             "<body>\n"
-             "<table class=\"nav\"><tr>"
-             "<td class=\"nav%s\"><a href=\"/\">Status</a></td>"
-             "<td class=\"nav%s\"><a href=\"/supplies\">Supplies</a></td>"
-             "<td class=\"nav%s\"><a href=\"/media\">Media</a></td>"
-             "</tr></table>\n"
-             "<div class=\"body\">\n", title, !strcmp(client->uri, "/") ? " sel" : "", !strcmp(client->uri, "/supplies") ? " sel" : "", !strcmp(client->uri, "/media") ? " sel" : "");
-}
+       case IPP_TAG_INTEGER :
+       case IPP_TAG_ENUM :
+           ippAddInteger(col, IPP_TAG_ZERO, value, attr, atoi(token));
+           break;
 
+       case IPP_TAG_RESOLUTION :
+           {
+             int       xres,           /* X resolution */
+                       yres;           /* Y resolution */
+             char      units[6];       /* Units */
+
+             if (sscanf(token, "%dx%d%5s", &xres, &yres, units) != 3 ||
+                 (_cups_strcasecmp(units, "dpi") &&
+                  _cups_strcasecmp(units, "dpc") &&
+                  _cups_strcasecmp(units, "dpcm") &&
+                  _cups_strcasecmp(units, "other")))
+             {
+               fprintf(stderr, "ippserver: Bad resolution value \"%s\" on line %d of \"%s\".\n", token, *linenum, filename);
+               goto col_error;
+             }
 
-/*
- * 'html_printf()' - Send formatted text to the client, quoting as needed.
- */
+             if (!_cups_strcasecmp(units, "dpi"))
+               ippAddResolution(col, IPP_TAG_ZERO, attr, IPP_RES_PER_INCH, xres, yres);
+             else if (!_cups_strcasecmp(units, "dpc") ||
+                      !_cups_strcasecmp(units, "dpcm"))
+               ippAddResolution(col, IPP_TAG_ZERO, attr, IPP_RES_PER_CM, xres, yres);
+             else
+               ippAddResolution(col, IPP_TAG_ZERO, attr, (ipp_res_t)0, xres, yres);
+           }
+           break;
 
-static void
-html_printf(_ipp_client_t *client,     /* I - Client */
-           const char    *format,      /* I - Printf-style format string */
-           ...)                        /* I - Additional arguments as needed */
-{
-  va_list      ap;                     /* Pointer to arguments */
-  const char   *start;                 /* Start of string */
-  char         size,                   /* Size character (h, l, L) */
-               type;                   /* Format type character */
-  int          width,                  /* Width of field */
-               prec;                   /* Number of characters of precision */
-  char         tformat[100],           /* Temporary format string for sprintf() */
-               *tptr,                  /* Pointer into temporary format */
-               temp[1024];             /* Buffer for formatted numbers */
-  char         *s;                     /* Pointer to string */
+       case IPP_TAG_RANGE :
+           {
+             int       lowers[4],      /* Lower value */
+                       uppers[4],      /* Upper values */
+                       num_vals;       /* Number of values */
 
 
- /*
-  * Loop through the format string, formatting as needed...
-  */
+             num_vals = sscanf(token, "%d-%d,%d-%d,%d-%d,%d-%d",
+                               lowers + 0, uppers + 0,
+                               lowers + 1, uppers + 1,
+                               lowers + 2, uppers + 2,
+                               lowers + 3, uppers + 3);
 
-  va_start(ap, format);
-  start = format;
+             if ((num_vals & 1) || num_vals == 0)
+             {
+               fprintf(stderr, "ippserver: Bad rangeOfInteger value \"%s\" on line %d of \"%s\".\n", token, *linenum, filename);
+               goto col_error;
+             }
 
-  while (*format)
-  {
-    if (*format == '%')
-    {
-      if (format > start)
-        httpWrite2(client->http, start, (size_t)(format - start));
+             ippAddRanges(col, IPP_TAG_ZERO, attr, num_vals / 2, lowers,
+                          uppers);
+           }
+           break;
 
-      tptr    = tformat;
-      *tptr++ = *format++;
+       case IPP_TAG_BEGIN_COLLECTION :
+           if (!strcmp(token, "{"))
+           {
+             ipp_t     *subcol = get_collection(fp, filename, linenum);
+                                     /* Collection value */
 
-      if (*format == '%')
-      {
+             if (subcol)
+             {
+               lastcol = ippAddCollection(col, IPP_TAG_ZERO, attr, subcol);
+               ippDelete(subcol);
+             }
+             else
+               goto col_error;
+           }
+           else
+           {
+             fprintf(stderr, "ippserver: Bad collection value on line %d of \"%s\".\n", *linenum, filename);
+             goto col_error;
+           }
+           break;
+       case IPP_TAG_STRING :
+           ippAddOctetString(col, IPP_TAG_ZERO, attr, token, (int)strlen(token));
+           break;
+
+       default :
+           if (!strchr(token, ','))
+             ippAddString(col, IPP_TAG_ZERO, value, attr, NULL, token);
+           else
+           {
+            /*
+             * Multiple string values...
+             */
+
+             int       num_values;     /* Number of values */
+             char      *values[100],   /* Values */
+                       *ptr;           /* Pointer to next value */
+
+
+             values[0]  = token;
+             num_values = 1;
+
+             for (ptr = strchr(token, ','); ptr; ptr = strchr(ptr, ','))
+             {
+               *ptr++ = '\0';
+               values[num_values] = ptr;
+               num_values ++;
+               if (num_values >= (int)(sizeof(values) / sizeof(values[0])))
+                 break;
+             }
+
+             ippAddStrings(col, IPP_TAG_ZERO, value, attr, num_values,
+                           NULL, (const char **)values);
+           }
+           break;
+      }
+    }
+  }
+
+  return (col);
+
+ /*
+  * If we get here there was a parse error; free memory and return.
+  */
+
+  col_error:
+
+  ippDelete(col);
+
+  return (NULL);
+}
+
+
+/*
+ * 'get_token()' - Get a token from a file.
+ */
+
+static char *                          /* O  - Token from file or NULL on EOF */
+get_token(FILE *fp,                    /* I  - File to read from */
+          char *buf,                   /* I  - Buffer to read into */
+         int  buflen,                  /* I  - Length of buffer */
+         int  *linenum)                /* IO - Current line number */
+{
+  int  ch,                             /* Character from file */
+       quote;                          /* Quoting character */
+  char *bufptr,                        /* Pointer into buffer */
+       *bufend;                        /* End of buffer */
+
+
+  for (;;)
+  {
+   /*
+    * Skip whitespace...
+    */
+
+    while (isspace(ch = getc(fp)))
+    {
+      if (ch == '\n')
+        (*linenum) ++;
+    }
+
+   /*
+    * Read a token...
+    */
+
+    if (ch == EOF)
+      return (NULL);
+    else if (ch == '\'' || ch == '\"')
+    {
+     /*
+      * Quoted text or regular expression...
+      */
+
+      quote  = ch;
+      bufptr = buf;
+      bufend = buf + buflen - 1;
+
+      while ((ch = getc(fp)) != EOF)
+      {
+        if (ch == '\\')
+       {
+        /*
+         * Escape next character...
+         */
+
+         if (bufptr < bufend)
+           *bufptr++ = (char)ch;
+
+         if ((ch = getc(fp)) != EOF && bufptr < bufend)
+           *bufptr++ = (char)ch;
+       }
+       else if (ch == quote)
+          break;
+       else if (bufptr < bufend)
+          *bufptr++ = (char)ch;
+      }
+
+      *bufptr = '\0';
+
+      return (buf);
+    }
+    else if (ch == '#')
+    {
+     /*
+      * Comment...
+      */
+
+      while ((ch = getc(fp)) != EOF)
+       if (ch == '\n')
+          break;
+
+      (*linenum) ++;
+    }
+    else if (ch == '{' || ch == '}' || ch == ',')
+    {
+      buf[0] = (char)ch;
+      buf[1] = '\0';
+
+      return (buf);
+    }
+    else
+    {
+     /*
+      * Whitespace delimited text...
+      */
+
+      ungetc(ch, fp);
+
+      bufptr = buf;
+      bufend = buf + buflen - 1;
+
+      while ((ch = getc(fp)) != EOF)
+       if (isspace(ch) || ch == '#')
+          break;
+       else if (bufptr < bufend)
+          *bufptr++ = (char)ch;
+
+      if (ch == '#')
+        ungetc(ch, fp);
+      else if (ch == '\n')
+        (*linenum) ++;
+
+      *bufptr = '\0';
+
+      return (buf);
+    }
+  }
+}
+
+
+/*
+ * 'html_escape()' - Write a HTML-safe string.
+ */
+
+static void
+html_escape(_ipp_client_t *client,     /* I - Client */
+           const char    *s,           /* I - String to write */
+           size_t        slen)         /* I - Number of characters to write */
+{
+  const char   *start,                 /* Start of segment */
+               *end;                   /* End of string */
+
+
+  start = s;
+  end   = s + (slen > 0 ? slen : strlen(s));
+
+  while (*s && s < end)
+  {
+    if (*s == '&' || *s == '<')
+    {
+      if (s > start)
+        httpWrite2(client->http, start, (size_t)(s - start));
+
+      if (*s == '&')
+        httpWrite2(client->http, "&amp;", 5);
+      else
+        httpWrite2(client->http, "&lt;", 4);
+
+      start = s + 1;
+    }
+
+    s ++;
+  }
+
+  if (s > start)
+    httpWrite2(client->http, start, (size_t)(s - start));
+}
+
+
+/*
+ * 'html_footer()' - Show the web interface footer.
+ *
+ * This function also writes the trailing 0-length chunk.
+ */
+
+static void
+html_footer(_ipp_client_t *client)     /* I - Client */
+{
+  html_printf(client,
+             "</div>\n"
+             "</body>\n"
+             "</html>\n");
+  httpWrite2(client->http, "", 0);
+}
+
+
+/*
+ * 'html_header()' - Show the web interface header and title.
+ */
+
+static void
+html_header(_ipp_client_t *client,     /* I - Client */
+            const char    *title)      /* I - Title */
+{
+  html_printf(client,
+             "<!doctype html>\n"
+             "<html>\n"
+             "<head>\n"
+             "<title>%s</title>\n"
+             "<link rel=\"shortcut icon\" href=\"/icon.png\" type=\"image/png\">\n"
+             "<link rel=\"apple-touch-icon\" href=\"/icon.png\" type=\"image/png\">\n"
+             "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=9\">\n"
+             "<meta name=\"viewport\" content=\"width=device-width\">\n"
+             "<style>\n"
+             "body { font-family: sans-serif; margin: 0; }\n"
+             "div.body { padding: 0px 10px 10px; }\n"
+             "blockquote { background: #dfd; border-radius: 5px; color: #006; padding: 10px; }\n"
+             "table.form { border-collapse: collapse; margin-top: 10px; width: 100%%; }\n"
+             "table.form td, table.form th { padding: 5px 2px; width: 50%%; }\n"
+             "table.form th { text-align: right; }\n"
+             "table.striped { border-bottom: solid thin black; border-collapse: collapse; width: 100%%; }\n"
+             "table.striped tr:nth-child(even) { background: #fcfcfc; }\n"
+             "table.striped tr:nth-child(odd) { background: #f0f0f0; }\n"
+             "table.striped th { background: white; border-bottom: solid thin black; text-align: left; vertical-align: bottom; }\n"
+             "table.striped td { margin: 0; padding: 5px; vertical-align: top; }\n"
+             "table.nav { border-collapse: collapse; width: 100%%; }\n"
+             "table.nav td { margin: 0; text-align: center; }\n"
+             "td.nav a, td.nav a:active, td.nav a:hover, td.nav a:hover:link, td.nav a:hover:link:visited, td.nav a:link, td.nav a:link:visited, td.nav a:visited { background: inherit; color: inherit; font-size: 80%%; text-decoration: none; }\n"
+             "td.nav { background: #333; color: #fff; padding: 4px 8px; width: 33%%; }\n"
+             "td.nav.sel { background: #fff; color: #000; font-weight: bold; }\n"
+             "td.nav:hover { background: #666; color: #fff; }\n"
+             "td.nav:active { background: #000; color: #ff0; }\n"
+             "</style>\n"
+             "</head>\n"
+             "<body>\n"
+             "<table class=\"nav\"><tr>"
+             "<td class=\"nav%s\"><a href=\"/\">Status</a></td>"
+             "<td class=\"nav%s\"><a href=\"/supplies\">Supplies</a></td>"
+             "<td class=\"nav%s\"><a href=\"/media\">Media</a></td>"
+             "</tr></table>\n"
+             "<div class=\"body\">\n", title, !strcmp(client->uri, "/") ? " sel" : "", !strcmp(client->uri, "/supplies") ? " sel" : "", !strcmp(client->uri, "/media") ? " sel" : "");
+}
+
+
+/*
+ * 'html_printf()' - Send formatted text to the client, quoting as needed.
+ */
+
+static void
+html_printf(_ipp_client_t *client,     /* I - Client */
+           const char    *format,      /* I - Printf-style format string */
+           ...)                        /* I - Additional arguments as needed */
+{
+  va_list      ap;                     /* Pointer to arguments */
+  const char   *start;                 /* Start of string */
+  char         size,                   /* Size character (h, l, L) */
+               type;                   /* Format type character */
+  int          width,                  /* Width of field */
+               prec;                   /* Number of characters of precision */
+  char         tformat[100],           /* Temporary format string for sprintf() */
+               *tptr,                  /* Pointer into temporary format */
+               temp[1024];             /* Buffer for formatted numbers */
+  char         *s;                     /* Pointer to string */
+
+
+ /*
+  * Loop through the format string, formatting as needed...
+  */
+
+  va_start(ap, format);
+  start = format;
+
+  while (*format)
+  {
+    if (*format == '%')
+    {
+      if (format > start)
+        httpWrite2(client->http, start, (size_t)(format - start));
+
+      tptr    = tformat;
+      *tptr++ = *format++;
+
+      if (*format == '%')
+      {
         httpWrite2(client->http, "%", 1);
         format ++;
        start = format;
@@ -3290,45 +3673,22 @@ ipp_get_printer_attributes(
                    "printer-state-reasons", NULL, "none");
     else
     {
-      int                      num_reasons = 0;/* Number of reasons */
-      const char               *reasons[32];   /* Reason strings */
-
-      if (printer->state_reasons & _IPP_PREASON_OTHER)
-       reasons[num_reasons ++] = "other";
-      if (printer->state_reasons & _IPP_PREASON_COVER_OPEN)
-       reasons[num_reasons ++] = "cover-open";
-      if (printer->state_reasons & _IPP_PREASON_INPUT_TRAY_MISSING)
-       reasons[num_reasons ++] = "input-tray-missing";
-      if (printer->state_reasons & _IPP_PREASON_MARKER_SUPPLY_EMPTY)
-       reasons[num_reasons ++] = "marker-supply-empty-warning";
-      if (printer->state_reasons & _IPP_PREASON_MARKER_SUPPLY_LOW)
-       reasons[num_reasons ++] = "marker-supply-low-report";
-      if (printer->state_reasons & _IPP_PREASON_MARKER_WASTE_ALMOST_FULL)
-       reasons[num_reasons ++] = "marker-waste-almost-full-report";
-      if (printer->state_reasons & _IPP_PREASON_MARKER_WASTE_FULL)
-       reasons[num_reasons ++] = "marker-waste-full-warning";
-      if (printer->state_reasons & _IPP_PREASON_MEDIA_EMPTY)
-       reasons[num_reasons ++] = "media-empty-warning";
-      if (printer->state_reasons & _IPP_PREASON_MEDIA_JAM)
-       reasons[num_reasons ++] = "media-jam-warning";
-      if (printer->state_reasons & _IPP_PREASON_MEDIA_LOW)
-       reasons[num_reasons ++] = "media-low-report";
-      if (printer->state_reasons & _IPP_PREASON_MEDIA_NEEDED)
-       reasons[num_reasons ++] = "media-needed-report";
-      if (printer->state_reasons & _IPP_PREASON_MOVING_TO_PAUSED)
-       reasons[num_reasons ++] = "moving-to-paused";
-      if (printer->state_reasons & _IPP_PREASON_PAUSED)
-       reasons[num_reasons ++] = "paused";
-      if (printer->state_reasons & _IPP_PREASON_SPOOL_AREA_FULL)
-       reasons[num_reasons ++] = "spool-area-full";
-      if (printer->state_reasons & _IPP_PREASON_TONER_EMPTY)
-       reasons[num_reasons ++] = "toner-empty-warning";
-      if (printer->state_reasons & _IPP_PREASON_TONER_LOW)
-       reasons[num_reasons ++] = "toner-low-report";
-
-      ippAddStrings(client->response, IPP_TAG_PRINTER,
-                    IPP_CONST_TAG(IPP_TAG_KEYWORD),
-                    "printer-state-reasons", num_reasons, NULL, reasons);
+      ipp_attribute_t  *attr = NULL;           /* printer-state-reasons */
+      _ipp_preason_t   bit;                    /* Reason bit */
+      int              i;                      /* Looping var */
+      char             reason[32];             /* Reason string */
+
+      for (i = 0, bit = 1; i < (int)(sizeof(_ipp_preason_strings) / sizeof(_ipp_preason_strings[0])); i ++, bit *= 2)
+      {
+        if (printer->state_reasons & bit)
+       {
+         snprintf(reason, sizeof(reason), "%s-%s", _ipp_preason_strings[i], printer->state == IPP_PSTATE_IDLE ? "report" : printer->state == IPP_PSTATE_PROCESSING ? "warning" : "error");
+         if (attr)
+           ippSetString(client->response, &attr, ippGetCount(attr), reason);
+         else
+           attr = ippAddString(client->response, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "printer-state-reasons", NULL, reason);
+       }
+      }
     }
   }
 
@@ -3371,7 +3731,21 @@ static void
 ipp_identify_printer(
     _ipp_client_t *client)             /* I - Client */
 {
-  /* TODO: Do something */
+  ipp_attribute_t      *actions,       /* identify-actions */
+                       *message;       /* message */
+
+
+  actions = ippFindAttribute(client->request, "identify-actions", IPP_TAG_KEYWORD);
+  message = ippFindAttribute(client->request, "message", IPP_TAG_TEXT);
+
+  if (!actions || ippContainsString(actions, "sound"))
+  {
+    putchar(0x07);
+    fflush(stdout);
+  }
+
+  if (ippContainsString(actions, "display"))
+    printf("IDENTIFY from %s: %s\n", client->hostname, message ? ippGetString(message, 0, NULL) : "No message supplied");
 
   respond_ipp(client, IPP_STATUS_OK, NULL);
 }
@@ -4418,6 +4792,296 @@ ipp_validate_job(_ipp_client_t *client) /* I - Client */
 }
 
 
+/*
+ * 'load_attributes()' - Load printer attributes from a file.
+ *
+ * Syntax is based on ipptool format:
+ *
+ *    ATTR value-tag name value
+ */
+
+static void
+load_attributes(const char *filename,  /* I - File to load */
+                ipp_t      *attrs)     /* I - Printer attributes */
+{
+  int          linenum = 0;            /* Current line number */
+  FILE         *fp = NULL;             /* Test file */
+  char         attr[128],              /* Attribute name */
+               token[1024],            /* Token from file */
+               *tokenptr;              /* Pointer into token */
+  ipp_tag_t    value;                  /* Current value type */
+  ipp_attribute_t *attrptr,            /* Attribute pointer */
+               *lastcol = NULL;        /* Last collection attribute */
+
+
+  if ((fp = fopen(filename, "r")) == NULL)
+  {
+    fprintf(stderr, "ippserver: Unable to open \"%s\": %s\n", filename, strerror(errno));
+    exit(1);
+  }
+
+  while (get_token(fp, token, sizeof(token), &linenum) != NULL)
+  {
+    if (!_cups_strcasecmp(token, "ATTR"))
+    {
+     /*
+      * Attribute...
+      */
+
+      if (!get_token(fp, token, sizeof(token), &linenum))
+      {
+       fprintf(stderr, "ippserver: Missing ATTR value tag on line %d of \"%s\".\n", linenum, filename);
+        exit(1);
+      }
+
+      if ((value = ippTagValue(token)) == IPP_TAG_ZERO)
+      {
+       fprintf(stderr, "ippserver: Bad ATTR value tag \"%s\" on line %d of \"%s\".\n", token, linenum, filename);
+       exit(1);
+      }
+
+      if (!get_token(fp, attr, sizeof(attr), &linenum))
+      {
+       fprintf(stderr, "ippserver: Missing ATTR name on line %d of \"%s\".\n", linenum, filename);
+       exit(1);
+      }
+
+      if (!get_token(fp, token, sizeof(token), &linenum))
+      {
+       fprintf(stderr, "ippserver: Missing ATTR value on line %d of \"%s\".\n", linenum, filename);
+       exit(1);
+      }
+
+      attrptr = NULL;
+
+      switch (value)
+      {
+       case IPP_TAG_BOOLEAN :
+           if (!_cups_strcasecmp(token, "true"))
+             attrptr = ippAddBoolean(attrs, IPP_TAG_PRINTER, attr, 1);
+           else
+             attrptr = ippAddBoolean(attrs, IPP_TAG_PRINTER, attr, (char)atoi(token));
+           break;
+
+       case IPP_TAG_INTEGER :
+       case IPP_TAG_ENUM :
+           if (!strchr(token, ','))
+             attrptr = ippAddInteger(attrs, IPP_TAG_PRINTER, value, attr, (int)strtol(token, &tokenptr, 0));
+           else
+           {
+             int       values[100],    /* Values */
+                       num_values = 1; /* Number of values */
+
+             values[0] = (int)strtol(token, &tokenptr, 10);
+             while (tokenptr && *tokenptr &&
+                    num_values < (int)(sizeof(values) / sizeof(values[0])))
+             {
+               if (*tokenptr == ',')
+                 tokenptr ++;
+               else if (!isdigit(*tokenptr & 255) && *tokenptr != '-')
+                 break;
+
+               values[num_values] = (int)strtol(tokenptr, &tokenptr, 0);
+               num_values ++;
+             }
+
+             attrptr = ippAddIntegers(attrs, IPP_TAG_PRINTER, value, attr, num_values, values);
+           }
+
+           if (!tokenptr || *tokenptr)
+           {
+             fprintf(stderr, "ippserver: Bad %s value \"%s\" on line %d of \"%s\".\n", ippTagString(value), token, linenum, filename);
+             exit(1);
+           }
+           break;
+
+       case IPP_TAG_RESOLUTION :
+           {
+             int       xres,           /* X resolution */
+                       yres;           /* Y resolution */
+             ipp_res_t units;          /* Units */
+             char      *start,         /* Start of value */
+                       *ptr,           /* Pointer into value */
+                       *next = NULL;   /* Next value */
+
+             for (start = token; start; start = next)
+             {
+               xres = yres = (int)strtol(start, (char **)&ptr, 10);
+               if (ptr > start && xres > 0)
+               {
+                 if (*ptr == 'x')
+                   yres = (int)strtol(ptr + 1, (char **)&ptr, 10);
+               }
+
+               if (ptr && (next = strchr(ptr, ',')) != NULL)
+                 *next++ = '\0';
+
+               if (ptr <= start || xres <= 0 || yres <= 0 || !ptr ||
+                   (_cups_strcasecmp(ptr, "dpi") &&
+                    _cups_strcasecmp(ptr, "dpc") &&
+                    _cups_strcasecmp(ptr, "dpcm") &&
+                    _cups_strcasecmp(ptr, "other")))
+               {
+                 fprintf(stderr, "ippserver: Bad resolution value \"%s\" on line %d of \"%s\".\n", token, linenum, filename);
+                 exit(1);
+               }
+
+               if (!_cups_strcasecmp(ptr, "dpc") || !_cups_strcasecmp(ptr, "dpcm"))
+                 units = IPP_RES_PER_CM;
+               else
+                 units = IPP_RES_PER_INCH;
+
+                if (attrptr)
+                 ippSetResolution(attrs, &attrptr, ippGetCount(attrptr), units, xres, yres);
+               else
+                 attrptr = ippAddResolution(attrs, IPP_TAG_PRINTER, attr, units, xres, yres);
+             }
+           }
+           break;
+
+       case IPP_TAG_RANGE :
+           {
+             int       lowers[4],      /* Lower value */
+                       uppers[4],      /* Upper values */
+                       num_vals;       /* Number of values */
+
+
+             num_vals = sscanf(token, "%d-%d,%d-%d,%d-%d,%d-%d",
+                               lowers + 0, uppers + 0,
+                               lowers + 1, uppers + 1,
+                               lowers + 2, uppers + 2,
+                               lowers + 3, uppers + 3);
+
+             if ((num_vals & 1) || num_vals == 0)
+             {
+               fprintf(stderr, "ippserver: Bad rangeOfInteger value \"%s\" on line %d of \"%s\".", token, linenum, filename);
+               exit(1);
+             }
+
+             attrptr = ippAddRanges(attrs, IPP_TAG_PRINTER, attr, num_vals / 2, lowers,
+                                    uppers);
+           }
+           break;
+
+       case IPP_TAG_BEGIN_COLLECTION :
+           if (!strcmp(token, "{"))
+           {
+             ipp_t     *col = get_collection(fp, filename, &linenum);
+                                   /* Collection value */
+
+             if (col)
+             {
+               attrptr = lastcol = ippAddCollection(attrs, IPP_TAG_PRINTER, attr, col);
+               ippDelete(col);
+             }
+             else
+               exit(1);
+           }
+           else
+           {
+             fprintf(stderr, "ippserver: Bad ATTR collection value on line %d of \"%s\".\n", linenum, filename);
+             exit(1);
+           }
+
+           do
+           {
+             ipp_t     *col;                   /* Collection value */
+             long      pos = ftell(fp);        /* Save position of file */
+
+             if (!get_token(fp, token, sizeof(token), &linenum))
+               break;
+
+             if (strcmp(token, ","))
+             {
+               fseek(fp, pos, SEEK_SET);
+               break;
+             }
+
+             if (!get_token(fp, token, sizeof(token), &linenum) || strcmp(token, "{"))
+             {
+               fprintf(stderr, "ippserver: Unexpected \"%s\" on line %d of \"%s\".\n", token, linenum, filename);
+               exit(1);
+             }
+
+             if ((col = get_collection(fp, filename, &linenum)) == NULL)
+               break;
+
+             ippSetCollection(attrs, &attrptr, ippGetCount(attrptr), col);
+             lastcol = attrptr;
+           }
+           while (!strcmp(token, "{"));
+           break;
+
+       case IPP_TAG_STRING :
+           attrptr = ippAddOctetString(attrs, IPP_TAG_PRINTER, attr, token, (int)strlen(token));
+           break;
+
+       default :
+           fprintf(stderr, "ippserver: Unsupported ATTR value tag %s on line %d of \"%s\".\n", ippTagString(value), linenum, filename);
+           exit(1);
+
+       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 (!strchr(token, ','))
+             attrptr = ippAddString(attrs, IPP_TAG_PRINTER, value, attr, NULL, token);
+           else
+           {
+            /*
+             * Multiple string values...
+             */
+
+             int       num_values;     /* Number of values */
+             char      *values[100],   /* Values */
+                       *ptr;           /* Pointer to next value */
+
+
+             values[0]  = token;
+             num_values = 1;
+
+             for (ptr = strchr(token, ','); ptr; ptr = strchr(ptr, ','))
+             {
+               if (ptr > token && ptr[-1] == '\\')
+                 _cups_strcpy(ptr - 1, ptr);
+               else
+               {
+                 *ptr++ = '\0';
+                 values[num_values] = ptr;
+                 num_values ++;
+                 if (num_values >= (int)(sizeof(values) / sizeof(values[0])))
+                   break;
+               }
+             }
+
+             attrptr = ippAddStrings(attrs, IPP_TAG_PRINTER, value, attr, num_values, NULL, (const char **)values);
+           }
+           break;
+      }
+
+      if (!attrptr)
+      {
+        fprintf(stderr, "ippserver: Unable to add attribute on line %d of \"%s\": %s\n", linenum, filename, cupsLastErrorString());
+        exit(1);
+      }
+    }
+    else
+    {
+      fprintf(stderr, "ippserver: Unknown directive \"%s\" on line %d of \"%s\".\n", token, linenum, filename);
+      exit(1);
+    }
+  }
+
+  fclose(fp);
+}
+
+
 /*
  * 'parse_options()' - Parse URL options into CUPS options.
  *
@@ -4452,6 +5116,20 @@ parse_options(_ipp_client_t *client,     /* I - Client */
 }
 
 
+/*
+ * 'process_attr_message()' - Process an ATTR: message from a command.
+ */
+
+static void
+process_attr_message(
+    _ipp_job_t *job,                   /* I - Job */
+    char       *message)               /* I - Message */
+{
+  (void)job;
+  (void)message;
+}
+
+
 /*
  * 'process_client()' - Process client requests on a thread.
  */
@@ -4799,7 +5477,7 @@ process_http(_ipp_client_t *client)       /* I - Client connection */
            if (client->printer->state_reasons & reason)
              html_printf(client, "\n<br>&nbsp;&nbsp;&nbsp;&nbsp;%s", reasons[i]);
          html_printf(client, "</p>\n");
-         
+
           if (cupsArrayCount(client->printer->jobs) > 0)
          {
             _cupsRWLockRead(&(client->printer->rwlock));
@@ -5378,6 +6056,13 @@ process_job(_ipp_job_t *job)             /* I - Job */
     ipp_attribute_t *attr;             /* Job attribute */
     char       val[1280],              /* IPP_NAME=value */
                *valptr;                /* Pointer into string */
+#ifndef WIN32
+    int                mypipe[2];              /* Pipe for stderr */
+    char       line[2048],             /* Line from stderr */
+               *ptr,                   /* Pointer into line */
+               *endptr;                /* End of line */
+    ssize_t    bytes;                  /* Bytes read */
+#endif /* !WIN32 */
 
     fprintf(stderr, "Running command \"%s %s\".\n", job->printer->command,
             job->filename);
@@ -5437,13 +6122,25 @@ process_job(_ipp_job_t *job)            /* I - Job */
 
 #ifdef WIN32
     status = _spawnvpe(_P_WAIT, job->printer->command, myargv, myenvp);
+
 #else
+    if (pipe(mypipe))
+    {
+      perror("Unable to create pipe for stderr");
+      mypipe[0] = mypipe[1] = -1;
+    }
+
     if ((pid = fork()) == 0)
     {
      /*
       * Child comes here...
       */
 
+      close(2);
+      dup2(mypipe[1], 2);
+      close(mypipe[0]);
+      close(mypipe[1]);
+
       execve(job->printer->command, myargv, myenvp);
       exit(errno);
     }
@@ -5455,6 +6152,16 @@ process_job(_ipp_job_t *job)             /* I - Job */
 
       perror("Unable to start job processing command");
       status = -1;
+
+      close(mypipe[0]);
+      close(mypipe[1]);
+
+     /*
+      * Free memory used for environment...
+      */
+
+      while (myenvc > 0)
+       free(myenvp[-- myenvc]);
     }
     else
     {
@@ -5465,6 +6172,54 @@ process_job(_ipp_job_t *job)             /* I - Job */
       while (myenvc > 0)
        free(myenvp[-- myenvc]);
 
+     /*
+      * If the pipe exists, read from it until EOF...
+      */
+
+      if (mypipe[0] >= 0)
+      {
+       close(mypipe[1]);
+
+       endptr = line;
+       while ((bytes = read(mypipe[0], endptr, sizeof(line) - (size_t)(endptr - line) - 1)) > 0)
+       {
+         endptr += bytes;
+         *endptr = '\0';
+
+          while ((ptr = strchr(line, '\n')) != NULL)
+         {
+           *ptr++ = '\0';
+
+           if (!strncmp(line, "STATE:", 6))
+           {
+            /*
+             * Process printer-state-reasons keywords.
+             */
+
+             process_state_message(job, line);
+           }
+           else if (!strncmp(line, "ATTR:", 5))
+           {
+            /*
+             * Process printer attribute update.
+             */
+
+             process_attr_message(job, line);
+           }
+           else if (Verbosity > 1)
+             fprintf(stderr, "%s: %s\n", job->printer->command, line);
+
+           bytes = ptr - line;
+            if (ptr < endptr)
+             memmove(line, ptr, (size_t)(endptr - ptr));
+           endptr -= bytes;
+           *endptr = '\0';
+         }
+       }
+
+       close(mypipe[0]);
+      }
+
      /*
       * Wait for child to complete...
       */
@@ -5527,6 +6282,95 @@ process_job(_ipp_job_t *job)             /* I - Job */
 }
 
 
+/*
+ * 'process_state_message()' - Process a STATE: message from a command.
+ */
+
+static void
+process_state_message(
+    _ipp_job_t *job,                   /* I - Job */
+    char       *message)               /* I - Message */
+{
+  int          i;                      /* Looping var */
+  _ipp_preason_t state_reasons,                /* printer-state-reasons values */
+               bit;                    /* Current reason bit */
+  char         *ptr,                   /* Pointer into message */
+               *next;                  /* Next keyword in message */
+  int          remove;                 /* Non-zero if we are removing keywords */
+
+
+ /*
+  * Skip leading "STATE:" and any whitespace...
+  */
+
+  for (message += 6; *message; message ++)
+    if (*message != ' ' && *message != '\t')
+      break;
+
+ /*
+  * Support the following forms of message:
+  *
+  * "keyword[,keyword,...]" to set the printer-state-reasons value(s).
+  *
+  * "-keyword[,keyword,...]" to remove keywords.
+  *
+  * "+keyword[,keyword,...]" to add keywords.
+  *
+  * Keywords may or may not have a suffix (-report, -warning, -error) per
+  * RFC 2911.
+  */
+
+  if (*message == '-')
+  {
+    remove        = 1;
+    state_reasons = job->printer->state_reasons;
+    message ++;
+  }
+  else if (*message == '+')
+  {
+    remove        = 0;
+    state_reasons = job->printer->state_reasons;
+    message ++;
+  }
+  else
+  {
+    remove        = 0;
+    state_reasons = _IPP_PREASON_NONE;
+  }
+
+  while (*message)
+  {
+    if ((next = strchr(message, ',')) != NULL)
+      *next++ = '\0';
+
+    if ((ptr = strstr(message, "-error")) != NULL)
+      *ptr = '\0';
+    else if ((ptr = strstr(message, "-report")) != NULL)
+      *ptr = '\0';
+    else if ((ptr = strstr(message, "-warning")) != NULL)
+      *ptr = '\0';
+
+    for (i = 0, bit = 1; i < (int)(sizeof(_ipp_preason_strings) / sizeof(_ipp_preason_strings[0])); i ++, bit *= 2)
+    {
+      if (!strcmp(message, _ipp_preason_strings[i]))
+      {
+        if (remove)
+         state_reasons &= ~bit;
+       else
+         state_reasons |= bit;
+      }
+    }
+
+    if (next)
+      message = next;
+    else
+      break;
+  }
+
+  job->printer->state_reasons = state_reasons;
+}
+
+
 /*
  * 'register_printer()' - Register a printer object via Bonjour.
  */
@@ -5544,7 +6388,9 @@ register_printer(
     int            duplex,             /* I - 1 = duplex, 0 = simplex */
     const char     *subtype)           /* I - Service subtype */
 {
+#if defined(HAVE_DNSSD) || defined(HAVE_AVAHI)
   _ipp_txt_t           ipp_txt;        /* Bonjour IPP TXT record */
+#endif /* HAVE_DNSSD || HAVE_AVAHI */
 #ifdef HAVE_DNSSD
   DNSServiceErrorType  error;          /* Error from Bonjour */
   char                 make_model[256],/* Make and model together */
@@ -5582,6 +6428,11 @@ register_printer(
 #  ifdef HAVE_SSL
   TXTRecordSetValue(&ipp_txt, "TLS", 3, "1.2");
 #  endif /* HAVE_SSL */
+  if (strstr(formats, "image/urf"))
+    TXTRecordSetValue(&ipp_txt, "URF", 66, "CP1,IS1-5-7,MT1-2-3-4-5-6-8-9-10-11-12-13,RS300,SRGB24,V1.4,W8,DM1");
+
+  TXTRecordSetValue(&ipp_txt, "txtvers", 1, "1");
+  TXTRecordSetValue(&ipp_txt, "qtotal", 1, "1");
 
  /*
   * Register the _printer._tcp (LPD) service type with a port number of 0 to
@@ -6038,7 +6889,7 @@ usage(int status)                 /* O - Exit status */
 {
   if (!status)
   {
-    puts(CUPS_SVERSION " - Copyright 2010-2014 by Apple Inc. All rights "
+    puts(CUPS_SVERSION " - Copyright 2010-2015 by Apple Inc. All rights "
          "reserved.");
     puts("");
   }
@@ -6049,6 +6900,7 @@ usage(int status)                 /* O - Exit status */
   puts("-2                      Supports 2-sided printing (default=1-sided)");
   puts("-M manufacturer         Manufacturer name (default=Test)");
   puts("-P                      PIN printing mode");
+  puts("-a attributes-file      Load printer attributes from file");
   puts("-c command              Run command for every print job");
   printf("-d spool-directory      Spool directory "
          "(default=/tmp/ippserver.%d)\n", (int)getpid());
@@ -6227,6 +7079,7 @@ valid_job_attributes(
     _ipp_client_t *client)             /* I - Client */
 {
   int                  i,              /* Looping var */
+                       count,          /* Number of values */
                        valid = 1;      /* Valid attributes? */
   ipp_attribute_t      *attr,          /* Current attribute */
                        *supported;     /* xxx-supported attribute */
@@ -6333,13 +7186,9 @@ valid_job_attributes(
     }
     else
     {
-      for (i = 0;
-           i < (int)(sizeof(media_supported) / sizeof(media_supported[0]));
-          i ++)
-        if (!strcmp(ippGetString(attr, 0, NULL), media_supported[i]))
-         break;
+      supported = ippFindAttribute(client->printer->attrs, "media-supported", IPP_TAG_KEYWORD);
 
-      if (i >= (int)(sizeof(media_supported) / sizeof(media_supported[0])))
+      if (!ippContainsString(supported, ippGetString(attr, 0, NULL)))
       {
        respond_unsupported(client, attr);
        valid = 0;
@@ -6349,13 +7198,86 @@ valid_job_attributes(
 
   if ((attr = ippFindAttribute(client->request, "media-col", IPP_TAG_ZERO)) != NULL)
   {
+    ipp_t              *col,           /* media-col collection */
+                       *size;          /* media-size collection */
+    ipp_attribute_t    *member,        /* Member attribute */
+                       *x_dim,         /* x-dimension */
+                       *y_dim;         /* y-dimension */
+    int                        x_value,        /* y-dimension value */
+                       y_value;        /* x-dimension value */
+
     if (ippGetCount(attr) != 1 ||
         ippGetValueTag(attr) != IPP_TAG_BEGIN_COLLECTION)
     {
       respond_unsupported(client, attr);
       valid = 0;
     }
-    /* TODO: check for valid media-col */
+
+    col = ippGetCollection(attr, 0);
+
+    if ((member = ippFindAttribute(col, "media-size-name", IPP_TAG_ZERO)) != NULL)
+    {
+      if (ippGetCount(member) != 1 ||
+         (ippGetValueTag(member) != IPP_TAG_NAME &&
+          ippGetValueTag(member) != IPP_TAG_NAMELANG &&
+          ippGetValueTag(member) != IPP_TAG_KEYWORD))
+      {
+       respond_unsupported(client, attr);
+       valid = 0;
+      }
+      else
+      {
+       supported = ippFindAttribute(client->printer->attrs, "media-supported", IPP_TAG_KEYWORD);
+
+       if (!ippContainsString(supported, ippGetString(member, 0, NULL)))
+       {
+         respond_unsupported(client, attr);
+         valid = 0;
+       }
+      }
+    }
+    else if ((member = ippFindAttribute(col, "media-size", IPP_TAG_BEGIN_COLLECTION)) != NULL)
+    {
+      if (ippGetCount(member) != 1)
+      {
+       respond_unsupported(client, attr);
+       valid = 0;
+      }
+      else
+      {
+       size = ippGetCollection(member, 0);
+
+       if ((x_dim = ippFindAttribute(size, "x-dimension", IPP_TAG_INTEGER)) == NULL || ippGetCount(x_dim) != 1 ||
+           (y_dim = ippFindAttribute(size, "y-dimension", IPP_TAG_INTEGER)) == NULL || ippGetCount(y_dim) != 1)
+       {
+         respond_unsupported(client, attr);
+         valid = 0;
+       }
+       else
+       {
+         x_value   = ippGetInteger(x_dim, 0);
+         y_value   = ippGetInteger(y_dim, 0);
+         supported = ippFindAttribute(client->printer->attrs, "media-size-supported", IPP_TAG_BEGIN_COLLECTION);
+         count     = ippGetCount(supported);
+
+         for (i = 0; i < count ; i ++)
+         {
+           size  = ippGetCollection(supported, i);
+           x_dim = ippFindAttribute(size, "x-dimension", IPP_TAG_ZERO);
+           y_dim = ippFindAttribute(size, "y-dimension", IPP_TAG_ZERO);
+
+           if (ippContainsInteger(x_dim, x_value) && ippContainsInteger(y_dim, y_value))
+             break;
+         }
+
+         if (i >= count)
+         {
+           respond_unsupported(client, attr);
+           valid = 0;
+         }
+       }
+      }
+    }
   }
 
   if ((attr = ippFindAttribute(client->request, "multiple-document-handling", IPP_TAG_ZERO)) != NULL)
@@ -6414,8 +7336,7 @@ valid_job_attributes(
     }
     else
     {
-      int      count,                  /* Number of supported values */
-               xdpi,                   /* Horizontal resolution for job template attribute */
+      int      xdpi,                   /* Horizontal resolution for job template attribute */
                ydpi,                   /* Vertical resolution for job template attribute */
                sydpi;                  /* Vertical resolution for supported value */
       ipp_res_t        units,                  /* Units for job template attribute */
@@ -6465,8 +7386,3 @@ valid_job_attributes(
 
   return (valid);
 }
-
-
-/*
- * End of "$Id$".
- */