]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - test/ippserver.c
Import CUPS v2.0b1
[thirdparty/cups.git] / test / ippserver.c
index 0a849596228d9f38a804ec0823cdc4040bb909b0..8ee491fbfc012df42936a7847dfbe9f2dace4597 100644 (file)
@@ -1,79 +1,45 @@
 /*
- * "$Id$"
+ * "$Id: ippserver.c 11986 2014-07-02 15:52:01Z msweet $"
  *
- *   Sample IPP/2.0 server for CUPS.
+ * Sample IPP/2.0 server for CUPS.
  *
- *   Copyright 2010-2011 by Apple Inc.
+ * Copyright 2010-2014 by Apple Inc.
  *
- *   These coded instructions, statements, and computer programs are the
- *   property of Apple Inc. and are protected by Federal copyright
- *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
- *   which should have been included with this file.  If this file is
- *   file is missing or damaged, see the license at "http://www.cups.org/".
+ * These coded instructions, statements, and computer programs are the
+ * property of Apple Inc. and are protected by Federal copyright
+ * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
+ * which should have been included with this file.  If this file is
+ * file is missing or damaged, see the license at "http://www.cups.org/".
  *
- *   This file is subject to the Apple OS-Developed Software exception.
- *
- * Contents:
- *
- *   main()                       - Main entry to the sample server.
- *   clean_jobs()                 - Clean out old (completed) jobs.
- *   compare_jobs()               - Compare two jobs.
- *   copy_attribute()             - Copy a single attribute.
- *   copy_attributes()            - Copy attributes from one request to another.
- *   copy_job_attrs()             - Copy job attributes to the response.
- *   create_client()              - Accept a new network connection and create a
- *                                  client object.
- *   create_job()                 - Create a new job object from a Print-Job or
- *                                  Create-Job request.
- *   create_listener()            - Create a listener socket.
- *   create_media_col()           - Create a media-col value.
- *   create_printer()             - Create, register, and listen for connections
- *                                  to a printer object.
- *   create_requested_array()     - Create an array for requested-attributes.
- *   debug_attributes()           - Print attributes in a request or response.
- *   delete_client()              - Close the socket and free all memory used by
- *                                  a client object.
- *   delete_job()                 - Remove from the printer and free all memory
- *                                  used by a job object.
- *   delete_printer()             - Unregister, close listen sockets, and free
- *                                  all memory used by a printer object.
- *   dnssd_callback()             - Handle Bonjour registration events.
- *   find_job()                   - Find a job specified in a request.
- *   html_escape()                - Write a HTML-safe string.
- *   html_printf()                - Send formatted text to the client, quoting
- *                                  as needed.
- *   ipp_cancel_job()             - Cancel a job.
- *   ipp_create_job()             - Create a job object.
- *   ipp_get_job_attributes()     - Get the attributes for a job object.
- *   ipp_get_jobs()               - Get a list of job objects.
- *   ipp_get_printer_attributes() - Get the attributes for a printer object.
- *   ipp_print_job()              - Create a job object with an attached
- *                                  document.
- *   ipp_send_document()          - Add an attached document to a job object
- *                                  created with Create-Job.
- *   ipp_validate_job()           - Validate job creation attributes.
- *   process_client()             - Process client requests on a thread.
- *   process_http()               - Process a HTTP request.
- *   process_ipp()                - Process an IPP request.
- *   process_job()                - Process a print job.
- *   register_printer()           - Register a printer object via Bonjour.
- *   respond_http()               - Send a HTTP response.
- *   respond_ipp()                - Send an IPP response.
- *   run_printer()                - Run the printer service.
- *   usage()                      - Show program usage.
- *   valid_job_attributes()       - Determine whether the job attributes are
- *                                  valid.
+ * This file is subject to the Apple OS-Developed Software exception.
+ */
+
+/*
+ * Disable private and deprecated stuff so we can verify that the public API
+ * is sufficient to implement a server.
  */
 
+#define _IPP_PRIVATE_STRUCTURES 0      /* Disable private IPP stuff */
+#define _CUPS_NO_DEPRECATED 1          /* Disable deprecated stuff */
+
+
 /*
  * Include necessary headers...
  */
 
-#include <cups/cups-private.h>
+#include <cups/cups.h>                 /* Public API */
+#include <config.h>                    /* CUPS configuration header */
+#include <cups/string-private.h>       /* For string functions */
+#include <cups/thread-private.h>       /* For multithreading functions */
+
+#include <sys/wait.h>
+
 #ifdef HAVE_DNSSD
 #  include <dns_sd.h>
 #endif /* HAVE_DNSSD */
+#include <limits.h>
 #include <sys/stat.h>
+#include <sys/fcntl.h>
 #include <poll.h>
 #ifdef HAVE_SYS_MOUNT_H
 #  include <sys/mount.h>
 
 enum _ipp_preasons_e                   /* printer-state-reasons bit values */
 {
-  _IPP_PRINTER_NONE = 0x0000,          /* none */
-  _IPP_PRINTER_OTHER = 0x0001,         /* other */
-  _IPP_PRINTER_COVER_OPEN = 0x0002,    /* cover-open */
-  _IPP_PRINTER_INPUT_TRAY_MISSING = 0x0004,
+  _IPP_PSTATE_NONE = 0x0000,           /* none */
+  _IPP_PSTATE_OTHER = 0x0001,          /* other */
+  _IPP_PSTATE_COVER_OPEN = 0x0002,     /* cover-open */
+  _IPP_PSTATE_INPUT_TRAY_MISSING = 0x0004,
                                        /* input-tray-missing */
-  _IPP_PRINTER_MARKER_SUPPLY_EMPTY = 0x0008,
+  _IPP_PSTATE_MARKER_SUPPLY_EMPTY = 0x0008,
                                        /* marker-supply-empty */
-  _IPP_PRINTER_MARKER_SUPPLY_LOW = 0x0010,
+  _IPP_PSTATE_MARKER_SUPPLY_LOW = 0x0010,
                                        /* marker-suply-low */
-  _IPP_PRINTER_MARKER_WASTE_ALMOST_FULL = 0x0020,
+  _IPP_PSTATE_MARKER_WASTE_ALMOST_FULL = 0x0020,
                                        /* marker-waste-almost-full */
-  _IPP_PRINTER_MARKER_WASTE_FULL = 0x0040,
+  _IPP_PSTATE_MARKER_WASTE_FULL = 0x0040,
                                        /* marker-waste-full */
-  _IPP_PRINTER_MEDIA_EMPTY = 0x0080,   /* media-empty */
-  _IPP_PRINTER_MEDIA_JAM = 0x0100,     /* media-jam */
-  _IPP_PRINTER_MEDIA_LOW = 0x0200,     /* media-low */
-  _IPP_PRINTER_MEDIA_NEEDED = 0x0400,  /* media-needed */
-  _IPP_PRINTER_MOVING_TO_PAUSED = 0x0800,
+  _IPP_PSTATE_MEDIA_EMPTY = 0x0080,    /* media-empty */
+  _IPP_PSTATE_MEDIA_JAM = 0x0100,      /* media-jam */
+  _IPP_PSTATE_MEDIA_LOW = 0x0200,      /* media-low */
+  _IPP_PSTATE_MEDIA_NEEDED = 0x0400,   /* media-needed */
+  _IPP_PSTATE_MOVING_TO_PAUSED = 0x0800,
                                        /* moving-to-paused */
-  _IPP_PRINTER_PAUSED = 0x1000,                /* paused */
-  _IPP_PRINTER_SPOOL_AREA_FULL = 0x2000,/* spool-area-full */
-  _IPP_PRINTER_TONER_EMPTY = 0x4000,   /* toner-empty */
-  _IPP_PRINTER_TONER_LOW = 0x8000      /* toner-low */
+  _IPP_PSTATE_PAUSED = 0x1000,         /* paused */
+  _IPP_PSTATE_SPOOL_AREA_FULL = 0x2000,/* spool-area-full */
+  _IPP_PSTATE_TONER_EMPTY = 0x4000,    /* toner-empty */
+  _IPP_PSTATE_TONER_LOW = 0x8000       /* toner-low */
 };
 typedef unsigned int _ipp_preasons_t;  /* Bitfield for printer-state-reasons */
 
@@ -140,7 +106,7 @@ static const char * const media_supported[] =
   "na_index-3x5_3x5in",                        /* 3x5 */
   "oe_photo-l_3.5x5in",                        /* L */
   "na_index-4x6_4x6in",                        /* 4x6 */
-  "na_5x7_5x7in"                       /* 5x7 */
+  "na_5x7_5x7in"                       /* 5x7 aka 2L */
 };
 static const int media_col_sizes[][3] =
 {                                      /* media-col-database sizes */
@@ -154,7 +120,7 @@ static const int media_col_sizes[][3] =
   {  7630, 12700, _IPP_PHOTO_ONLY },   /* 3x5 */
   {  8890, 12700, _IPP_PHOTO_ONLY },   /* L */
   { 10160, 15240, _IPP_PHOTO_ONLY },   /* 4x6 */
-  { 12700, 17780, _IPP_PHOTO_ONLY }    /* 5x7 */
+  { 12700, 17780, _IPP_PHOTO_ONLY }    /* 5x7 aka 2L */
 };
 static const char * const media_type_supported[] =
                                      /* media-type-supported values */
@@ -188,6 +154,9 @@ typedef struct _ipp_printer_s               /**** Printer data ****/
 #ifdef HAVE_DNSSD
   DNSServiceRef                common_ref,     /* Shared service connection */
                        ipp_ref,        /* Bonjour IPP service */
+#  ifdef HAVE_SSL
+                       ipps_ref,       /* Bonjour IPPS service */
+#  endif /* HAVE_SSL */
                        http_ref,       /* Bonjour HTTP service */
                        printer_ref;    /* Bonjour LPD service */
   TXTRecordRef         ipp_txt;        /* Bonjour IPP TXT record */
@@ -197,7 +166,8 @@ typedef struct _ipp_printer_s               /**** Printer data ****/
                        *icon,          /* Icon filename */
                        *directory,     /* Spool directory */
                        *hostname,      /* Hostname */
-                       *uri;           /* printer-uri-supported */
+                       *uri,           /* printer-uri-supported */
+                       *command;       /* Command to run with job file */
   int                  port;           /* Port */
   size_t               urilen;         /* Length of printer URI */
   ipp_t                        *attrs;         /* Static attributes */
@@ -212,7 +182,7 @@ typedef struct _ipp_printer_s               /**** Printer data ****/
 struct _ipp_job_s                      /**** Job data ****/
 {
   int                  id;             /* Job ID */
-  char                 *name,          /* job-name */
+  const char           *name,          /* job-name */
                        *username,      /* job-originating-user-name */
                        *format;        /* document-format */
   ipp_jstate_t         state;          /* job-state value */
@@ -227,7 +197,7 @@ struct _ipp_job_s                   /**** Job data ****/
 
 typedef struct _ipp_client_s           /**** Client data ****/
 {
-  http_t               http;           /* HTTP connection */
+  http_t               *http;          /* HTTP connection */
   ipp_t                        *request,       /* IPP request */
                        *response;      /* IPP response */
   time_t               start;          /* Request start time */
@@ -235,6 +205,7 @@ typedef struct _ipp_client_s                /**** Client data ****/
   ipp_op_t             operation_id;   /* IPP operation-id */
   char                 uri[1024];      /* Request URI */
   http_addr_t          addr;           /* Client address */
+  char                 hostname[256];  /* Client hostname */
   _ipp_printer_t       *printer;       /* Printer */
   _ipp_job_t           *job;           /* Current job, if any */
 } _ipp_client_t;
@@ -246,8 +217,6 @@ typedef struct _ipp_client_s                /**** Client data ****/
 
 static void            clean_jobs(_ipp_printer_t *printer);
 static int             compare_jobs(_ipp_job_t *a, _ipp_job_t *b);
-static ipp_attribute_t *copy_attribute(ipp_t *to, ipp_attribute_t *attr,
-                                       ipp_tag_t group_tag, int quickcopy);
 static void            copy_attributes(ipp_t *to, ipp_t *from, cups_array_t *ra,
                                        ipp_tag_t group_tag, int quickcopy);
 static void            copy_job_attributes(_ipp_client_t *client,
@@ -257,18 +226,21 @@ static _ipp_job_t *create_job(_ipp_client_t *client);
 static int             create_listener(int family, int *port);
 static ipp_t           *create_media_col(const char *media, const char *type,
                                          int width, int length, int margins);
+static ipp_t           *create_media_size(int width, int length);
 static _ipp_printer_t  *create_printer(const char *servername,
                                        const char *name, const char *location,
                                        const char *make, const char *model,
                                        const char *icon,
                                        const char *docformats, int ppm,
                                        int ppm_color, int duplex, int port,
+                                       int pin,
 #ifdef HAVE_DNSSD
-                                       const char *regtype,
+                                       const char *subtype,
 #endif /* HAVE_DNSSD */
-                                       const char *directory);
-static cups_array_t    *create_requested_array(_ipp_client_t *client);
-static void            debug_attributes(const char *title, ipp_t *ipp);
+                                       const char *directory,
+                                       const char *command);
+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);
@@ -285,22 +257,17 @@ static _ipp_job_t *find_job(_ipp_client_t *client);
 static void            html_escape(_ipp_client_t *client, const char *s,
                                    size_t slen);
 static void            html_printf(_ipp_client_t *client, const char *format,
-                                   ...)
-#  ifdef __GNUC__
-__attribute__ ((__format__ (__printf__, 2, 3)))
-#  endif /* __GNUC__ */
-;
+                                   ...) __attribute__((__format__(__printf__,
+                                                                  2, 3)));
 static void            ipp_cancel_job(_ipp_client_t *client);
-#if 0
 static void            ipp_create_job(_ipp_client_t *client);
-#endif /* 0 */
 static void            ipp_get_job_attributes(_ipp_client_t *client);
 static void            ipp_get_jobs(_ipp_client_t *client);
 static void            ipp_get_printer_attributes(_ipp_client_t *client);
 static void            ipp_print_job(_ipp_client_t *client);
-#if 0
+static void            ipp_print_uri(_ipp_client_t *client);
 static void            ipp_send_document(_ipp_client_t *client);
-#endif /* 0 */
+static void            ipp_send_uri(_ipp_client_t *client);
 static void            ipp_validate_job(_ipp_client_t *client);
 static void            *process_client(_ipp_client_t *client);
 static int             process_http(_ipp_client_t *client);
@@ -314,15 +281,16 @@ static int                register_printer(_ipp_printer_t *printer,
                                         int duplex, const char *regtype);
 #endif /* HAVE_DNSSD */
 static int             respond_http(_ipp_client_t *client, http_status_t code,
+                                    const char *content_coding,
                                     const char *type, size_t length);
 static void            respond_ipp(_ipp_client_t *client, ipp_status_t status,
                                    const char *message, ...)
-#ifdef __GNUC__
-__attribute__ ((__format__ (__printf__, 3, 4)))
-#endif /* __GNUC__ */
-;
+                       __attribute__ ((__format__ (__printf__, 3, 4)));
+static void            respond_unsupported(_ipp_client_t *client,
+                                           ipp_attribute_t *attr);
 static void            run_printer(_ipp_printer_t *printer);
-static void            usage(int status);
+static void            usage(int status) __attribute__((noreturn));
+static int             valid_doc_attributes(_ipp_client_t *client);
 static int             valid_job_attributes(_ipp_client_t *client);
 
 
@@ -344,21 +312,26 @@ main(int  argc,                           /* I - Number of command-line args */
 {
   int          i;                      /* Looping var */
   const char   *opt,                   /* Current option character */
+               *command = NULL,        /* Command to run with job files */
                *servername = NULL,     /* Server host name */
                *name = NULL,           /* Printer name */
                *location = "",         /* Location of printer */
                *make = "Test",         /* Manufacturer */
                *model = "Printer",     /* Model */
                *icon = "printer.png",  /* Icon file */
-               *formats = "application/pdf,image/jpeg";
+               *formats = "application/pdf,image/jpeg,image/pwg-raster";
                                        /* Supported formats */
+#ifdef HAVE_SSL
+  const char   *keypath = NULL;        /* Keychain path */
+#endif /* HAVE_SSL */
 #ifdef HAVE_DNSSD
-  const char   *regtype = "_ipp._tcp"; /* Bonjour service type */
+  const char   *subtype = "_print";    /* Bonjour service subtype */
 #endif /* HAVE_DNSSD */
-  int          port = 8631,            /* Port number (0 = auto) TODO: FIX */
+  int          port = 8631,            /* Port number (0 = auto) */
                duplex = 0,             /* Duplex mode */
                ppm = 10,               /* Pages per minute for mono */
-               ppm_color = 0;          /* Pages per minute for color */
+               ppm_color = 0,          /* Pages per minute for color */
+               pin = 0;                /* PIN printing mode? */
   char         directory[1024] = "";   /* Spool directory */
   _ipp_printer_t *printer;             /* Printer object */
 
@@ -371,12 +344,22 @@ main(int  argc,                           /* I - Number of command-line args */
     if (argv[i][0] == '-')
     {
       for (opt = argv[i] + 1; *opt; opt ++)
+      {
         switch (*opt)
        {
          case '2' : /* -2 (enable 2-sided printing) */
              duplex = 1;
              break;
 
+#ifdef HAVE_SSL
+         case 'K' : /* -K keypath */
+             i ++;
+             if (i >= argc)
+               usage(1);
+             keypath = argv[i];
+             break;
+#endif /* HAVE_SSL */
+
          case 'M' : /* -M manufacturer */
              i ++;
              if (i >= argc)
@@ -384,6 +367,18 @@ main(int  argc,                            /* I - Number of command-line args */
              make = argv[i];
              break;
 
+          case 'P' : /* -P (PIN printing mode) */
+              pin = 1;
+              break;
+
+          case 'c' : /* -c command */
+              i ++;
+             if (i >= argc)
+               usage(1);
+
+             command = argv[i];
+             break;
+
          case 'd' : /* -d spool-directory */
              i ++;
              if (i >= argc)
@@ -400,7 +395,6 @@ main(int  argc,                             /* I - Number of command-line args */
 
           case 'h' : /* -h (show help) */
              usage(0);
-             break;
 
          case 'i' : /* -i icon.png */
              i ++;
@@ -442,11 +436,11 @@ main(int  argc,                           /* I - Number of command-line args */
              break;
 
 #ifdef HAVE_DNSSD
-         case 'r' : /* -r regtype */
+         case 'r' : /* -r subtype */
              i ++;
              if (i >= argc)
                usage(1);
-             regtype = argv[i];
+             subtype = argv[i];
              break;
 #endif /* HAVE_DNSSD */
 
@@ -465,8 +459,8 @@ main(int  argc,                             /* I - Number of command-line args */
           default : /* Unknown */
              fprintf(stderr, "Unknown option \"-%c\".\n", *opt);
              usage(1);
-             break;
        }
+      }
     }
     else if (!name)
     {
@@ -500,16 +494,20 @@ main(int  argc,                           /* I - Number of command-line args */
       fprintf(stderr, "Using spool directory \"%s\".\n", directory);
   }
 
+#ifdef HAVE_SSL
+  cupsSetServerCredentials(keypath, servername, 1);
+#endif /* HAVE_SSL */
+
  /*
   * Create the printer...
   */
 
   if ((printer = create_printer(servername, name, location, make, model, icon,
-                                formats, ppm, ppm_color, duplex, port,
+                                formats, ppm, ppm_color, duplex, port, pin,
 #ifdef HAVE_DNSSD
-                               regtype,
+                               subtype,
 #endif /* HAVE_DNSSD */
-                               directory)) == NULL)
+                               directory, command)) == NULL)
     return (1);
 
  /*
@@ -571,191 +569,6 @@ compare_jobs(_ipp_job_t *a,               /* I - First job */
 }
 
 
-/*
- * 'copy_attribute()' - Copy a single attribute.
- */
-
-static ipp_attribute_t *               /* O - New attribute */
-copy_attribute(
-    ipp_t           *to,               /* O - Destination request/response */
-    ipp_attribute_t *attr,             /* I - Attribute to copy */
-    ipp_tag_t       group_tag,         /* I - Group to put the copy in */
-    int             quickcopy)         /* I - Do a quick copy? */
-{
-  int                  i;              /* Looping var */
-  ipp_attribute_t      *toattr;        /* Destination attribute */
-
-
-  if (Verbosity && attr->name)
-  {
-    char       buffer[2048];           /* Attribute value */
-
-    _ippAttrString(attr, buffer, sizeof(buffer));
-
-    fprintf(stderr, "Copying %s (%s%s) %s\n", attr->name,
-           attr->num_values > 1 ? "1setOf " : "",
-           ippTagString(attr->value_tag & ~IPP_TAG_COPY), buffer);
-  }
-
-  switch (attr->value_tag & ~IPP_TAG_COPY)
-  {
-    case IPP_TAG_ZERO :
-        toattr = ippAddSeparator(to);
-       break;
-
-    case IPP_TAG_INTEGER :
-    case IPP_TAG_ENUM :
-        toattr = ippAddIntegers(to, group_tag, attr->value_tag,
-                               attr->name, attr->num_values, NULL);
-
-        for (i = 0; i < attr->num_values; i ++)
-         toattr->values[i].integer = attr->values[i].integer;
-        break;
-
-    case IPP_TAG_BOOLEAN :
-        toattr = ippAddBooleans(to, group_tag, attr->name,
-                               attr->num_values, NULL);
-
-        for (i = 0; i < attr->num_values; i ++)
-         toattr->values[i].boolean = attr->values[i].boolean;
-        break;
-
-    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 :
-        toattr = ippAddStrings(to, group_tag,
-                              (ipp_tag_t)(attr->value_tag | quickcopy),
-                              attr->name, attr->num_values, NULL, NULL);
-
-        if (quickcopy)
-       {
-          for (i = 0; i < attr->num_values; i ++)
-           toattr->values[i].string.text = attr->values[i].string.text;
-        }
-       else
-       {
-          for (i = 0; i < attr->num_values; i ++)
-           toattr->values[i].string.text =
-               _cupsStrAlloc(attr->values[i].string.text);
-       }
-        break;
-
-    case IPP_TAG_DATE :
-        toattr = ippAddDate(to, group_tag, attr->name,
-                           attr->values[0].date);
-        break;
-
-    case IPP_TAG_RESOLUTION :
-        toattr = ippAddResolutions(to, group_tag, attr->name,
-                                  attr->num_values, IPP_RES_PER_INCH,
-                                  NULL, NULL);
-
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         toattr->values[i].resolution.xres  = attr->values[i].resolution.xres;
-         toattr->values[i].resolution.yres  = attr->values[i].resolution.yres;
-         toattr->values[i].resolution.units = attr->values[i].resolution.units;
-       }
-        break;
-
-    case IPP_TAG_RANGE :
-        toattr = ippAddRanges(to, group_tag, attr->name,
-                             attr->num_values, NULL, NULL);
-
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         toattr->values[i].range.lower = attr->values[i].range.lower;
-         toattr->values[i].range.upper = attr->values[i].range.upper;
-       }
-        break;
-
-    case IPP_TAG_TEXTLANG :
-    case IPP_TAG_NAMELANG :
-        toattr = ippAddStrings(to, group_tag,
-                              (ipp_tag_t)(attr->value_tag | quickcopy),
-                              attr->name, attr->num_values, NULL, NULL);
-
-        if (quickcopy)
-       {
-          for (i = 0; i < attr->num_values; i ++)
-         {
-            toattr->values[i].string.charset = attr->values[i].string.charset;
-           toattr->values[i].string.text    = attr->values[i].string.text;
-          }
-        }
-       else
-       {
-          for (i = 0; i < attr->num_values; i ++)
-         {
-           if (!i)
-              toattr->values[i].string.charset =
-                 _cupsStrAlloc(attr->values[i].string.charset);
-           else
-              toattr->values[i].string.charset =
-                 toattr->values[0].string.charset;
-
-           toattr->values[i].string.text =
-               _cupsStrAlloc(attr->values[i].string.text);
-          }
-        }
-        break;
-
-    case IPP_TAG_BEGIN_COLLECTION :
-        toattr = ippAddCollections(to, group_tag, attr->name,
-                                  attr->num_values, NULL);
-
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         toattr->values[i].collection = attr->values[i].collection;
-         attr->values[i].collection->use ++;
-       }
-        break;
-
-    case IPP_TAG_STRING :
-        if (quickcopy)
-       {
-         toattr = ippAddOctetString(to, group_tag, attr->name, NULL, 0);
-         toattr->value_tag |= quickcopy;
-         toattr->values[0].unknown.data   = attr->values[0].unknown.data;
-         toattr->values[0].unknown.length = attr->values[0].unknown.length;
-       }
-       else
-         toattr = ippAddOctetString(to, attr->group_tag, attr->name,
-                                    attr->values[0].unknown.data,
-                                    attr->values[0].unknown.length);
-        break;
-
-    default :
-        toattr = ippAddIntegers(to, group_tag, attr->value_tag,
-                               attr->name, attr->num_values, NULL);
-
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         toattr->values[i].unknown.length = attr->values[i].unknown.length;
-
-         if (toattr->values[i].unknown.length > 0)
-         {
-           if ((toattr->values[i].unknown.data =
-                    malloc(toattr->values[i].unknown.length)) == NULL)
-             toattr->values[i].unknown.length = 0;
-           else
-             memcpy(toattr->values[i].unknown.data,
-                    attr->values[i].unknown.data,
-                    toattr->values[i].unknown.length);
-         }
-       }
-        break; /* anti-compiler-warning-code */
-  }
-
-  return (toattr);
-}
-
-
 /*
  * 'copy_attributes()' - Copy attributes from one request to another.
  */
@@ -773,18 +586,23 @@ copy_attributes(ipp_t        *to, /* I - Destination request */
   if (!to || !from)
     return;
 
-  for (fromattr = from->attrs; fromattr; fromattr = fromattr->next)
+  for (fromattr = ippFirstAttribute(from);
+       fromattr;
+       fromattr = ippNextAttribute(from))
   {
    /*
     * Filter attributes as needed...
     */
 
-    if ((group_tag != IPP_TAG_ZERO && fromattr->group_tag != group_tag &&
-         fromattr->group_tag != IPP_TAG_ZERO) || !fromattr->name)
+    ipp_tag_t fromgroup = ippGetGroupTag(fromattr);
+    const char *fromname = ippGetName(fromattr);
+
+    if ((group_tag != IPP_TAG_ZERO && fromgroup != group_tag &&
+         fromgroup != IPP_TAG_ZERO) || !fromname)
       continue;
 
-    if (!ra || cupsArrayFind(ra, fromattr->name))
-      copy_attribute(to, fromattr, fromattr->group_tag, quickcopy);
+    if (!ra || cupsArrayFind(ra, (void *)fromname))
+      ippCopyAttribute(to, fromattr, quickcopy);
   }
 }
 
@@ -799,7 +617,7 @@ copy_job_attributes(
     _ipp_job_t    *job,                        /* I - Job */
     cups_array_t  *ra)                 /* I - requested-attributes */
 {
-  copy_attributes(client->response, job->attrs, ra, IPP_TAG_ZERO, 0);
+  copy_attributes(client->response, job->attrs, ra, IPP_TAG_JOB, 0);
 
   if (!ra || cupsArrayFind(ra, "job-printer-up-time"))
     ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_INTEGER,
@@ -813,55 +631,59 @@ copy_job_attributes(
   {
     switch (job->state)
     {
-      case IPP_JOB_PENDING :
+      case IPP_JSTATE_PENDING :
          ippAddString(client->response, IPP_TAG_JOB,
-                      IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
+                      IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons",
                       NULL, "none");
          break;
 
-      case IPP_JOB_HELD :
+      case IPP_JSTATE_HELD :
           if (job->fd >= 0)
            ippAddString(client->response, IPP_TAG_JOB,
-                        IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
-                        NULL, "job-incoming");
+                        IPP_CONST_TAG(IPP_TAG_KEYWORD),
+                        "job-state-reasons", NULL, "job-incoming");
          else if (ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_ZERO))
            ippAddString(client->response, IPP_TAG_JOB,
-                        IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
-                        NULL, "job-hold-until-specified");
+                        IPP_CONST_TAG(IPP_TAG_KEYWORD),
+                        "job-state-reasons", NULL, "job-hold-until-specified");
+          else
+           ippAddString(client->response, IPP_TAG_JOB,
+                        IPP_CONST_TAG(IPP_TAG_KEYWORD),
+                        "job-state-reasons", NULL, "job-data-insufficient");
          break;
 
-      case IPP_JOB_PROCESSING :
+      case IPP_JSTATE_PROCESSING :
          if (job->cancel)
            ippAddString(client->response, IPP_TAG_JOB,
-                        IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
-                        NULL, "processing-to-stop-point");
+                        IPP_CONST_TAG(IPP_TAG_KEYWORD),
+                        "job-state-reasons", NULL, "processing-to-stop-point");
          else
            ippAddString(client->response, IPP_TAG_JOB,
-                        IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
-                        NULL, "job-printing");
+                        IPP_CONST_TAG(IPP_TAG_KEYWORD),
+                        "job-state-reasons", NULL, "job-printing");
          break;
 
-      case IPP_JOB_STOPPED :
+      case IPP_JSTATE_STOPPED :
          ippAddString(client->response, IPP_TAG_JOB,
-                      IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
+                      IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons",
                       NULL, "job-stopped");
          break;
 
-      case IPP_JOB_CANCELED :
+      case IPP_JSTATE_CANCELED :
          ippAddString(client->response, IPP_TAG_JOB,
-                      IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
+                      IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons",
                       NULL, "job-canceled-by-user");
          break;
 
-      case IPP_JOB_ABORTED :
+      case IPP_JSTATE_ABORTED :
          ippAddString(client->response, IPP_TAG_JOB,
-                      IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
+                      IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons",
                       NULL, "aborted-by-system");
          break;
 
-      case IPP_JOB_COMPLETED :
+      case IPP_JSTATE_COMPLETED :
          ippAddString(client->response, IPP_TAG_JOB,
-                      IPP_TAG_KEYWORD | IPP_TAG_COPY, "job-state-reasons",
+                      IPP_CONST_TAG(IPP_TAG_KEYWORD), "job-state-reasons",
                       NULL, "job-completed-successfully");
          break;
     }
@@ -870,12 +692,12 @@ copy_job_attributes(
   if (!ra || cupsArrayFind(ra, "time-at-completed"))
     ippAddInteger(client->response, IPP_TAG_JOB,
                   job->completed ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE,
-                  "time-at-completed", job->completed);
+                  "time-at-completed", (int)job->completed);
 
   if (!ra || cupsArrayFind(ra, "time-at-processing"))
     ippAddInteger(client->response, IPP_TAG_JOB,
                   job->processing ? IPP_TAG_INTEGER : IPP_TAG_NOVALUE,
-                  "time-at-processing", job->processing);
+                  "time-at-processing", (int)job->processing);
 }
 
 
@@ -889,8 +711,6 @@ create_client(_ipp_printer_t *printer,      /* I - Printer */
               int            sock)     /* I - Listen socket */
 {
   _ipp_client_t        *client;                /* Client */
-  int          val;                    /* Parameter value */
-  socklen_t    addrlen;                /* Length of address */
 
 
   if ((client = calloc(1, sizeof(_ipp_client_t))) == NULL)
@@ -899,19 +719,13 @@ create_client(_ipp_printer_t *printer,    /* I - Printer */
     return (NULL);
   }
 
-  client->printer       = printer;
-  client->http.activity = time(NULL);
-  client->http.hostaddr = &(client->addr);
-  client->http.blocking = 1;
+  client->printer = printer;
 
  /*
   * Accept the client and get the remote address...
   */
 
-  addrlen = sizeof(http_addr_t);
-
-  if ((client->http.fd = accept(sock, (struct sockaddr *)&(client->addr),
-                                &addrlen)) < 0)
+  if ((client->http = httpAcceptConnection(sock, 1)) == NULL)
   {
     perror("Unable to accept client connection");
 
@@ -920,23 +734,10 @@ create_client(_ipp_printer_t *printer,    /* I - Printer */
     return (NULL);
   }
 
-  httpAddrString(&(client->addr), client->http.hostname,
-                sizeof(client->http.hostname));
+  httpGetHostname(client->http, client->hostname, sizeof(client->hostname));
 
   if (Verbosity)
-    fprintf(stderr, "Accepted connection from %s (%s)\n", client->http.hostname,
-           client->http.hostaddr->addr.sa_family == AF_INET ? "IPv4" : "IPv6");
-
- /*
-  * Using TCP_NODELAY improves responsiveness, especially on systems
-  * with a slow loopback interface.  Since we write large buffers
-  * when sending print files and requests, there shouldn't be any
-  * performance penalty for this...
-  */
-
-  val = 1;
-  setsockopt(client->http.fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val,
-             sizeof(val));
+    fprintf(stderr, "Accepted connection from %s\n", client->hostname);
 
   return (client);
 }
@@ -957,7 +758,7 @@ create_job(_ipp_client_t *client)   /* I - Client */
 
   _cupsRWLockWrite(&(client->printer->rwlock));
   if (client->printer->active_job &&
-      client->printer->active_job->state < IPP_JOB_CANCELED)
+      client->printer->active_job->state < IPP_JSTATE_CANCELED)
   {
    /*
     * Only accept a single job at a time...
@@ -979,7 +780,7 @@ create_job(_ipp_client_t *client)   /* I - Client */
 
   job->printer    = client->printer;
   job->attrs      = client->request;
-  job->state      = IPP_JOB_HELD;
+  job->state      = IPP_JSTATE_HELD;
   job->fd         = -1;
   client->request = NULL;
 
@@ -987,8 +788,12 @@ create_job(_ipp_client_t *client)  /* I - Client */
   * Set all but the first two attributes to the job attributes group...
   */
 
-  for (attr = job->attrs->attrs->next->next; attr; attr = attr->next)
-    attr->group_tag = IPP_TAG_JOB;
+  for (ippFirstAttribute(job->attrs),
+           ippNextAttribute(job->attrs),
+           attr = ippNextAttribute(job->attrs);
+       attr;
+       attr = ippNextAttribute(job->attrs))
+    ippSetGroupTag(job->attrs, &attr, IPP_TAG_JOB);
 
  /*
   * Get the requesting-user-name, document format, and priority...
@@ -996,22 +801,20 @@ create_job(_ipp_client_t *client) /* I - Client */
 
   if ((attr = ippFindAttribute(job->attrs, "requesting-user-name",
                                IPP_TAG_NAME)) != NULL)
-  {
-    _cupsStrFree(attr->name);
-    attr->name = _cupsStrAlloc("job-originating-user-name");
-  }
+    ippSetName(job->attrs, &attr, "job-originating-user-name");
   else
-    attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME | IPP_TAG_COPY,
+    attr = ippAddString(job->attrs, IPP_TAG_JOB,
+                        IPP_CONST_TAG(IPP_TAG_NAME),
                         "job-originating-user-name", NULL, "anonymous");
 
   if (attr)
-    job->username = attr->values[0].string.text;
+    job->username = ippGetString(attr, 0, NULL);
   else
     job->username = "anonymous";
 
   if ((attr = ippFindAttribute(job->attrs, "document-format",
                                IPP_TAG_MIMETYPE)) != NULL)
-    job->format = attr->values[0].string.text;
+    job->format = ippGetString(attr, 0, NULL);
   else
     job->format = "application/octet-stream";
 
@@ -1047,57 +850,24 @@ static int                               /* O  - Listener socket or -1 on error */
 create_listener(int family,            /* I  - Address family */
                 int *port)             /* IO - Port number */
 {
-  int          sock,                   /* Listener socket */
-               val;                    /* Socket value */
-  http_addr_t  address;                /* Listen address */
-  socklen_t    addrlen;                /* Length of listen address */
-
-
-  if ((sock = socket(family, SOCK_STREAM, 0)) < 0)
-    return (-1);
+  int                  sock;           /* Listener socket */
+  http_addrlist_t      *addrlist;      /* Listen address */
+  char                 service[255];   /* Service port */
 
-  val = 1;
-  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
-
-#ifdef IPV6_V6ONLY
-  if (family == AF_INET6)
-    setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
-#endif /* IPV6_V6ONLY */
 
   if (!*port)
   {
-   /*
-    * Get the auto-assigned port number for the IPv4 socket...
-    */
-
-    /* TODO: This code does not appear to work - port is always 0... */
-    addrlen = sizeof(address);
-    if (getsockname(sock, (struct sockaddr *)&address, &addrlen))
-    {
-      perror("getsockname() failed");
-      *port = 8631;
-    }
-    else
-      *port = _httpAddrPort(&address);
-
+    *port = 8000 + (getuid() % 1000);
     fprintf(stderr, "Listening on port %d.\n", *port);
   }
 
-  memset(&address, 0, sizeof(address));
-  address.addr.sa_family = family;
-  _httpAddrSetPort(&address, *port);
-
-  if (bind(sock, (struct sockaddr *)&address, httpAddrLength(&address)))
-  {
-    close(sock);
+  snprintf(service, sizeof(service), "%d", *port);
+  if ((addrlist = httpAddrGetList(NULL, family, service)) == NULL)
     return (-1);
-  }
 
-  if (listen(sock, 5))
-  {
-    close(sock);
-    return (-1);
-  }
+  sock = httpAddrListen(&(addrlist->addr), *port);
+
+  httpAddrFreeList(addrlist);
 
   return (sock);
 }
@@ -1115,15 +885,11 @@ create_media_col(const char *media,      /* I - Media name */
                 int        margins)    /* I - Value for margins */
 {
   ipp_t        *media_col = ippNew(),          /* media-col value */
-       *media_size = ippNew();         /* media-size value */
+       *media_size = create_media_size(width, length);
+                                       /* media-size value */
   char media_key[256];                 /* media-key value */
 
 
-  ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "x-dimension",
-                width);
-  ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "y-dimension",
-                length);
-
   snprintf(media_key, sizeof(media_key), "%s_%s%s", media, type,
            margins == 0 ? "_borderless" : "");
 
@@ -1147,6 +913,26 @@ create_media_col(const char *media,       /* I - Media name */
 }
 
 
+/*
+ * 'create_media_size()' - Create a media-size value.
+ */
+
+static ipp_t *                         /* O - media-col collection */
+create_media_size(int width,           /* I - x-dimension in 2540ths */
+                 int length)           /* I - y-dimension in 2540ths */
+{
+  ipp_t        *media_size = ippNew();         /* media-size value */
+
+
+  ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "x-dimension",
+                width);
+  ippAddInteger(media_size, IPP_TAG_PRINTER, IPP_TAG_INTEGER, "y-dimension",
+                length);
+
+  return (media_size);
+}
+
+
 /*
  * 'create_printer()' - Create, register, and listen for connections to a
  *                      printer object.
@@ -1164,10 +950,12 @@ create_printer(const char *servername,   /* I - Server hostname (NULL for default)
               int        ppm_color,    /* I - Pages per minute in color (0 for gray) */
               int        duplex,       /* I - 1 = duplex, 0 = simplex */
               int        port,         /* I - Port for listeners or 0 for auto */
+              int        pin,          /* I - Require PIN printing */
 #ifdef HAVE_DNSSD
-              const char *regtype,     /* I - Bonjour service type */
+              const char *subtype,     /* I - Bonjour service subtype */
 #endif /* HAVE_DNSSD */
-              const char *directory)   /* I - Spool directory */
+              const char *directory,   /* I - Spool directory */
+              const char *command)     /* I - Command to run on job files */
 {
   int                  i, j;           /* Looping vars */
   _ipp_printer_t       *printer;       /* Printer */
@@ -1183,12 +971,13 @@ create_printer(const char *servername,   /* I - Server hostname (NULL for default)
                        *ptr;           /* Pointer into string */
   const char           *prefix;        /* Prefix string */
   int                  num_database;   /* Number of database values */
-  ipp_attribute_t      *media_col_database;
+  ipp_attribute_t      *media_col_database,
                                        /* media-col-database value */
+                       *media_size_supported;
+                                       /* media-size-supported value */
   ipp_t                        *media_col_default;
                                        /* media-col-default value */
-  ipp_value_t          *media_col_value;
-                                       /* Current media-col-database value */
+  int                  media_col_index;/* Current media-col-database value */
   int                  k_supported;    /* Maximum file size supported */
 #ifdef HAVE_STATVFS
   struct statvfs       spoolinfo;      /* FS info for spool directory */
@@ -1199,10 +988,10 @@ create_printer(const char *servername,   /* I - Server hostname (NULL for default)
 #endif /* HAVE_STATVFS */
   static const int     orients[4] =    /* orientation-requested-supported values */
   {
-    IPP_PORTRAIT,
-    IPP_LANDSCAPE,
-    IPP_REVERSE_LANDSCAPE,
-    IPP_REVERSE_PORTRAIT
+    IPP_ORIENT_PORTRAIT,
+    IPP_ORIENT_LANDSCAPE,
+    IPP_ORIENT_REVERSE_LANDSCAPE,
+    IPP_ORIENT_REVERSE_PORTRAIT
   };
   static const char * const versions[] =/* ipp-versions-supported values */
   {
@@ -1212,25 +1001,38 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
   };
   static const int     ops[] =         /* operations-supported values */
   {
-    IPP_PRINT_JOB,
-    IPP_VALIDATE_JOB,
-    IPP_CREATE_JOB,
-    IPP_SEND_DOCUMENT,
-    IPP_CANCEL_JOB,
-    IPP_GET_JOB_ATTRIBUTES,
-    IPP_GET_JOBS,
-    IPP_GET_PRINTER_ATTRIBUTES
+    IPP_OP_PRINT_JOB,
+    IPP_OP_PRINT_URI,
+    IPP_OP_VALIDATE_JOB,
+    IPP_OP_CREATE_JOB,
+    IPP_OP_SEND_DOCUMENT,
+    IPP_OP_SEND_URI,
+    IPP_OP_CANCEL_JOB,
+    IPP_OP_GET_JOB_ATTRIBUTES,
+    IPP_OP_GET_JOBS,
+    IPP_OP_GET_PRINTER_ATTRIBUTES
   };
   static const char * const charsets[] =/* charset-supported values */
   {
     "us-ascii",
     "utf-8"
   };
+  static const char * const compressions[] =/* compression-supported values */
+  {
+#ifdef HAVE_LIBZ
+    "deflate",
+    "gzip",
+#endif /* HAVE_LIBZ */
+    "none"
+  };
   static const char * const job_creation[] =
   {                                    /* job-creation-attributes-supported values */
     "copies",
     "ipp-attribute-fidelity",
+    "job-account-id",
+    "job-accounting-user-id",
     "job-name",
+    "job-password",
     "job-priority",
     "media",
     "media-col",
@@ -1264,6 +1066,29 @@ create_printer(const char *servername,   /* I - Server hostname (NULL for default)
     IPP_QUALITY_NORMAL,
     IPP_QUALITY_HIGH
   };
+  static const int     pwg_raster_document_resolution_supported[] =
+  {
+    150,
+    300,
+    600
+  };
+  static const char * const pwg_raster_document_type_supported[] =
+  {
+    "black-1",
+    "cmyk-8",
+    "sgray-8",
+    "srgb-8",
+    "srgb-16"
+  };
+  static const char * const reference_uri_schemes_supported[] =
+  {                                    /* reference-uri-schemes-supported */
+    "file",
+    "ftp",
+    "http"
+#ifdef HAVE_SSL
+    , "https"
+#endif /* HAVE_SSL */
+  };
   static const char * const sides_supported[] =
   {                                    /* sides-supported values */
     "one-sided",
@@ -1296,25 +1121,29 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
 
   printer->ipv4          = -1;
   printer->ipv6          = -1;
-  printer->name          = _cupsStrAlloc(name);
+  printer->name          = strdup(name);
 #ifdef HAVE_DNSSD
-  printer->dnssd_name    = _cupsStrRetain(printer->name);
+  printer->dnssd_name    = strdup(printer->name);
 #endif /* HAVE_DNSSD */
-  printer->directory     = _cupsStrAlloc(directory);
-  printer->hostname      = _cupsStrAlloc(servername ? servername :
+  printer->command       = command ? strdup(command) : NULL;
+  printer->directory     = strdup(directory);
+  printer->hostname      = strdup(servername ? servername :
                                              httpGetHostname(NULL, hostname,
                                                              sizeof(hostname)));
   printer->port          = port;
-  printer->state         = IPP_PRINTER_IDLE;
-  printer->state_reasons = _IPP_PRINTER_NONE;
+  printer->state         = IPP_PSTATE_IDLE;
+  printer->state_reasons = _IPP_PSTATE_NONE;
   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");
-  printer->uri    = _cupsStrAlloc(uri);
+                 printer->hostname, printer->port, "/ipp/print");
+  printer->uri    = strdup(uri);
   printer->urilen = strlen(uri);
 
+  if (icon)
+    printer->icon = strdup(icon);
+
   _cupsRWInit(&(printer->rwlock));
 
  /*
@@ -1368,18 +1197,17 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
   for (i = 0; i < num_formats; i ++)
   {
     if (!_cups_strcasecmp(formats[i], "application/pdf"))
-      snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPDF", prefix);
+      snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPDF", prefix);
     else if (!_cups_strcasecmp(formats[i], "application/postscript"))
-      snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPS", prefix);
+      snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPS", prefix);
     else if (!_cups_strcasecmp(formats[i], "application/vnd.hp-PCL"))
-      snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPCL", prefix);
+      snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPCL", prefix);
     else if (!_cups_strcasecmp(formats[i], "image/jpeg"))
-      snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sJPEG", prefix);
+      snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sJPEG", prefix);
     else if (!_cups_strcasecmp(formats[i], "image/png"))
-      snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPNG", prefix);
+      snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%sPNG", prefix);
     else if (_cups_strcasecmp(formats[i], "application/octet-stream"))
-      snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%s%s", prefix,
-               formats[i]);
+      snprintf(ptr, sizeof(device_id) - (size_t)(ptr - device_id), "%s%s", prefix, formats[i]);
 
     ptr += strlen(ptr);
     prefix = ",";
@@ -1422,11 +1250,13 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
   printer->attrs = ippNew();
 
   /* charset-configured */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_CHARSET | IPP_TAG_COPY,
+  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_TAG_CHARSET | IPP_TAG_COPY,
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER,
+                IPP_CONST_TAG(IPP_TAG_CHARSET),
                 "charset-supported", sizeof(charsets) / sizeof(charsets[0]),
                NULL, charsets);
 
@@ -1435,8 +1265,11 @@ create_printer(const char *servername,   /* I - Server hostname (NULL for default)
                 ppm_color > 0);
 
   /* compression-supported */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
-              "compression-supported", NULL, "none");
+  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,
@@ -1463,16 +1296,26 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
                 "finishings-supported", IPP_FINISHINGS_NONE);
 
   /* generated-natural-language-supported */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE | IPP_TAG_COPY,
+  ippAddString(printer->attrs, IPP_TAG_PRINTER,
+               IPP_CONST_TAG(IPP_TAG_LANGUAGE),
                "generated-natural-language-supported", NULL, "en");
 
   /* ipp-versions-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+  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-supported */
+  ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "job-account-id-supported", 1);
+
+  /* job-accounting-user-id-supported */
+  ippAddBoolean(printer->attrs, IPP_TAG_PRINTER,
+                "job-accounting-user-id-supported", 1);
+
   /* job-creation-attributes-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+  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);
@@ -1481,6 +1324,10 @@ create_printer(const char *servername,   /* I - Server hostname (NULL for default)
   ippAddRange(printer->attrs, IPP_TAG_PRINTER, "job-k-octets-supported", 0,
              k_supported);
 
+  /* job-password-supported */
+  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);
@@ -1490,11 +1337,13 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
                 "job-priority-supported", 100);
 
   /* job-sheets-default */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY,
+  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_TAG_NAME | IPP_TAG_COPY,
+  ippAddString(printer->attrs, IPP_TAG_PRINTER,
+               IPP_CONST_TAG(IPP_TAG_NAME),
                "job-sheets-supported", NULL, "none");
 
   /* media-bottom-margin-supported */
@@ -1522,7 +1371,7 @@ create_printer(const char *servername,    /* I - Server hostname (NULL for default)
   media_col_database = ippAddCollections(printer->attrs, IPP_TAG_PRINTER,
                                          "media-col-database", num_database,
                                         NULL);
-  for (media_col_value = media_col_database->values, i = 0;
+  for (media_col_index = 0, i = 0;
        i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0]));
        i ++)
   {
@@ -1540,11 +1389,13 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
               strncmp(media_type_supported[j], "photographic-", 13))
        continue;
 
-      media_col_value->collection =
-          create_media_col(media_supported[i], media_type_supported[j],
-                          media_col_sizes[i][0], media_col_sizes[i][1],
-                          media_xxx_margin_supported[1]);
-      media_col_value ++;
+      ippSetCollection(printer->attrs, &media_col_database, media_col_index,
+                       create_media_col(media_supported[i],
+                                        media_type_supported[j],
+                                       media_col_sizes[i][0],
+                                       media_col_sizes[i][1],
+                                       media_xxx_margin_supported[1]));
+      media_col_index ++;
 
       if (media_col_sizes[i][2] != _IPP_ENV_ONLY &&
          (!strcmp(media_type_supported[j], "auto") ||
@@ -1554,11 +1405,13 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
         * Add borderless version for this combination...
        */
 
-       media_col_value->collection =
-           create_media_col(media_supported[i], media_type_supported[j],
-                            media_col_sizes[i][0], media_col_sizes[i][1],
-                            media_xxx_margin_supported[0]);
-       media_col_value ++;
+       ippSetCollection(printer->attrs, &media_col_database, media_col_index,
+                         create_media_col(media_supported[i],
+                                          media_type_supported[j],
+                                         media_col_sizes[i][0],
+                                         media_col_sizes[i][1],
+                                         media_xxx_margin_supported[0]));
+       media_col_index ++;
       }
     }
   }
@@ -1575,14 +1428,16 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
   ippDelete(media_col_default);
 
   /* media-col-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+  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_TAG_KEYWORD | IPP_TAG_COPY,
+  ippAddString(printer->attrs, IPP_TAG_PRINTER,
+               IPP_CONST_TAG(IPP_TAG_KEYWORD),
                "media-default", NULL, media_supported[0]);
 
   /* media-left-margin-supported */
@@ -1600,11 +1455,25 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
                 media_xxx_margin_supported);
 
   /* media-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+  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]));
+
   /* media-top-margin-supported */
   ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                  "media-top-margin-supported",
@@ -1612,8 +1481,17 @@ create_printer(const char *servername,   /* I - Server hostname (NULL for default)
                       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);
+
   /* multiple-document-handling-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+  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,
@@ -1624,7 +1502,8 @@ create_printer(const char *servername,    /* I - Server hostname (NULL for default)
                 "multiple-document-jobs-supported", 0);
 
   /* natural-language-configured */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE | IPP_TAG_COPY,
+  ippAddString(printer->attrs, IPP_TAG_PRINTER,
+               IPP_CONST_TAG(IPP_TAG_LANGUAGE),
                "natural-language-configured", NULL, "en");
 
   /* number-up-default */
@@ -1648,11 +1527,13 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
                  "orientation-requested-supported", 4, orients);
 
   /* output-bin-default */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+  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_TAG_KEYWORD | IPP_TAG_COPY,
+  ippAddString(printer->attrs, IPP_TAG_PRINTER,
+               IPP_CONST_TAG(IPP_TAG_KEYWORD),
                "output-bin-supported", NULL, "face-down");
 
   /* pages-per-minute */
@@ -1665,7 +1546,8 @@ 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_TAG_KEYWORD | IPP_TAG_COPY,
+  ippAddString(printer->attrs, IPP_TAG_PRINTER,
+               IPP_CONST_TAG(IPP_TAG_KEYWORD),
                "pdl-override-supported", NULL, "attempted");
 
   /* print-quality-default */
@@ -1703,6 +1585,20 @@ create_printer(const char *servername,   /* I - Server hostname (NULL for default)
   ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
                "printer-make-and-model", NULL, make_model);
 
+  /* printer-mandatory-job-attributes */
+  if (pin)
+  {
+    static const char * const names[] =
+    {
+      "job-accounting-user-id",
+      "job-password"
+    };
+
+    ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+                  "printer-mandatory-job-attributes",
+                  (int)(sizeof(names) / sizeof(names[0])), NULL, names);
+  }
+
   /* printer-more-info */
   ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI,
                "printer-more-info", NULL, adminurl);
@@ -1723,30 +1619,66 @@ create_printer(const char *servername,  /* I - Server hostname (NULL for default)
   ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI,
                "printer-uri-supported", NULL, uri);
 
+  /* pwg-raster-document-xxx-supported */
+  for (i = 0; i < num_formats; i ++)
+    if (!_cups_strcasecmp(formats[i], "image/pwg-raster"))
+      break;
+
+  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);
+  }
+
+  /* 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);
+
   /* sides-default */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+  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_TAG_KEYWORD | IPP_TAG_COPY,
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER,
+                IPP_CONST_TAG(IPP_TAG_KEYWORD),
                 "sides-supported", duplex ? 3 : 1, NULL, sides_supported);
 
   /* uri-authentication-supported */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+  ippAddString(printer->attrs, IPP_TAG_PRINTER,
+               IPP_CONST_TAG(IPP_TAG_KEYWORD),
                "uri-authentication-supported", NULL, "none");
 
   /* uri-security-supported */
-  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+  ippAddString(printer->attrs, IPP_TAG_PRINTER,
+               IPP_CONST_TAG(IPP_TAG_KEYWORD),
                "uri-security-supported", NULL, "none");
 
   /* which-jobs-supported */
-  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+  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]);
 
-  debug_attributes("Printer", printer->attrs);
+  debug_attributes("Printer", printer->attrs, 0);
 
 #ifdef HAVE_DNSSD
  /*
@@ -1754,7 +1686,7 @@ create_printer(const char *servername,    /* I - Server hostname (NULL for default)
   */
 
   if (!register_printer(printer, location, make, model, docformats, adminurl,
-                        ppm_color > 0, duplex, regtype))
+                        ppm_color > 0, duplex, subtype))
     goto bad_printer;
 #endif /* HAVE_DNSSD */
 
@@ -1776,239 +1708,51 @@ create_printer(const char *servername, /* I - Server hostname (NULL for default)
 }
 
 
-/*
- * 'create_requested_array()' - Create an array for requested-attributes.
- */
-
-static cups_array_t *                  /* O - requested-attributes array */
-create_requested_array(
-    _ipp_client_t *client)             /* I - Client */
-{
-  int                  i;              /* Looping var */
-  ipp_attribute_t      *requested;     /* requested-attributes attribute */
-  cups_array_t         *ra;            /* Requested attributes array */
-  char                 *value;         /* Current value */
-
-
- /*
-  * Get the requested-attributes attribute, and return NULL if we don't
-  * have one...
-  */
-
-  if ((requested = ippFindAttribute(client->request, "requested-attributes",
-                                    IPP_TAG_KEYWORD)) == NULL)
-    return (NULL);
-
- /*
-  * If the attribute contains a single "all" keyword, return NULL...
-  */
-
-  if (requested->num_values == 1 &&
-      !strcmp(requested->values[0].string.text, "all"))
-    return (NULL);
-
- /*
-  * Create an array using "strcmp" as the comparison function...
-  */
-
-  ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
-
-  for (i = 0; i < requested->num_values; i ++)
-  {
-    value = requested->values[i].string.text;
-
-    if (!strcmp(value, "job-template"))
-    {
-      cupsArrayAdd(ra, "copies");
-      cupsArrayAdd(ra, "copies-default");
-      cupsArrayAdd(ra, "copies-supported");
-      cupsArrayAdd(ra, "finishings");
-      cupsArrayAdd(ra, "finishings-default");
-      cupsArrayAdd(ra, "finishings-supported");
-      cupsArrayAdd(ra, "job-hold-until");
-      cupsArrayAdd(ra, "job-hold-until-default");
-      cupsArrayAdd(ra, "job-hold-until-supported");
-      cupsArrayAdd(ra, "job-priority");
-      cupsArrayAdd(ra, "job-priority-default");
-      cupsArrayAdd(ra, "job-priority-supported");
-      cupsArrayAdd(ra, "job-sheets");
-      cupsArrayAdd(ra, "job-sheets-default");
-      cupsArrayAdd(ra, "job-sheets-supported");
-      cupsArrayAdd(ra, "media");
-      cupsArrayAdd(ra, "media-col");
-      cupsArrayAdd(ra, "media-col-default");
-      cupsArrayAdd(ra, "media-col-supported");
-      cupsArrayAdd(ra, "media-default");
-      cupsArrayAdd(ra, "media-source-supported");
-      cupsArrayAdd(ra, "media-supported");
-      cupsArrayAdd(ra, "media-type-supported");
-      cupsArrayAdd(ra, "multiple-document-handling");
-      cupsArrayAdd(ra, "multiple-document-handling-default");
-      cupsArrayAdd(ra, "multiple-document-handling-supported");
-      cupsArrayAdd(ra, "number-up");
-      cupsArrayAdd(ra, "number-up-default");
-      cupsArrayAdd(ra, "number-up-supported");
-      cupsArrayAdd(ra, "orientation-requested");
-      cupsArrayAdd(ra, "orientation-requested-default");
-      cupsArrayAdd(ra, "orientation-requested-supported");
-      cupsArrayAdd(ra, "page-ranges");
-      cupsArrayAdd(ra, "page-ranges-supported");
-      cupsArrayAdd(ra, "printer-resolution");
-      cupsArrayAdd(ra, "printer-resolution-default");
-      cupsArrayAdd(ra, "printer-resolution-supported");
-      cupsArrayAdd(ra, "print-quality");
-      cupsArrayAdd(ra, "print-quality-default");
-      cupsArrayAdd(ra, "print-quality-supported");
-      cupsArrayAdd(ra, "sides");
-      cupsArrayAdd(ra, "sides-default");
-      cupsArrayAdd(ra, "sides-supported");
-    }
-    else if (!strcmp(value, "job-description"))
-    {
-      cupsArrayAdd(ra, "date-time-at-completed");
-      cupsArrayAdd(ra, "date-time-at-creation");
-      cupsArrayAdd(ra, "date-time-at-processing");
-      cupsArrayAdd(ra, "job-detailed-status-message");
-      cupsArrayAdd(ra, "job-document-access-errors");
-      cupsArrayAdd(ra, "job-id");
-      cupsArrayAdd(ra, "job-impressions");
-      cupsArrayAdd(ra, "job-impressions-completed");
-      cupsArrayAdd(ra, "job-k-octets");
-      cupsArrayAdd(ra, "job-k-octets-processed");
-      cupsArrayAdd(ra, "job-media-sheets");
-      cupsArrayAdd(ra, "job-media-sheets-completed");
-      cupsArrayAdd(ra, "job-message-from-operator");
-      cupsArrayAdd(ra, "job-more-info");
-      cupsArrayAdd(ra, "job-name");
-      cupsArrayAdd(ra, "job-originating-user-name");
-      cupsArrayAdd(ra, "job-printer-up-time");
-      cupsArrayAdd(ra, "job-printer-uri");
-      cupsArrayAdd(ra, "job-state");
-      cupsArrayAdd(ra, "job-state-message");
-      cupsArrayAdd(ra, "job-state-reasons");
-      cupsArrayAdd(ra, "job-uri");
-      cupsArrayAdd(ra, "number-of-documents");
-      cupsArrayAdd(ra, "number-of-intervening-jobs");
-      cupsArrayAdd(ra, "output-device-assigned");
-      cupsArrayAdd(ra, "time-at-completed");
-      cupsArrayAdd(ra, "time-at-creation");
-      cupsArrayAdd(ra, "time-at-processing");
-    }
-    else if (!strcmp(value, "printer-description"))
-    {
-      cupsArrayAdd(ra, "charset-configured");
-      cupsArrayAdd(ra, "charset-supported");
-      cupsArrayAdd(ra, "color-supported");
-      cupsArrayAdd(ra, "compression-supported");
-      cupsArrayAdd(ra, "document-format-default");
-      cupsArrayAdd(ra, "document-format-supported");
-      cupsArrayAdd(ra, "generated-natural-language-supported");
-      cupsArrayAdd(ra, "ipp-versions-supported");
-      cupsArrayAdd(ra, "job-impressions-supported");
-      cupsArrayAdd(ra, "job-k-octets-supported");
-      cupsArrayAdd(ra, "job-media-sheets-supported");
-      cupsArrayAdd(ra, "multiple-document-jobs-supported");
-      cupsArrayAdd(ra, "multiple-operation-time-out");
-      cupsArrayAdd(ra, "natural-language-configured");
-      cupsArrayAdd(ra, "notify-attributes-supported");
-      cupsArrayAdd(ra, "notify-lease-duration-default");
-      cupsArrayAdd(ra, "notify-lease-duration-supported");
-      cupsArrayAdd(ra, "notify-max-events-supported");
-      cupsArrayAdd(ra, "notify-events-default");
-      cupsArrayAdd(ra, "notify-events-supported");
-      cupsArrayAdd(ra, "notify-pull-method-supported");
-      cupsArrayAdd(ra, "notify-schemes-supported");
-      cupsArrayAdd(ra, "operations-supported");
-      cupsArrayAdd(ra, "pages-per-minute");
-      cupsArrayAdd(ra, "pages-per-minute-color");
-      cupsArrayAdd(ra, "pdl-override-supported");
-      cupsArrayAdd(ra, "printer-alert");
-      cupsArrayAdd(ra, "printer-alert-description");
-      cupsArrayAdd(ra, "printer-current-time");
-      cupsArrayAdd(ra, "printer-driver-installer");
-      cupsArrayAdd(ra, "printer-info");
-      cupsArrayAdd(ra, "printer-is-accepting-jobs");
-      cupsArrayAdd(ra, "printer-location");
-      cupsArrayAdd(ra, "printer-make-and-model");
-      cupsArrayAdd(ra, "printer-message-from-operator");
-      cupsArrayAdd(ra, "printer-more-info");
-      cupsArrayAdd(ra, "printer-more-info-manufacturer");
-      cupsArrayAdd(ra, "printer-name");
-      cupsArrayAdd(ra, "printer-state");
-      cupsArrayAdd(ra, "printer-state-message");
-      cupsArrayAdd(ra, "printer-state-reasons");
-      cupsArrayAdd(ra, "printer-up-time");
-      cupsArrayAdd(ra, "printer-uri-supported");
-      cupsArrayAdd(ra, "queued-job-count");
-      cupsArrayAdd(ra, "reference-uri-schemes-supported");
-      cupsArrayAdd(ra, "uri-authentication-supported");
-      cupsArrayAdd(ra, "uri-security-supported");
-    }
-    else if (!strcmp(value, "printer-defaults"))
-    {
-      cupsArrayAdd(ra, "copies-default");
-      cupsArrayAdd(ra, "document-format-default");
-      cupsArrayAdd(ra, "finishings-default");
-      cupsArrayAdd(ra, "job-hold-until-default");
-      cupsArrayAdd(ra, "job-priority-default");
-      cupsArrayAdd(ra, "job-sheets-default");
-      cupsArrayAdd(ra, "media-default");
-      cupsArrayAdd(ra, "media-col-default");
-      cupsArrayAdd(ra, "number-up-default");
-      cupsArrayAdd(ra, "orientation-requested-default");
-      cupsArrayAdd(ra, "sides-default");
-    }
-    else if (!strcmp(value, "subscription-template"))
-    {
-      cupsArrayAdd(ra, "notify-attributes");
-      cupsArrayAdd(ra, "notify-charset");
-      cupsArrayAdd(ra, "notify-events");
-      cupsArrayAdd(ra, "notify-lease-duration");
-      cupsArrayAdd(ra, "notify-natural-language");
-      cupsArrayAdd(ra, "notify-pull-method");
-      cupsArrayAdd(ra, "notify-recipient-uri");
-      cupsArrayAdd(ra, "notify-time-interval");
-      cupsArrayAdd(ra, "notify-user-data");
-    }
-    else
-      cupsArrayAdd(ra, value);
-  }
-
-  return (ra);
-}
-
-
 /*
  * 'debug_attributes()' - Print attributes in a request or response.
  */
 
 static void
 debug_attributes(const char *title,    /* I - Title */
-                 ipp_t      *ipp)      /* I - Request/response */
+                 ipp_t      *ipp,      /* I - Request/response */
+                 int        type)      /* I - 0 = object, 1 = request, 2 = response */
 {
   ipp_tag_t            group_tag;      /* Current group */
   ipp_attribute_t      *attr;          /* Current attribute */
   char                 buffer[2048];   /* String buffer for value */
+  int                  major, minor;   /* Version */
 
 
   if (Verbosity <= 1)
     return;
 
   fprintf(stderr, "%s:\n", title);
-  for (attr = ipp->attrs, group_tag = IPP_TAG_ZERO; attr; attr = attr->next)
+  major = ippGetVersion(ipp, &minor);
+  fprintf(stderr, "  version=%d.%d\n", major, minor);
+  if (type == 1)
+    fprintf(stderr, "  operation-id=%s(%04x)\n",
+            ippOpString(ippGetOperation(ipp)), ippGetOperation(ipp));
+  else if (type == 2)
+    fprintf(stderr, "  status-code=%s(%04x)\n",
+            ippErrorString(ippGetStatusCode(ipp)), ippGetStatusCode(ipp));
+  fprintf(stderr, "  request-id=%d\n\n", ippGetRequestId(ipp));
+
+  for (attr = ippFirstAttribute(ipp), group_tag = IPP_TAG_ZERO;
+       attr;
+       attr = ippNextAttribute(ipp))
   {
-    if (attr->group_tag != group_tag)
+    if (ippGetGroupTag(attr) != group_tag)
     {
-      group_tag = attr->group_tag;
+      group_tag = ippGetGroupTag(attr);
       fprintf(stderr, "  %s\n", ippTagString(group_tag));
     }
 
-    if (attr->name)
+    if (ippGetName(attr))
     {
-      _ippAttrString(attr, buffer, sizeof(buffer));
-      fprintf(stderr, "    %s (%s%s) %s\n", attr->name,
-             attr->num_values > 1 ? "1setOf " : "",
-             ippTagString(attr->value_tag), buffer);
+      ippAttributeString(attr, buffer, sizeof(buffer));
+      fprintf(stderr, "    %s (%s%s) %s\n", ippGetName(attr),
+             ippGetCount(attr) > 1 ? "1setOf " : "",
+             ippTagString(ippGetValueTag(attr)), buffer);
     }
   }
 }
@@ -2023,27 +1767,21 @@ static void
 delete_client(_ipp_client_t *client)   /* I - Client */
 {
   if (Verbosity)
-    fprintf(stderr, "Closing connection from %s (%s)\n", client->http.hostname,
-           client->http.hostaddr->addr.sa_family == AF_INET ? "IPv4" : "IPv6");
+    fprintf(stderr, "Closing connection from %s\n", client->hostname);
 
  /*
   * Flush pending writes before closing...
   */
 
-  httpFlushWrite(&(client->http));
-
-  if (client->http.fd >= 0)
-    close(client->http.fd);
+  httpFlushWrite(client->http);
 
  /*
   * Free memory...
   */
 
-  httpClearCookie(&(client->http));
-  httpClearFields(&(client->http));
+  httpClose(client->http);
 
   ippDelete(client->request);
-
   ippDelete(client->response);
 
   free(client);
@@ -2096,6 +1834,10 @@ delete_printer(_ipp_printer_t *printer)  /* I - Printer */
   if (printer->ipp_ref)
     DNSServiceRefDeallocate(printer->ipp_ref);
 
+#  ifdef HAVE_SSL
+  if (printer->ipps_ref)
+    DNSServiceRefDeallocate(printer->ipps_ref);
+#  endif /* HAVE_SSL */
   if (printer->http_ref)
     DNSServiceRefDeallocate(printer->http_ref);
 
@@ -2105,19 +1847,21 @@ delete_printer(_ipp_printer_t *printer) /* I - Printer */
   TXTRecordDeallocate(&(printer->ipp_txt));
 
   if (printer->dnssd_name)
-    _cupsStrFree(printer->dnssd_name);
+    free(printer->dnssd_name);
 #endif /* HAVE_DNSSD */
 
   if (printer->name)
-    _cupsStrFree(printer->name);
+    free(printer->name);
   if (printer->icon)
-    _cupsStrFree(printer->icon);
+    free(printer->icon);
+  if (printer->command)
+    free(printer->command);
   if (printer->directory)
-    _cupsStrFree(printer->directory);
+    free(printer->directory);
   if (printer->hostname)
-    _cupsStrFree(printer->hostname);
+    free(printer->hostname);
   if (printer->uri)
-    _cupsStrFree(printer->uri);
+    free(printer->uri);
 
   ippDelete(printer->attrs);
   cupsArrayDelete(printer->jobs);
@@ -2141,6 +1885,10 @@ dnssd_callback(
     const char          *domain,       /* I - Domain for service */
     _ipp_printer_t      *printer)      /* I - Printer */
 {
+  (void)sdRef;
+  (void)flags;
+  (void)domain;
+
   if (errorCode)
   {
     fprintf(stderr, "DNSServiceRegister for %s failed with error %d.\n",
@@ -2153,8 +1901,8 @@ dnssd_callback(
       fprintf(stderr, "Now using DNS-SD service name \"%s\".\n", name);
 
     /* No lock needed since only the main thread accesses/changes this */
-    _cupsStrFree(printer->dnssd_name);
-    printer->dnssd_name = _cupsStrAlloc(name);
+    free(printer->dnssd_name);
+    printer->dnssd_name = strdup(name);
   }
 }
 #endif /* HAVE_DNSSD */
@@ -2177,14 +1925,15 @@ find_job(_ipp_client_t *client)         /* I - Client */
   if ((attr = ippFindAttribute(client->request, "job-uri",
                                IPP_TAG_URI)) != NULL)
   {
-    if (!strncmp(attr->values[0].string.text, client->printer->uri,
-                 client->printer->urilen) &&
-        attr->values[0].string.text[client->printer->urilen] == '/')
-      key.id = atoi(attr->values[0].string.text + client->printer->urilen + 1);
+    const char *uri = ippGetString(attr, 0, NULL);
+
+    if (!strncmp(uri, client->printer->uri, client->printer->urilen) &&
+        uri[client->printer->urilen] == '/')
+      key.id = atoi(uri + client->printer->urilen + 1);
   }
   else if ((attr = ippFindAttribute(client->request, "job-id",
                                     IPP_TAG_INTEGER)) != NULL)
-    key.id = attr->values[0].integer;
+    key.id = ippGetInteger(attr, 0);
 
   _cupsRWLockRead(&(client->printer->rwlock));
   job = (_ipp_job_t *)cupsArrayFind(client->printer->jobs, &key);
@@ -2215,12 +1964,12 @@ html_escape(_ipp_client_t *client,      /* I - Client */
     if (*s == '&' || *s == '<')
     {
       if (s > start)
-        httpWrite2(&(client->http), start, s - start);
+        httpWrite2(client->http, start, (size_t)(s - start));
 
       if (*s == '&')
-        httpWrite2(&(client->http), "&amp;", 5);
+        httpWrite2(client->http, "&amp;", 5);
       else
-        httpWrite2(&(client->http), "&lt;", 4);
+        httpWrite2(client->http, "&lt;", 4);
 
       start = s + 1;
     }
@@ -2229,7 +1978,7 @@ html_escape(_ipp_client_t *client,        /* I - Client */
   }
 
   if (s > start)
-    httpWrite2(&(client->http), start, s - start);
+    httpWrite2(client->http, start, (size_t)(s - start));
 }
 
 
@@ -2266,14 +2015,14 @@ html_printf(_ipp_client_t *client,      /* I - Client */
     if (*format == '%')
     {
       if (format > start)
-        httpWrite2(&(client->http), start, format - start);
+        httpWrite2(client->http, start, (size_t)(format - start));
 
       tptr    = tformat;
       *tptr++ = *format++;
 
       if (*format == '%')
       {
-        httpWrite2(&(client->http), "%", 1);
+        httpWrite2(client->http, "%", 1);
         format ++;
        continue;
       }
@@ -2289,7 +2038,7 @@ html_printf(_ipp_client_t *client,        /* I - Client */
        format ++;
        width = va_arg(ap, int);
 
-       snprintf(tptr, sizeof(tformat) - (tptr - tformat), "%d", width);
+       snprintf(tptr, sizeof(tformat) - (size_t)(tptr - tformat), "%d", width);
        tptr += strlen(tptr);
       }
       else
@@ -2321,7 +2070,7 @@ html_printf(_ipp_client_t *client,        /* I - Client */
          format ++;
          prec = va_arg(ap, int);
 
-         snprintf(tptr, sizeof(tformat) - (tptr - tformat), "%d", prec);
+         snprintf(tptr, sizeof(tformat) - (size_t)(tptr - tformat), "%d", prec);
          tptr += strlen(tptr);
        }
        else
@@ -2381,12 +2130,12 @@ html_printf(_ipp_client_t *client,      /* I - Client */
        case 'e' :
        case 'f' :
        case 'g' :
-           if ((width + 2) > sizeof(temp))
+           if ((size_t)(width + 2) > sizeof(temp))
              break;
 
            sprintf(temp, tformat, va_arg(ap, double));
 
-            httpWrite2(&(client->http), temp, strlen(temp));
+            httpWrite2(client->http, temp, strlen(temp));
            break;
 
         case 'B' : /* Integer formats */
@@ -2397,7 +2146,7 @@ html_printf(_ipp_client_t *client,        /* I - Client */
        case 'o' :
        case 'u' :
        case 'x' :
-           if ((width + 2) > sizeof(temp))
+           if ((size_t)(width + 2) > sizeof(temp))
              break;
 
 #  ifdef HAVE_LONG_LONG
@@ -2410,22 +2159,22 @@ html_printf(_ipp_client_t *client,      /* I - Client */
            else
              sprintf(temp, tformat, va_arg(ap, int));
 
-            httpWrite2(&(client->http), temp, strlen(temp));
+            httpWrite2(client->http, temp, strlen(temp));
            break;
 
        case 'p' : /* Pointer value */
-           if ((width + 2) > sizeof(temp))
+           if ((size_t)(width + 2) > sizeof(temp))
              break;
 
            sprintf(temp, tformat, va_arg(ap, void *));
 
-            httpWrite2(&(client->http), temp, strlen(temp));
+            httpWrite2(client->http, temp, strlen(temp));
            break;
 
         case 'c' : /* Character or character array */
             if (width <= 1)
             {
-              temp[0] = va_arg(ap, int);
+              temp[0] = (char)va_arg(ap, int);
               temp[1] = '\0';
               html_escape(client, temp, 1);
             }
@@ -2446,7 +2195,7 @@ html_printf(_ipp_client_t *client,        /* I - Client */
   }
 
   if (format > start)
-    httpWrite2(&(client->http), start, format - start);
+    httpWrite2(client->http, start, (size_t)(format - start));
 
   va_end(ap);
 }
@@ -2467,7 +2216,10 @@ ipp_cancel_job(_ipp_client_t *client)    /* I - Client */
   */
 
   if ((job = find_job(client)) == NULL)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist.");
     return;
+  }
 
  /*
   * See if the job is already completed, canceled, or aborted; if so,
@@ -2476,18 +2228,18 @@ ipp_cancel_job(_ipp_client_t *client)   /* I - Client */
 
   switch (job->state)
   {
-    case IPP_JOB_CANCELED :
-       respond_ipp(client, IPP_NOT_POSSIBLE,
+    case IPP_JSTATE_CANCELED :
+       respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE,
                    "Job #%d is already canceled - can\'t cancel.", job->id);
         break;
 
-    case IPP_JOB_ABORTED :
-       respond_ipp(client, IPP_NOT_POSSIBLE,
+    case IPP_JSTATE_ABORTED :
+       respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE,
                    "Job #%d is already aborted - can\'t cancel.", job->id);
         break;
 
-    case IPP_JOB_COMPLETED :
-       respond_ipp(client, IPP_NOT_POSSIBLE,
+    case IPP_JSTATE_COMPLETED :
+       respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE,
                    "Job #%d is already completed - can\'t cancel.", job->id);
         break;
 
@@ -2498,24 +2250,23 @@ ipp_cancel_job(_ipp_client_t *client)   /* I - Client */
 
        _cupsRWLockWrite(&(client->printer->rwlock));
 
-       if (job->state == IPP_JOB_PROCESSING ||
-           (job->state == IPP_JOB_HELD && job->fd >= 0))
+       if (job->state == IPP_JSTATE_PROCESSING ||
+           (job->state == IPP_JSTATE_HELD && job->fd >= 0))
           job->cancel = 1;
        else
        {
-         job->state     = IPP_JOB_CANCELED;
+         job->state     = IPP_JSTATE_CANCELED;
          job->completed = time(NULL);
        }
 
        _cupsRWUnlock(&(client->printer->rwlock));
 
-       respond_ipp(client, IPP_OK, NULL);
+       respond_ipp(client, IPP_STATUS_OK, NULL);
         break;
   }
 }
 
 
-#if 0
 /*
  * 'ipp_create_job()' - Create a job object.
  */
@@ -2523,33 +2274,82 @@ ipp_cancel_job(_ipp_client_t *client)   /* I - Client */
 static void
 ipp_create_job(_ipp_client_t *client)  /* I - Client */
 {
-}
-#endif /* 0 */
+  _ipp_job_t           *job;           /* New job */
+  cups_array_t         *ra;            /* Attributes to send in response */
 
 
-/*
* 'ipp_get_job_attributes()' - Get the attributes for a job object.
- */
+ /*
 * Validate print job attributes...
 */
 
-static void
-ipp_get_job_attributes(
-    _ipp_client_t *client)             /* I - Client */
-{
-  _ipp_job_t   *job;                   /* Job */
-  cups_array_t *ra;                    /* requested-attributes */
+  if (!valid_job_attributes(client))
+  {
+    httpFlush(client->http);
+    return;
+  }
 
+ /*
+  * Do we have a file to print?
+  */
 
-  if ((job = find_job(client)) == NULL)
+  if (httpGetState(client->http) == HTTP_STATE_POST_RECV)
   {
-    respond_ipp(client, IPP_NOT_FOUND, "Job not found.");
+    respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+                "Unexpected document data following request.");
     return;
   }
 
-  respond_ipp(client, IPP_OK, NULL);
+ /*
+  * Create the job...
+  */
 
-  ra = create_requested_array(client);
-  copy_job_attributes(client, job, ra);
-  cupsArrayDelete(ra);
+  if ((job = create_job(client)) == NULL)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_BUSY,
+                "Currently printing another job.");
+    return;
+  }
+
+ /*
+  * Return the job info...
+  */
+
+  respond_ipp(client, IPP_STATUS_OK, NULL);
+
+  ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+  cupsArrayAdd(ra, "job-id");
+  cupsArrayAdd(ra, "job-state");
+  cupsArrayAdd(ra, "job-state-reasons");
+  cupsArrayAdd(ra, "job-uri");
+
+  copy_job_attributes(client, job, ra);
+  cupsArrayDelete(ra);
+}
+
+
+/*
+ * 'ipp_get_job_attributes()' - Get the attributes for a job object.
+ */
+
+static void
+ipp_get_job_attributes(
+    _ipp_client_t *client)             /* I - Client */
+{
+  _ipp_job_t   *job;                   /* Job */
+  cups_array_t *ra;                    /* requested-attributes */
+
+
+  if ((job = find_job(client)) == NULL)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job not found.");
+    return;
+  }
+
+  respond_ipp(client, IPP_STATUS_OK, NULL);
+
+  ra = ippCreateRequestedArray(client->request);
+  copy_job_attributes(client, job, ra);
+  cupsArrayDelete(ra);
 }
 
 
@@ -2561,6 +2361,8 @@ static void
 ipp_get_jobs(_ipp_client_t *client)    /* I - Client */
 {
   ipp_attribute_t      *attr;          /* Current attribute */
+  const char           *which_jobs = NULL;
+                                       /* which-jobs values */
   int                  job_comparison; /* Job comparison */
   ipp_jstate_t         job_state;      /* job-state value */
   int                  first_job_id,   /* First job ID */
@@ -2577,61 +2379,62 @@ ipp_get_jobs(_ipp_client_t *client)     /* I - Client */
 
   if ((attr = ippFindAttribute(client->request, "which-jobs",
                                IPP_TAG_KEYWORD)) != NULL)
-    fprintf(stderr, "%s Get-Jobs which-jobs=%s", client->http.hostname,
-            attr->values[0].string.text);
+  {
+    which_jobs = ippGetString(attr, 0, NULL);
+    fprintf(stderr, "%s Get-Jobs which-jobs=%s", client->hostname, which_jobs);
+  }
 
-  if (!attr || !strcmp(attr->values[0].string.text, "not-completed"))
+  if (!which_jobs || !strcmp(which_jobs, "not-completed"))
   {
     job_comparison = -1;
-    job_state      = IPP_JOB_STOPPED;
+    job_state      = IPP_JSTATE_STOPPED;
   }
-  else if (!strcmp(attr->values[0].string.text, "completed"))
+  else if (!strcmp(which_jobs, "completed"))
   {
     job_comparison = 1;
-    job_state      = IPP_JOB_CANCELED;
+    job_state      = IPP_JSTATE_CANCELED;
   }
-  else if (!strcmp(attr->values[0].string.text, "aborted"))
+  else if (!strcmp(which_jobs, "aborted"))
   {
     job_comparison = 0;
-    job_state      = IPP_JOB_ABORTED;
+    job_state      = IPP_JSTATE_ABORTED;
   }
-  else if (!strcmp(attr->values[0].string.text, "all"))
+  else if (!strcmp(which_jobs, "all"))
   {
     job_comparison = 1;
-    job_state      = IPP_JOB_PENDING;
+    job_state      = IPP_JSTATE_PENDING;
   }
-  else if (!strcmp(attr->values[0].string.text, "canceled"))
+  else if (!strcmp(which_jobs, "canceled"))
   {
     job_comparison = 0;
-    job_state      = IPP_JOB_CANCELED;
+    job_state      = IPP_JSTATE_CANCELED;
   }
-  else if (!strcmp(attr->values[0].string.text, "pending"))
+  else if (!strcmp(which_jobs, "pending"))
   {
     job_comparison = 0;
-    job_state      = IPP_JOB_PENDING;
+    job_state      = IPP_JSTATE_PENDING;
   }
-  else if (!strcmp(attr->values[0].string.text, "pending-held"))
+  else if (!strcmp(which_jobs, "pending-held"))
   {
     job_comparison = 0;
-    job_state      = IPP_JOB_HELD;
+    job_state      = IPP_JSTATE_HELD;
   }
-  else if (!strcmp(attr->values[0].string.text, "processing"))
+  else if (!strcmp(which_jobs, "processing"))
   {
     job_comparison = 0;
-    job_state      = IPP_JOB_PROCESSING;
+    job_state      = IPP_JSTATE_PROCESSING;
   }
-  else if (!strcmp(attr->values[0].string.text, "processing-stopped"))
+  else if (!strcmp(which_jobs, "processing-stopped"))
   {
     job_comparison = 0;
-    job_state      = IPP_JOB_STOPPED;
+    job_state      = IPP_JSTATE_STOPPED;
   }
   else
   {
-    respond_ipp(client, IPP_ATTRIBUTES,
-                "The which-jobs value \"%s\" is not supported.",
-                attr->values[0].string.text);
+    respond_ipp(client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES,
+                "The which-jobs value \"%s\" is not supported.", which_jobs);
     ippAddString(client->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD,
-                 "which-jobs", NULL, attr->values[0].string.text);
+                 "which-jobs", NULL, which_jobs);
     return;
   }
 
@@ -2642,9 +2445,9 @@ ipp_get_jobs(_ipp_client_t *client)       /* I - Client */
   if ((attr = ippFindAttribute(client->request, "limit",
                                IPP_TAG_INTEGER)) != NULL)
   {
-    limit = attr->values[0].integer;
+    limit = ippGetInteger(attr, 0);
 
-    fprintf(stderr, "%s Get-Jobs limit=%d", client->http.hostname, limit);
+    fprintf(stderr, "%s Get-Jobs limit=%d", client->hostname, limit);
   }
   else
     limit = 0;
@@ -2652,9 +2455,9 @@ ipp_get_jobs(_ipp_client_t *client)       /* I - Client */
   if ((attr = ippFindAttribute(client->request, "first-job-id",
                                IPP_TAG_INTEGER)) != NULL)
   {
-    first_job_id = attr->values[0].integer;
+    first_job_id = ippGetInteger(attr, 0);
 
-    fprintf(stderr, "%s Get-Jobs first-job-id=%d", client->http.hostname,
+    fprintf(stderr, "%s Get-Jobs first-job-id=%d", client->hostname,
             first_job_id);
   }
   else
@@ -2669,23 +2472,25 @@ ipp_get_jobs(_ipp_client_t *client)     /* I - Client */
   if ((attr = ippFindAttribute(client->request, "my-jobs",
                                IPP_TAG_BOOLEAN)) != NULL)
   {
-    fprintf(stderr, "%s Get-Jobs my-jobs=%s\n", client->http.hostname,
-            attr->values[0].boolean ? "true" : "false");
+    int my_jobs = ippGetBoolean(attr, 0);
+
+    fprintf(stderr, "%s Get-Jobs my-jobs=%s\n", client->hostname,
+            my_jobs ? "true" : "false");
 
-    if (attr->values[0].boolean)
+    if (my_jobs)
     {
       if ((attr = ippFindAttribute(client->request, "requesting-user-name",
                                        IPP_TAG_NAME)) == NULL)
       {
-       respond_ipp(client, IPP_BAD_REQUEST,
+       respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
                    "Need requesting-user-name with my-jobs.");
        return;
       }
 
-      username = attr->values[0].string.text;
+      username = ippGetString(attr, 0, NULL);
 
       fprintf(stderr, "%s Get-Jobs requesting-user-name=\"%s\"\n",
-              client->http.hostname, username);
+              client->hostname, username);
     }
   }
 
@@ -2693,21 +2498,9 @@ ipp_get_jobs(_ipp_client_t *client)      /* I - Client */
   * OK, build a list of jobs for this printer...
   */
 
-  if ((ra = create_requested_array(client)) == NULL &&
-      !ippFindAttribute(client->request, "requested-attributes",
-                        IPP_TAG_KEYWORD))
-  {
-   /*
-    * IPP conformance - Get-Jobs has a default requested-attributes value of
-    * "job-id" and "job-uri".
-    */
-
-    ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
-    cupsArrayAdd(ra, "job-id");
-    cupsArrayAdd(ra, "job-uri");
-  }
+  ra = ippCreateRequestedArray(client->request);
 
-  respond_ipp(client, IPP_OK, NULL);
+  respond_ipp(client, IPP_STATUS_OK, NULL);
 
   _cupsRWLockRead(&(client->printer->rwlock));
 
@@ -2723,7 +2516,8 @@ ipp_get_jobs(_ipp_client_t *client)       /* I - Client */
        (job_comparison == 0 && job->state != job_state) ||
        (job_comparison > 0 && job->state < job_state) ||
        job->id < first_job_id ||
-       (username && job->username && _cups_strcasecmp(username, job->username)))
+       (username && job->username &&
+        _cups_strcasecmp(username, job->username)))
       continue;
 
     if (count > 0)
@@ -2755,15 +2549,15 @@ ipp_get_printer_attributes(
   * Send the attributes...
   */
 
-  ra      = create_requested_array(client);
+  ra      = ippCreateRequestedArray(client->request);
   printer = client->printer;
 
-  respond_ipp(client, IPP_OK, NULL);
+  respond_ipp(client, IPP_STATUS_OK, NULL);
 
   _cupsRWLockRead(&(printer->rwlock));
 
   copy_attributes(client->response, printer->attrs, ra, IPP_TAG_ZERO,
-                 IPP_TAG_COPY);
+                 IPP_TAG_CUPS_CONST);
 
   if (!ra || cupsArrayFind(ra, "printer-state"))
     ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_ENUM,
@@ -2771,51 +2565,51 @@ ipp_get_printer_attributes(
 
   if (!ra || cupsArrayFind(ra, "printer-state-reasons"))
   {
-    if (printer->state_reasons == _IPP_PRINTER_NONE)
+    if (printer->state_reasons == _IPP_PSTATE_NONE)
       ippAddString(client->response, IPP_TAG_PRINTER,
-                   IPP_TAG_KEYWORD | IPP_TAG_COPY, "printer-state-reasons",
-                  NULL, "none");
+                   IPP_CONST_TAG(IPP_TAG_KEYWORD),
+                   "printer-state-reasons", NULL, "none");
     else
     {
       int                      num_reasons = 0;/* Number of reasons */
       const char               *reasons[32];   /* Reason strings */
 
-      if (printer->state_reasons & _IPP_PRINTER_OTHER)
+      if (printer->state_reasons & _IPP_PSTATE_OTHER)
        reasons[num_reasons ++] = "other";
-      if (printer->state_reasons & _IPP_PRINTER_COVER_OPEN)
+      if (printer->state_reasons & _IPP_PSTATE_COVER_OPEN)
        reasons[num_reasons ++] = "cover-open";
-      if (printer->state_reasons & _IPP_PRINTER_INPUT_TRAY_MISSING)
+      if (printer->state_reasons & _IPP_PSTATE_INPUT_TRAY_MISSING)
        reasons[num_reasons ++] = "input-tray-missing";
-      if (printer->state_reasons & _IPP_PRINTER_MARKER_SUPPLY_EMPTY)
+      if (printer->state_reasons & _IPP_PSTATE_MARKER_SUPPLY_EMPTY)
        reasons[num_reasons ++] = "marker-supply-empty-warning";
-      if (printer->state_reasons & _IPP_PRINTER_MARKER_SUPPLY_LOW)
+      if (printer->state_reasons & _IPP_PSTATE_MARKER_SUPPLY_LOW)
        reasons[num_reasons ++] = "marker-supply-low-report";
-      if (printer->state_reasons & _IPP_PRINTER_MARKER_WASTE_ALMOST_FULL)
+      if (printer->state_reasons & _IPP_PSTATE_MARKER_WASTE_ALMOST_FULL)
        reasons[num_reasons ++] = "marker-waste-almost-full-report";
-      if (printer->state_reasons & _IPP_PRINTER_MARKER_WASTE_FULL)
+      if (printer->state_reasons & _IPP_PSTATE_MARKER_WASTE_FULL)
        reasons[num_reasons ++] = "marker-waste-full-warning";
-      if (printer->state_reasons & _IPP_PRINTER_MEDIA_EMPTY)
+      if (printer->state_reasons & _IPP_PSTATE_MEDIA_EMPTY)
        reasons[num_reasons ++] = "media-empty-warning";
-      if (printer->state_reasons & _IPP_PRINTER_MEDIA_JAM)
+      if (printer->state_reasons & _IPP_PSTATE_MEDIA_JAM)
        reasons[num_reasons ++] = "media-jam-warning";
-      if (printer->state_reasons & _IPP_PRINTER_MEDIA_LOW)
+      if (printer->state_reasons & _IPP_PSTATE_MEDIA_LOW)
        reasons[num_reasons ++] = "media-low-report";
-      if (printer->state_reasons & _IPP_PRINTER_MEDIA_NEEDED)
+      if (printer->state_reasons & _IPP_PSTATE_MEDIA_NEEDED)
        reasons[num_reasons ++] = "media-needed-report";
-      if (printer->state_reasons & _IPP_PRINTER_MOVING_TO_PAUSED)
+      if (printer->state_reasons & _IPP_PSTATE_MOVING_TO_PAUSED)
        reasons[num_reasons ++] = "moving-to-paused";
-      if (printer->state_reasons & _IPP_PRINTER_PAUSED)
+      if (printer->state_reasons & _IPP_PSTATE_PAUSED)
        reasons[num_reasons ++] = "paused";
-      if (printer->state_reasons & _IPP_PRINTER_SPOOL_AREA_FULL)
+      if (printer->state_reasons & _IPP_PSTATE_SPOOL_AREA_FULL)
        reasons[num_reasons ++] = "spool-area-full";
-      if (printer->state_reasons & _IPP_PRINTER_TONER_EMPTY)
+      if (printer->state_reasons & _IPP_PSTATE_TONER_EMPTY)
        reasons[num_reasons ++] = "toner-empty-warning";
-      if (printer->state_reasons & _IPP_PRINTER_TONER_LOW)
+      if (printer->state_reasons & _IPP_PSTATE_TONER_LOW)
        reasons[num_reasons ++] = "toner-low-report";
 
       ippAddStrings(client->response, IPP_TAG_PRINTER,
-                    IPP_TAG_KEYWORD | IPP_TAG_COPY,  "printer-state-reasons",
-                   num_reasons, NULL, reasons);
+                    IPP_CONST_TAG(IPP_TAG_KEYWORD),
+                    "printer-state-reasons", num_reasons, NULL, reasons);
     }
   }
 
@@ -2827,7 +2621,7 @@ ipp_get_printer_attributes(
     ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
                   "queued-job-count",
                  printer->active_job &&
-                     printer->active_job->state < IPP_JOB_CANCELED);
+                     printer->active_job->state < IPP_JSTATE_CANCELED);
 
   _cupsRWUnlock(&(printer->rwlock));
 
@@ -2855,7 +2649,7 @@ ipp_print_job(_ipp_client_t *client)      /* I - Client */
 
   if (!valid_job_attributes(client))
   {
-    httpFlush(&(client->http));
+    httpFlush(client->http);
     return;
   }
 
@@ -2863,9 +2657,9 @@ ipp_print_job(_ipp_client_t *client)      /* I - Client */
   * Do we have a file to print?
   */
 
-  if (client->http.state == HTTP_POST_SEND)
+  if (httpGetState(client->http) == HTTP_STATE_POST_SEND)
   {
-    respond_ipp(client, IPP_BAD_REQUEST, "No file in request.");
+    respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "No file in request.");
     return;
   }
 
@@ -2875,7 +2669,8 @@ ipp_print_job(_ipp_client_t *client)      /* I - Client */
 
   if ((job = create_job(client)) == NULL)
   {
-    respond_ipp(client, IPP_PRINTER_BUSY, "Currently printing another job.");
+    respond_ipp(client, IPP_STATUS_ERROR_BUSY,
+                "Currently printing another job.");
     return;
   }
 
@@ -2889,6 +2684,9 @@ ipp_print_job(_ipp_client_t *client)      /* I - Client */
   else if (!_cups_strcasecmp(job->format, "image/png"))
     snprintf(filename, sizeof(filename), "%s/%d.png",
              client->printer->directory, job->id);
+  else if (!_cups_strcasecmp(job->format, "image/pwg-raster"))
+    snprintf(filename, sizeof(filename), "%s/%d.ras",
+             client->printer->directory, job->id);
   else if (!_cups_strcasecmp(job->format, "application/pdf"))
     snprintf(filename, sizeof(filename), "%s/%d.pdf",
              client->printer->directory, job->id);
@@ -2899,29 +2697,32 @@ ipp_print_job(_ipp_client_t *client)    /* I - Client */
     snprintf(filename, sizeof(filename), "%s/%d.prn",
              client->printer->directory, job->id);
 
+  if (Verbosity)
+    fprintf(stderr, "Creating job file \"%s\", format \"%s\".\n", filename, job->format);
+
   if ((job->fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0)
   {
-    job->state = IPP_JOB_ABORTED;
+    job->state = IPP_JSTATE_ABORTED;
 
-    respond_ipp(client, IPP_INTERNAL_ERROR,
+    respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
                 "Unable to create print file: %s", strerror(errno));
     return;
   }
 
-  while ((bytes = httpRead2(&(client->http), buffer, sizeof(buffer))) > 0)
+  while ((bytes = httpRead2(client->http, buffer, sizeof(buffer))) > 0)
   {
-    if (write(job->fd, buffer, bytes) < bytes)
+    if (write(job->fd, buffer, (size_t)bytes) < bytes)
     {
       int error = errno;               /* Write error */
 
-      job->state = IPP_JOB_ABORTED;
+      job->state = IPP_JSTATE_ABORTED;
 
       close(job->fd);
       job->fd = -1;
 
       unlink(filename);
 
-      respond_ipp(client, IPP_INTERNAL_ERROR,
+      respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
                   "Unable to write print file: %s", strerror(error));
       return;
     }
@@ -2933,14 +2734,15 @@ ipp_print_job(_ipp_client_t *client)    /* I - Client */
     * Got an error while reading the print data, so abort this job.
     */
 
-    job->state = IPP_JOB_ABORTED;
+    job->state = IPP_JSTATE_ABORTED;
 
     close(job->fd);
     job->fd = -1;
 
     unlink(filename);
 
-    respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to read print file.");
+    respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+                "Unable to read print file.");
     return;
   }
 
@@ -2948,36 +2750,41 @@ ipp_print_job(_ipp_client_t *client)    /* I - Client */
   {
     int error = errno;         /* Write error */
 
-    job->state = IPP_JOB_ABORTED;
+    job->state = IPP_JSTATE_ABORTED;
     job->fd    = -1;
 
     unlink(filename);
 
-    respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to write print file: %s",
-                strerror(error));
+    respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+                "Unable to write print file: %s", strerror(error));
     return;
   }
 
   job->fd       = -1;
   job->filename = strdup(filename);
-  job->state    = IPP_JOB_PENDING;
+  job->state    = IPP_JSTATE_PENDING;
 
  /*
   * Process the job...
   */
 
+#if 0
   if (!_cupsThreadCreate((_cups_thread_func_t)process_job, job))
   {
-    job->state = IPP_JOB_ABORTED;
-    respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to process job.");
+    job->state = IPP_JSTATE_ABORTED;
+    respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to process job.");
     return;
   }
 
+#else
+  process_job(job);
+#endif /* 0 */
+
  /*
   * Return the job info...
   */
 
-  respond_ipp(client, IPP_OK, NULL);
+  respond_ipp(client, IPP_STATUS_OK, NULL);
 
   ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
   cupsArrayAdd(ra, "job-id");
@@ -2990,43 +2797,954 @@ ipp_print_job(_ipp_client_t *client)   /* I - Client */
 }
 
 
-#if 0
 /*
- * 'ipp_send_document()' - Add an attached document to a job object created with
- *                         Create-Job.
+ * 'ipp_print_uri()' - Create a job object with a referenced document.
  */
 
 static void
-ipp_send_document(_ipp_client_t *client)/* I - Client */
+ipp_print_uri(_ipp_client_t *client)   /* I - Client */
 {
-}
+  _ipp_job_t           *job;           /* New job */
+  ipp_attribute_t      *uri;           /* document-uri */
+  char                 scheme[256],    /* URI scheme */
+                       userpass[256],  /* Username and password info */
+                       hostname[256],  /* Hostname */
+                       resource[1024]; /* Resource path */
+  int                  port;           /* Port number */
+  http_uri_status_t    uri_status;     /* URI decode status */
+  http_encryption_t    encryption;     /* Encryption to use, if any */
+  http_t               *http;          /* Connection for http/https URIs */
+  http_status_t                status;         /* Access status for http/https URIs */
+  int                  infile;         /* Input file for local file URIs */
+  char                 filename[1024], /* Filename buffer */
+                       buffer[4096];   /* Copy buffer */
+  ssize_t              bytes;          /* Bytes read */
+  cups_array_t         *ra;            /* Attributes to send in response */
+  static const char * const uri_status_strings[] =
+  {                                    /* URI decode errors */
+    "URI too large.",
+    "Bad arguments to function.",
+    "Bad resource in URI.",
+    "Bad port number in URI.",
+    "Bad hostname in URI.",
+    "Bad username in URI.",
+    "Bad scheme in URI.",
+    "Bad/empty URI."
+  };
+
+
+ /*
+  * Validate print job attributes...
+  */
+
+  if (!valid_job_attributes(client))
+  {
+    httpFlush(client->http);
+    return;
+  }
+
+ /*
+  * Do we have a file to print?
+  */
+
+  if (httpGetState(client->http) == HTTP_STATE_POST_RECV)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+                "Unexpected document data following request.");
+    return;
+  }
+
+ /*
+  * Do we have a document URI?
+  */
+
+  if ((uri = ippFindAttribute(client->request, "document-uri",
+                              IPP_TAG_URI)) == NULL)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing document-uri.");
+    return;
+  }
+
+  if (ippGetCount(uri) != 1)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+                "Too many document-uri values.");
+    return;
+  }
+
+  uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, ippGetString(uri, 0, NULL),
+                               scheme, sizeof(scheme), userpass,
+                               sizeof(userpass), hostname, sizeof(hostname),
+                               &port, resource, sizeof(resource));
+  if (uri_status < HTTP_URI_STATUS_OK)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Bad document-uri: %s",
+                uri_status_strings[uri_status - HTTP_URI_STATUS_OVERFLOW]);
+    return;
+  }
+
+  if (strcmp(scheme, "file") &&
+#ifdef HAVE_SSL
+      strcmp(scheme, "https") &&
+#endif /* HAVE_SSL */
+      strcmp(scheme, "http"))
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_URI_SCHEME,
+                "URI scheme \"%s\" not supported.", scheme);
+    return;
+  }
+
+  if (!strcmp(scheme, "file") && access(resource, R_OK))
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS,
+                "Unable to access URI: %s", strerror(errno));
+    return;
+  }
+
+ /*
+  * Print the job...
+  */
+
+  if ((job = create_job(client)) == NULL)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_BUSY,
+                "Currently printing another job.");
+    return;
+  }
+
+ /*
+  * Create a file for the request data...
+  */
+
+  if (!_cups_strcasecmp(job->format, "image/jpeg"))
+    snprintf(filename, sizeof(filename), "%s/%d.jpg",
+             client->printer->directory, job->id);
+  else if (!_cups_strcasecmp(job->format, "image/png"))
+    snprintf(filename, sizeof(filename), "%s/%d.png",
+             client->printer->directory, job->id);
+  else if (!_cups_strcasecmp(job->format, "application/pdf"))
+    snprintf(filename, sizeof(filename), "%s/%d.pdf",
+             client->printer->directory, job->id);
+  else if (!_cups_strcasecmp(job->format, "application/postscript"))
+    snprintf(filename, sizeof(filename), "%s/%d.ps",
+             client->printer->directory, job->id);
+  else
+    snprintf(filename, sizeof(filename), "%s/%d.prn",
+             client->printer->directory, job->id);
+
+  if ((job->fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600)) < 0)
+  {
+    job->state = IPP_JSTATE_ABORTED;
+
+    respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+                "Unable to create print file: %s", strerror(errno));
+    return;
+  }
+
+  if (!strcmp(scheme, "file"))
+  {
+    if ((infile = open(resource, O_RDONLY)) < 0)
+    {
+      respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS,
+                  "Unable to access URI: %s", strerror(errno));
+      return;
+    }
+
+    do
+    {
+      if ((bytes = read(infile, buffer, sizeof(buffer))) < 0 &&
+          (errno == EAGAIN || errno == EINTR))
+        bytes = 1;
+      else if (bytes > 0 && write(job->fd, buffer, (size_t)bytes) < bytes)
+      {
+       int error = errno;              /* Write error */
+
+       job->state = IPP_JSTATE_ABORTED;
+
+       close(job->fd);
+       job->fd = -1;
+
+       unlink(filename);
+       close(infile);
+
+       respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+                   "Unable to write print file: %s", strerror(error));
+       return;
+      }
+    }
+    while (bytes > 0);
+
+    close(infile);
+  }
+  else
+  {
+#ifdef HAVE_SSL
+    if (port == 443 || !strcmp(scheme, "https"))
+      encryption = HTTP_ENCRYPTION_ALWAYS;
+    else
+#endif /* HAVE_SSL */
+    encryption = HTTP_ENCRYPTION_IF_REQUESTED;
+
+    if ((http = httpConnect2(hostname, port, NULL, AF_UNSPEC, encryption,
+                             1, 30000, NULL)) == NULL)
+    {
+      respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS,
+                  "Unable to connect to %s: %s", hostname,
+                 cupsLastErrorString());
+      job->state = IPP_JSTATE_ABORTED;
+
+      close(job->fd);
+      job->fd = -1;
+
+      unlink(filename);
+      return;
+    }
+
+    httpClearFields(http);
+    httpSetField(http, HTTP_FIELD_ACCEPT_LANGUAGE, "en");
+    if (httpGet(http, resource))
+    {
+      respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS,
+                  "Unable to GET URI: %s", strerror(errno));
+
+      job->state = IPP_JSTATE_ABORTED;
+
+      close(job->fd);
+      job->fd = -1;
+
+      unlink(filename);
+      httpClose(http);
+      return;
+    }
+
+    while ((status = httpUpdate(http)) == HTTP_STATUS_CONTINUE);
+
+    if (status != HTTP_STATUS_OK)
+    {
+      respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS,
+                  "Unable to GET URI: %s", httpStatus(status));
+
+      job->state = IPP_JSTATE_ABORTED;
+
+      close(job->fd);
+      job->fd = -1;
+
+      unlink(filename);
+      httpClose(http);
+      return;
+    }
+
+    while ((bytes = httpRead2(http, buffer, sizeof(buffer))) > 0)
+    {
+      if (write(job->fd, buffer, (size_t)bytes) < bytes)
+      {
+       int error = errno;              /* Write error */
+
+       job->state = IPP_JSTATE_ABORTED;
+
+       close(job->fd);
+       job->fd = -1;
+
+       unlink(filename);
+       httpClose(http);
+
+       respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+                   "Unable to write print file: %s", strerror(error));
+       return;
+      }
+    }
+
+    httpClose(http);
+  }
+
+  if (close(job->fd))
+  {
+    int error = errno;         /* Write error */
+
+    job->state = IPP_JSTATE_ABORTED;
+    job->fd    = -1;
+
+    unlink(filename);
+
+    respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+                "Unable to write print file: %s", strerror(error));
+    return;
+  }
+
+  job->fd       = -1;
+  job->filename = strdup(filename);
+  job->state    = IPP_JSTATE_PENDING;
+
+ /*
+  * Process the job...
+  */
+
+#if 0
+  if (!_cupsThreadCreate((_cups_thread_func_t)process_job, job))
+  {
+    job->state = IPP_JSTATE_ABORTED;
+    respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to process job.");
+    return;
+  }
+
+#else
+  process_job(job);
 #endif /* 0 */
 
+ /*
+  * Return the job info...
+  */
 
-/*
- * 'ipp_validate_job()' - Validate job creation attributes.
- */
+  respond_ipp(client, IPP_STATUS_OK, NULL);
 
-static void
-ipp_validate_job(_ipp_client_t *client)        /* I - Client */
-{
+  ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+  cupsArrayAdd(ra, "job-id");
+  cupsArrayAdd(ra, "job-state");
+  cupsArrayAdd(ra, "job-state-reasons");
+  cupsArrayAdd(ra, "job-uri");
+
+  copy_job_attributes(client, job, ra);
+  cupsArrayDelete(ra);
 }
 
 
 /*
- * 'process_client()' - Process client requests on a thread.
+ * 'ipp_send_document()' - Add an attached document to a job object created with
+ *                         Create-Job.
  */
 
-static void *                          /* O - Exit status */
-process_client(_ipp_client_t *client)  /* I - Client */
+static void
+ipp_send_document(_ipp_client_t *client)/* I - Client */
+{
+  _ipp_job_t           *job;           /* Job information */
+  char                 filename[1024], /* Filename buffer */
+                       buffer[4096];   /* Copy buffer */
+  ssize_t              bytes;          /* Bytes read */
+  ipp_attribute_t      *attr;          /* Current attribute */
+  cups_array_t         *ra;            /* Attributes to send in response */
+
+
+ /*
+  * Get the job...
+  */
+
+  if ((job = find_job(client)) == NULL)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist.");
+    httpFlush(client->http);
+    return;
+  }
+
+ /*
+  * See if we already have a document for this job or the job has already
+  * in a non-pending state...
+  */
+
+  if (job->state > IPP_JSTATE_HELD)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE,
+                "Job is not in a pending state.");
+    httpFlush(client->http);
+    return;
+  }
+  else if (job->filename || job->fd >= 0)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_MULTIPLE_JOBS_NOT_SUPPORTED,
+                "Multiple document jobs are not supported.");
+    httpFlush(client->http);
+    return;
+  }
+
+  if ((attr = ippFindAttribute(client->request, "last-document",
+                               IPP_TAG_ZERO)) == NULL)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+                "Missing required last-document attribute.");
+    httpFlush(client->http);
+    return;
+  }
+  else if (ippGetValueTag(attr) != IPP_TAG_BOOLEAN || ippGetCount(attr) != 1 ||
+           !ippGetBoolean(attr, 0))
+  {
+    respond_unsupported(client, attr);
+    httpFlush(client->http);
+    return;
+  }
+
+ /*
+  * Validate document attributes...
+  */
+
+  if (!valid_doc_attributes(client))
+  {
+    httpFlush(client->http);
+    return;
+  }
+
+ /*
+  * Get the document format for the job...
+  */
+
+  _cupsRWLockWrite(&(client->printer->rwlock));
+
+  if ((attr = ippFindAttribute(client->request, "document-format", IPP_TAG_MIMETYPE)) != NULL)
+    job->format = ippGetString(attr, 0, NULL);
+  else
+    job->format = "application/octet-stream";
+
+ /*
+  * Create a file for the request data...
+  */
+
+  if (!_cups_strcasecmp(job->format, "image/jpeg"))
+    snprintf(filename, sizeof(filename), "%s/%d.jpg",
+             client->printer->directory, job->id);
+  else if (!_cups_strcasecmp(job->format, "image/png"))
+    snprintf(filename, sizeof(filename), "%s/%d.png",
+             client->printer->directory, job->id);
+  else if (!_cups_strcasecmp(job->format, "application/pdf"))
+    snprintf(filename, sizeof(filename), "%s/%d.pdf",
+             client->printer->directory, job->id);
+  else if (!_cups_strcasecmp(job->format, "application/postscript"))
+    snprintf(filename, sizeof(filename), "%s/%d.ps",
+             client->printer->directory, job->id);
+  else
+    snprintf(filename, sizeof(filename), "%s/%d.prn",
+             client->printer->directory, job->id);
+
+  if (Verbosity)
+    fprintf(stderr, "Creating job file \"%s\", format \"%s\".\n", filename, job->format);
+
+  job->fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+
+  _cupsRWUnlock(&(client->printer->rwlock));
+
+  if (job->fd < 0)
+  {
+    job->state = IPP_JSTATE_ABORTED;
+
+    respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+                "Unable to create print file: %s", strerror(errno));
+    return;
+  }
+
+  while ((bytes = httpRead2(client->http, buffer, sizeof(buffer))) > 0)
+  {
+    if (write(job->fd, buffer, (size_t)bytes) < bytes)
+    {
+      int error = errno;               /* Write error */
+
+      job->state = IPP_JSTATE_ABORTED;
+
+      close(job->fd);
+      job->fd = -1;
+
+      unlink(filename);
+
+      respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+                  "Unable to write print file: %s", strerror(error));
+      return;
+    }
+  }
+
+  if (bytes < 0)
+  {
+   /*
+    * Got an error while reading the print data, so abort this job.
+    */
+
+    job->state = IPP_JSTATE_ABORTED;
+
+    close(job->fd);
+    job->fd = -1;
+
+    unlink(filename);
+
+    respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+                "Unable to read print file.");
+    return;
+  }
+
+  if (close(job->fd))
+  {
+    int error = errno;                 /* Write error */
+
+    job->state = IPP_JSTATE_ABORTED;
+    job->fd    = -1;
+
+    unlink(filename);
+
+    respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+                "Unable to write print file: %s", strerror(error));
+    return;
+  }
+
+  _cupsRWLockWrite(&(client->printer->rwlock));
+
+  job->fd       = -1;
+  job->filename = strdup(filename);
+  job->state    = IPP_JSTATE_PENDING;
+
+  _cupsRWUnlock(&(client->printer->rwlock));
+
+ /*
+  * Process the job...
+  */
+
+#if 0
+  if (!_cupsThreadCreate((_cups_thread_func_t)process_job, job))
+  {
+    job->state = IPP_JSTATE_ABORTED;
+    respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to process job.");
+    return;
+  }
+
+#else
+  process_job(job);
+#endif /* 0 */
+
+ /*
+  * Return the job info...
+  */
+
+  respond_ipp(client, IPP_STATUS_OK, NULL);
+
+  ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+  cupsArrayAdd(ra, "job-id");
+  cupsArrayAdd(ra, "job-state");
+  cupsArrayAdd(ra, "job-state-reasons");
+  cupsArrayAdd(ra, "job-uri");
+
+  copy_job_attributes(client, job, ra);
+  cupsArrayDelete(ra);
+}
+
+
+/*
+ * 'ipp_send_uri()' - Add a referenced document to a job object created with
+ *                    Create-Job.
+ */
+
+static void
+ipp_send_uri(_ipp_client_t *client)    /* I - Client */
+{
+  _ipp_job_t           *job;           /* Job information */
+  ipp_attribute_t      *uri;           /* document-uri */
+  char                 scheme[256],    /* URI scheme */
+                       userpass[256],  /* Username and password info */
+                       hostname[256],  /* Hostname */
+                       resource[1024]; /* Resource path */
+  int                  port;           /* Port number */
+  http_uri_status_t    uri_status;     /* URI decode status */
+  http_encryption_t    encryption;     /* Encryption to use, if any */
+  http_t               *http;          /* Connection for http/https URIs */
+  http_status_t                status;         /* Access status for http/https URIs */
+  int                  infile;         /* Input file for local file URIs */
+  char                 filename[1024], /* Filename buffer */
+                       buffer[4096];   /* Copy buffer */
+  ssize_t              bytes;          /* Bytes read */
+  ipp_attribute_t      *attr;          /* Current attribute */
+  cups_array_t         *ra;            /* Attributes to send in response */
+  static const char * const uri_status_strings[] =
+  {                                    /* URI decode errors */
+    "URI too large.",
+    "Bad arguments to function.",
+    "Bad resource in URI.",
+    "Bad port number in URI.",
+    "Bad hostname in URI.",
+    "Bad username in URI.",
+    "Bad scheme in URI.",
+    "Bad/empty URI."
+  };
+
+
+ /*
+  * Get the job...
+  */
+
+  if ((job = find_job(client)) == NULL)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "Job does not exist.");
+    httpFlush(client->http);
+    return;
+  }
+
+ /*
+  * See if we already have a document for this job or the job has already
+  * in a non-pending state...
+  */
+
+  if (job->state > IPP_JSTATE_HELD)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_NOT_POSSIBLE,
+                "Job is not in a pending state.");
+    httpFlush(client->http);
+    return;
+  }
+  else if (job->filename || job->fd >= 0)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_MULTIPLE_JOBS_NOT_SUPPORTED,
+                "Multiple document jobs are not supported.");
+    httpFlush(client->http);
+    return;
+  }
+
+  if ((attr = ippFindAttribute(client->request, "last-document",
+                               IPP_TAG_ZERO)) == NULL)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+                "Missing required last-document attribute.");
+    httpFlush(client->http);
+    return;
+  }
+  else if (ippGetValueTag(attr) != IPP_TAG_BOOLEAN || ippGetCount(attr) != 1 ||
+           !ippGetBoolean(attr, 0))
+  {
+    respond_unsupported(client, attr);
+    httpFlush(client->http);
+    return;
+  }
+
+ /*
+  * Validate document attributes...
+  */
+
+  if (!valid_doc_attributes(client))
+  {
+    httpFlush(client->http);
+    return;
+  }
+
+ /*
+  * Do we have a file to print?
+  */
+
+  if (httpGetState(client->http) == HTTP_STATE_POST_RECV)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+                "Unexpected document data following request.");
+    return;
+  }
+
+ /*
+  * Do we have a document URI?
+  */
+
+  if ((uri = ippFindAttribute(client->request, "document-uri",
+                              IPP_TAG_URI)) == NULL)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Missing document-uri.");
+    return;
+  }
+
+  if (ippGetCount(uri) != 1)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+                "Too many document-uri values.");
+    return;
+  }
+
+  uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, ippGetString(uri, 0, NULL),
+                               scheme, sizeof(scheme), userpass,
+                               sizeof(userpass), hostname, sizeof(hostname),
+                               &port, resource, sizeof(resource));
+  if (uri_status < HTTP_URI_STATUS_OK)
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Bad document-uri: %s",
+                uri_status_strings[uri_status - HTTP_URI_STATUS_OVERFLOW]);
+    return;
+  }
+
+  if (strcmp(scheme, "file") &&
+#ifdef HAVE_SSL
+      strcmp(scheme, "https") &&
+#endif /* HAVE_SSL */
+      strcmp(scheme, "http"))
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_URI_SCHEME,
+                "URI scheme \"%s\" not supported.", scheme);
+    return;
+  }
+
+  if (!strcmp(scheme, "file") && access(resource, R_OK))
+  {
+    respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS,
+                "Unable to access URI: %s", strerror(errno));
+    return;
+  }
+
+ /*
+  * Get the document format for the job...
+  */
+
+  _cupsRWLockWrite(&(client->printer->rwlock));
+
+  if ((attr = ippFindAttribute(job->attrs, "document-format",
+                               IPP_TAG_MIMETYPE)) != NULL)
+    job->format = ippGetString(attr, 0, NULL);
+  else
+    job->format = "application/octet-stream";
+
+ /*
+  * Create a file for the request data...
+  */
+
+  if (!_cups_strcasecmp(job->format, "image/jpeg"))
+    snprintf(filename, sizeof(filename), "%s/%d.jpg",
+             client->printer->directory, job->id);
+  else if (!_cups_strcasecmp(job->format, "image/png"))
+    snprintf(filename, sizeof(filename), "%s/%d.png",
+             client->printer->directory, job->id);
+  else if (!_cups_strcasecmp(job->format, "application/pdf"))
+    snprintf(filename, sizeof(filename), "%s/%d.pdf",
+             client->printer->directory, job->id);
+  else if (!_cups_strcasecmp(job->format, "application/postscript"))
+    snprintf(filename, sizeof(filename), "%s/%d.ps",
+             client->printer->directory, job->id);
+  else
+    snprintf(filename, sizeof(filename), "%s/%d.prn",
+             client->printer->directory, job->id);
+
+  job->fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+
+  _cupsRWUnlock(&(client->printer->rwlock));
+
+  if (job->fd < 0)
+  {
+    job->state = IPP_JSTATE_ABORTED;
+
+    respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+                "Unable to create print file: %s", strerror(errno));
+    return;
+  }
+
+  if (!strcmp(scheme, "file"))
+  {
+    if ((infile = open(resource, O_RDONLY)) < 0)
+    {
+      respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS,
+                  "Unable to access URI: %s", strerror(errno));
+      return;
+    }
+
+    do
+    {
+      if ((bytes = read(infile, buffer, sizeof(buffer))) < 0 &&
+          (errno == EAGAIN || errno == EINTR))
+        bytes = 1;
+      else if (bytes > 0 && write(job->fd, buffer, (size_t)bytes) < bytes)
+      {
+       int error = errno;              /* Write error */
+
+       job->state = IPP_JSTATE_ABORTED;
+
+       close(job->fd);
+       job->fd = -1;
+
+       unlink(filename);
+       close(infile);
+
+       respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+                   "Unable to write print file: %s", strerror(error));
+       return;
+      }
+    }
+    while (bytes > 0);
+
+    close(infile);
+  }
+  else
+  {
+#ifdef HAVE_SSL
+    if (port == 443 || !strcmp(scheme, "https"))
+      encryption = HTTP_ENCRYPTION_ALWAYS;
+    else
+#endif /* HAVE_SSL */
+    encryption = HTTP_ENCRYPTION_IF_REQUESTED;
+
+    if ((http = httpConnect2(hostname, port, NULL, AF_UNSPEC, encryption,
+                             1, 30000, NULL)) == NULL)
+    {
+      respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS,
+                  "Unable to connect to %s: %s", hostname,
+                 cupsLastErrorString());
+      job->state = IPP_JSTATE_ABORTED;
+
+      close(job->fd);
+      job->fd = -1;
+
+      unlink(filename);
+      return;
+    }
+
+    httpClearFields(http);
+    httpSetField(http, HTTP_FIELD_ACCEPT_LANGUAGE, "en");
+    if (httpGet(http, resource))
+    {
+      respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS,
+                  "Unable to GET URI: %s", strerror(errno));
+
+      job->state = IPP_JSTATE_ABORTED;
+
+      close(job->fd);
+      job->fd = -1;
+
+      unlink(filename);
+      httpClose(http);
+      return;
+    }
+
+    while ((status = httpUpdate(http)) == HTTP_STATUS_CONTINUE);
+
+    if (status != HTTP_STATUS_OK)
+    {
+      respond_ipp(client, IPP_STATUS_ERROR_DOCUMENT_ACCESS,
+                  "Unable to GET URI: %s", httpStatus(status));
+
+      job->state = IPP_JSTATE_ABORTED;
+
+      close(job->fd);
+      job->fd = -1;
+
+      unlink(filename);
+      httpClose(http);
+      return;
+    }
+
+    while ((bytes = httpRead2(http, buffer, sizeof(buffer))) > 0)
+    {
+      if (write(job->fd, buffer, (size_t)bytes) < bytes)
+      {
+       int error = errno;              /* Write error */
+
+       job->state = IPP_JSTATE_ABORTED;
+
+       close(job->fd);
+       job->fd = -1;
+
+       unlink(filename);
+       httpClose(http);
+
+       respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+                   "Unable to write print file: %s", strerror(error));
+       return;
+      }
+    }
+
+    httpClose(http);
+  }
+
+  if (close(job->fd))
+  {
+    int error = errno;         /* Write error */
+
+    job->state = IPP_JSTATE_ABORTED;
+    job->fd    = -1;
+
+    unlink(filename);
+
+    respond_ipp(client, IPP_STATUS_ERROR_INTERNAL,
+                "Unable to write print file: %s", strerror(error));
+    return;
+  }
+
+  _cupsRWLockWrite(&(client->printer->rwlock));
+
+  job->fd       = -1;
+  job->filename = strdup(filename);
+  job->state    = IPP_JSTATE_PENDING;
+
+  _cupsRWUnlock(&(client->printer->rwlock));
+
+ /*
+  * Process the job...
+  */
+
+#if 0
+  if (!_cupsThreadCreate((_cups_thread_func_t)process_job, job))
+  {
+    job->state = IPP_JSTATE_ABORTED;
+    respond_ipp(client, IPP_STATUS_ERROR_INTERNAL, "Unable to process job.");
+    return;
+  }
+
+#else
+  process_job(job);
+#endif /* 0 */
+
+ /*
+  * Return the job info...
+  */
+
+  respond_ipp(client, IPP_STATUS_OK, NULL);
+
+  ra = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+  cupsArrayAdd(ra, "job-id");
+  cupsArrayAdd(ra, "job-state");
+  cupsArrayAdd(ra, "job-state-reasons");
+  cupsArrayAdd(ra, "job-uri");
+
+  copy_job_attributes(client, job, ra);
+  cupsArrayDelete(ra);
+}
+
+
+/*
+ * 'ipp_validate_job()' - Validate job creation attributes.
+ */
+
+static void
+ipp_validate_job(_ipp_client_t *client)        /* I - Client */
+{
+  if (valid_job_attributes(client))
+    respond_ipp(client, IPP_STATUS_OK, NULL);
+}
+
+
+/*
+ * 'process_client()' - Process client requests on a thread.
+ */
+
+static void *                          /* O - Exit status */
+process_client(_ipp_client_t *client)  /* I - Client */
 {
  /*
   * Loop until we are out of requests or timeout (30 seconds)...
   */
 
-  while (httpWait(&(client->http), 30000))
+#ifdef HAVE_SSL
+  int first_time = 1;                  /* First time request? */
+#endif /* HAVE_SSL */
+
+  while (httpWait(client->http, 30000))
+  {
+#ifdef HAVE_SSL
+    if (first_time)
+    {
+     /*
+      * See if we need to negotiate a TLS connection...
+      */
+
+      char buf[1];                     /* First byte from client */
+
+      if (recv(httpGetFd(client->http), buf, 1, MSG_PEEK) == 1 && (!buf[0] || !strchr("DGHOPT", buf[0])))
+      {
+        fprintf(stderr, "%s Negotiating TLS session.\n", client->hostname);
+
+       if (httpEncryption(client->http, HTTP_ENCRYPTION_ALWAYS))
+       {
+         fprintf(stderr, "%s Unable to encrypt connection: %s\n", client->hostname, cupsLastErrorString());
+         break;
+        }
+      }
+
+      first_time = 0;
+    }
+#endif /* HAVE_SSL */
+
     if (!process_http(client))
       break;
+  }
 
  /*
   * Close the conection to the client and return...
@@ -3045,175 +3763,127 @@ process_client(_ipp_client_t *client) /* I - Client */
 int                                    /* O - 1 on success, 0 on failure */
 process_http(_ipp_client_t *client)    /* I - Client connection */
 {
-  char                 line[4096],     /* Line from client... */
-                       operation[64],  /* Operation code from socket */
-                       uri[1024],      /* URI */
-                       version[64],    /* HTTP version number string */
-                       *ptr;           /* Pointer into strings */
-  int                  major, minor;   /* HTTP version numbers */
-  http_status_t                status;         /* Transfer status */
-  ipp_state_t          state;          /* State of IPP transfer */
-
-
- /*
-  * Abort if we have an error on the connection...
-  */
+  char                 uri[1024];      /* URI */
+  http_state_t         http_state;     /* HTTP state */
+  http_status_t                http_status;    /* HTTP status */
+  ipp_state_t          ipp_state;      /* State of IPP transfer */
+  char                 scheme[32],     /* Method/scheme */
+                       userpass[128],  /* Username:password */
+                       hostname[HTTP_MAX_HOST];
+                                       /* Hostname */
+  int                  port;           /* Port number */
+  const char           *encoding;      /* Content-Encoding value */
+  static const char * const http_states[] =
+  {                                    /* Strings for logging HTTP method */
+    "WAITING",
+    "OPTIONS",
+    "GET",
+    "GET_SEND",
+    "HEAD",
+    "POST",
+    "POST_RECV",
+    "POST_SEND",
+    "PUT",
+    "PUT_RECV",
+    "DELETE",
+    "TRACE",
+    "CONNECT",
+    "STATUS",
+    "UNKNOWN_METHOD",
+    "UNKNOWN_VERSION"
+  };
 
-  if (client->http.error)
-    return (0);
 
  /*
   * Clear state variables...
   */
 
-  httpClearFields(&(client->http));
   ippDelete(client->request);
   ippDelete(client->response);
 
-  client->http.activity       = time(NULL);
-  client->http.version        = HTTP_1_1;
-  client->http.keep_alive     = HTTP_KEEPALIVE_OFF;
-  client->http.data_encoding  = HTTP_ENCODE_LENGTH;
-  client->http.data_remaining = 0;
-  client->request             = NULL;
-  client->response            = NULL;
-  client->operation           = HTTP_WAITING;
+  client->request   = NULL;
+  client->response  = NULL;
+  client->operation = HTTP_STATE_WAITING;
 
  /*
   * Read a request from the connection...
   */
 
-  while ((ptr = httpGets(line, sizeof(line) - 1, &(client->http))) != NULL)
-    if (*ptr)
-      break;
-
-  if (!ptr)
-    return (0);
+  while ((http_state = httpReadRequest(client->http, uri,
+                                       sizeof(uri))) == HTTP_STATE_WAITING)
+    usleep(1);
 
  /*
   * Parse the request line...
   */
 
-  fprintf(stderr, "%s %s\n", client->http.hostname, line);
-
-  switch (sscanf(line, "%63s%1023s%63s", operation, uri, version))
+  if (http_state == HTTP_STATE_ERROR)
   {
-    case 1 :
-       fprintf(stderr, "%s Bad request line.\n", client->http.hostname);
-       respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
-       return (0);
-
-    case 2 :
-       client->http.version = HTTP_0_9;
-       break;
-
-    case 3 :
-       if (sscanf(version, "HTTP/%d.%d", &major, &minor) != 2)
-       {
-         fprintf(stderr, "%s Bad HTTP version.\n", client->http.hostname);
-         respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
-         return (0);
-       }
+    if (httpError(client->http) == EPIPE)
+      fprintf(stderr, "%s Client closed connection.\n", client->hostname);
+    else
+      fprintf(stderr, "%s Bad request line (%s).\n", client->hostname,
+              strerror(httpError(client->http)));
 
-       if (major < 2)
-       {
-         client->http.version = (http_version_t)(major * 100 + minor);
-         if (client->http.version == HTTP_1_1)
-           client->http.keep_alive = HTTP_KEEPALIVE_ON;
-         else
-           client->http.keep_alive = HTTP_KEEPALIVE_OFF;
-       }
-       else
-       {
-         respond_http(client, HTTP_NOT_SUPPORTED, NULL, 0);
-         return (0);
-       }
-       break;
+    return (0);
   }
-
- /*
-  * Handle full URLs in the request line...
-  */
-
-  if (!strncmp(client->uri, "http:", 5) || !strncmp(client->uri, "ipp:", 4))
+  else if (http_state == HTTP_STATE_UNKNOWN_METHOD)
   {
-    char       scheme[32],             /* Method/scheme */
-               userpass[128],          /* Username:password */
-               hostname[HTTP_MAX_HOST];/* Hostname */
-    int                port;                   /* Port number */
-
-   /*
-    * Separate the URI into its components...
-    */
-
-    if (httpSeparateURI(HTTP_URI_CODING_MOST, uri, scheme, sizeof(scheme),
-                       userpass, sizeof(userpass),
-                       hostname, sizeof(hostname), &port,
-                       client->uri, sizeof(client->uri)) < HTTP_URI_OK)
-    {
-      fprintf(stderr, "%s Bad URI \"%s\".\n", client->http.hostname, uri);
-      respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
-      return (0);
-    }
+    fprintf(stderr, "%s Bad/unknown operation.\n", client->hostname);
+    respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
+    return (0);
   }
-  else
+  else if (http_state == HTTP_STATE_UNKNOWN_VERSION)
   {
-   /*
-    * Decode URI
-    */
-
-    if (!_httpDecodeURI(client->uri, uri, sizeof(client->uri)))
-    {
-      fprintf(stderr, "%s Bad URI \"%s\".\n", client->http.hostname, uri);
-      respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
-      return (0);
-    }
+    fprintf(stderr, "%s Bad HTTP version.\n", client->hostname);
+    respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
+    return (0);
   }
 
+  fprintf(stderr, "%s %s %s\n", client->hostname, http_states[http_state],
+          uri);
+
  /*
-  * Process the request...
+  * Separate the URI into its components...
   */
 
-  if (!strcmp(operation, "GET"))
-    client->http.state = HTTP_GET;
-  else if (!strcmp(operation, "POST"))
-    client->http.state = HTTP_POST;
-  else if (!strcmp(operation, "OPTIONS"))
-    client->http.state = HTTP_OPTIONS;
-  else if (!strcmp(operation, "HEAD"))
-    client->http.state = HTTP_HEAD;
-  else
+  if (httpSeparateURI(HTTP_URI_CODING_MOST, uri, scheme, sizeof(scheme),
+                     userpass, sizeof(userpass),
+                     hostname, sizeof(hostname), &port,
+                     client->uri, sizeof(client->uri)) < HTTP_URI_STATUS_OK)
   {
-    fprintf(stderr, "%s Bad operation \"%s\".\n", client->http.hostname,
-            operation);
-    respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
+    fprintf(stderr, "%s Bad URI \"%s\".\n", client->hostname, uri);
+    respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
     return (0);
   }
 
-  client->start       = time(NULL);
-  client->operation   = client->http.state;
-  client->http.status = HTTP_OK;
+ /*
+  * Process the request...
+  */
+
+  client->start     = time(NULL);
+  client->operation = httpGetState(client->http);
 
  /*
   * Parse incoming parameters until the status changes...
   */
 
-  while ((status = httpUpdate(&(client->http))) == HTTP_CONTINUE);
+  while ((http_status = httpUpdate(client->http)) == HTTP_STATUS_CONTINUE);
 
-  if (status != HTTP_OK)
+  if (http_status != HTTP_STATUS_OK)
   {
-    respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
+    respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
     return (0);
   }
 
-  if (!client->http.fields[HTTP_FIELD_HOST][0] &&
-      client->http.version >= HTTP_1_1)
+  if (!httpGetField(client->http, HTTP_FIELD_HOST)[0] &&
+      httpGetVersion(client->http) >= HTTP_VERSION_1_1)
   {
    /*
     * HTTP/1.1 and higher require the "Host:" field...
     */
 
-    respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
+    respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
     return (0);
   }
 
@@ -3221,9 +3891,10 @@ process_http(_ipp_client_t *client)      /* I - Client connection */
   * Handle HTTP Upgrade...
   */
 
-  if (!_cups_strcasecmp(client->http.fields[HTTP_FIELD_CONNECTION], "Upgrade"))
+  if (!_cups_strcasecmp(httpGetField(client->http, HTTP_FIELD_CONNECTION),
+                        "Upgrade"))
   {
-    if (!respond_http(client, HTTP_NOT_IMPLEMENTED, NULL, 0))
+    if (!respond_http(client, HTTP_STATUS_NOT_IMPLEMENTED, NULL, NULL, 0))
       return (0);
   }
 
@@ -3231,16 +3902,17 @@ process_http(_ipp_client_t *client)     /* I - Client connection */
   * Handle HTTP Expect...
   */
 
-  if (client->http.expect &&
-      (client->operation == HTTP_POST || client->operation == HTTP_PUT))
+  if (httpGetExpect(client->http) &&
+      (client->operation == HTTP_STATE_POST ||
+       client->operation == HTTP_STATE_PUT))
   {
-    if (client->http.expect == HTTP_CONTINUE)
+    if (httpGetExpect(client->http) == HTTP_STATUS_CONTINUE)
     {
      /*
       * Send 100-continue header...
       */
 
-      if (!respond_http(client, HTTP_CONTINUE, NULL, 0))
+      if (!respond_http(client, HTTP_STATUS_CONTINUE, NULL, NULL, 0))
        return (0);
     }
     else
@@ -3249,13 +3921,8 @@ process_http(_ipp_client_t *client)      /* I - Client connection */
       * Send 417-expectation-failed header...
       */
 
-      if (!respond_http(client, HTTP_EXPECTATION_FAILED, NULL, 0))
+      if (!respond_http(client, HTTP_STATUS_EXPECTATION_FAILED, NULL, NULL, 0))
        return (0);
-
-      httpPrintf(&(client->http), "Content-Length: 0\r\n");
-      httpPrintf(&(client->http), "\r\n");
-      httpFlushWrite(&(client->http));
-      client->http.data_encoding = HTTP_ENCODE_LENGTH;
     }
   }
 
@@ -3263,25 +3930,26 @@ process_http(_ipp_client_t *client)     /* I - Client connection */
   * Handle new transfers...
   */
 
+  encoding = httpGetContentEncoding(client->http);
+
   switch (client->operation)
   {
-    case HTTP_OPTIONS :
+    case HTTP_STATE_OPTIONS :
        /*
        * Do HEAD/OPTIONS command...
        */
 
-       return (respond_http(client, HTTP_OK, NULL, 0));
+       return (respond_http(client, HTTP_STATUS_OK, NULL, NULL, 0));
 
-    case HTTP_HEAD :
+    case HTTP_STATE_HEAD :
         if (!strcmp(client->uri, "/icon.png"))
-         return (respond_http(client, HTTP_OK, "image/png", 0));
+         return (respond_http(client, HTTP_STATUS_OK, NULL, "image/png", 0));
        else if (!strcmp(client->uri, "/"))
-         return (respond_http(client, HTTP_OK, "text/html", 0));
+         return (respond_http(client, HTTP_STATUS_OK, NULL, "text/html", 0));
        else
-         return (respond_http(client, HTTP_NOT_FOUND, NULL, 0));
-       break;
+         return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
 
-    case HTTP_GET :
+    case HTTP_STATE_GET :
         if (!strcmp(client->uri, "/icon.png"))
        {
         /*
@@ -3293,24 +3961,27 @@ process_http(_ipp_client_t *client)     /* I - Client connection */
          char          buffer[4096];   /* Copy buffer */
          ssize_t       bytes;          /* Bytes */
 
+          fprintf(stderr, "Icon file is \"%s\".\n", client->printer->icon);
+
           if (!stat(client->printer->icon, &fileinfo) &&
              (fd = open(client->printer->icon, O_RDONLY)) >= 0)
          {
-           if (!respond_http(client, HTTP_OK, "image/png", fileinfo.st_size))
+           if (!respond_http(client, HTTP_STATUS_OK, NULL, "image/png",
+                             (size_t)fileinfo.st_size))
            {
              close(fd);
              return (0);
            }
 
            while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
-             httpWrite2(&(client->http), buffer, bytes);
+             httpWrite2(client->http, buffer, (size_t)bytes);
 
-           httpFlushWrite(&(client->http));
+           httpFlushWrite(client->http);
 
            close(fd);
          }
          else
-           return (respond_http(client, HTTP_NOT_FOUND, NULL, 0));
+           return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
        }
        else if (!strcmp(client->uri, "/"))
        {
@@ -3318,7 +3989,7 @@ process_http(_ipp_client_t *client)       /* I - Client connection */
          * Show web status page...
          */
 
-          if (!respond_http(client, HTTP_OK, "text/html", 0))
+          if (!respond_http(client, HTTP_STATUS_OK, encoding, "text/html", 0))
            return (0);
 
           html_printf(client,
@@ -3332,43 +4003,32 @@ process_http(_ipp_client_t *client)     /* I - Client connection */
                      "</head>\n"
                      "<body>\n"
                      "</body>\n"
-                     "<h1>%s</h1>\n"
+                     "<h1><img align=\"right\" src=\"/icon.png\">%s</h1>\n"
                      "<p>%s, %d job(s).</p>\n"
                      "</body>\n"
                      "</html>\n",
                      client->printer->name, client->printer->name,
-                     client->printer->state == IPP_PRINTER_IDLE ? "Idle" :
-                         client->printer->state == IPP_PRINTER_PROCESSING ?
+                     client->printer->state == IPP_PSTATE_IDLE ? "Idle" :
+                         client->printer->state == IPP_PSTATE_PROCESSING ?
                          "Printing" : "Stopped",
                      cupsArrayCount(client->printer->jobs));
-          httpWrite2(&(client->http), "", 0);
+          httpWrite2(client->http, "", 0);
 
          return (1);
        }
        else
-         return (respond_http(client, HTTP_NOT_FOUND, NULL, 0));
+         return (respond_http(client, HTTP_STATUS_NOT_FOUND, NULL, NULL, 0));
        break;
 
-    case HTTP_POST :
-       if (client->http.data_remaining < 0 ||
-           (!client->http.fields[HTTP_FIELD_CONTENT_LENGTH][0] &&
-            client->http.data_encoding == HTTP_ENCODE_LENGTH))
-       {
-        /*
-         * Negative content lengths are invalid...
-         */
-
-         return (respond_http(client, HTTP_BAD_REQUEST, NULL, 0));
-       }
-
-       if (strcmp(client->http.fields[HTTP_FIELD_CONTENT_TYPE],
+    case HTTP_STATE_POST :
+       if (strcmp(httpGetField(client->http, HTTP_FIELD_CONTENT_TYPE),
                   "application/ipp"))
         {
         /*
          * Not an IPP request...
          */
 
-         return (respond_http(client, HTTP_BAD_REQUEST, NULL, 0));
+         return (respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0));
        }
 
        /*
@@ -3377,14 +4037,17 @@ process_http(_ipp_client_t *client)     /* I - Client connection */
 
        client->request = ippNew();
 
-        while ((state = ippRead(&(client->http), client->request)) != IPP_DATA)
-         if (state == IPP_ERROR)
+        while ((ipp_state = ippRead(client->http,
+                                    client->request)) != IPP_STATE_DATA)
+       {
+         if (ipp_state == IPP_STATE_ERROR)
          {
-            fprintf(stderr, "%s IPP read error (%s).\n", client->http.hostname,
-                   ippOpString(client->request->request.op.operation_id));
-           respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
+            fprintf(stderr, "%s IPP read error (%s).\n", client->hostname,
+                   cupsLastErrorString());
+           respond_http(client, HTTP_STATUS_BAD_REQUEST, NULL, NULL, 0);
            return (0);
          }
+       }
 
        /*
         * Now that we have the IPP request, process the request...
@@ -3412,45 +4075,40 @@ process_ipp(_ipp_client_t *client)      /* I - Client */
   ipp_attribute_t      *charset;       /* Character set attribute */
   ipp_attribute_t      *language;      /* Language attribute */
   ipp_attribute_t      *uri;           /* Printer URI attribute */
+  int                  major, minor;   /* Version number */
+  const char           *name;          /* Name of attribute */
 
 
-  debug_attributes("Request", client->request);
+  debug_attributes("Request", client->request, 1);
 
  /*
   * First build an empty response message for this request...
   */
 
-  client->operation_id = client->request->request.op.operation_id;
-  client->response     = ippNew();
-
-  client->response->request.status.version[0] =
-      client->request->request.op.version[0];
-  client->response->request.status.version[1] =
-      client->request->request.op.version[1];
-  client->response->request.status.request_id =
-      client->request->request.op.request_id;
+  client->operation_id = ippGetOperation(client->request);
+  client->response     = ippNewResponse(client->request);
 
  /*
   * Then validate the request header and required attributes...
   */
 
-  if (client->request->request.any.version[0] < 1 ||
-      client->request->request.any.version[0] > 2)
+  major = ippGetVersion(client->request, &minor);
+
+  if (major < 1 || major > 2)
   {
    /*
     * Return an error, since we only support IPP 1.x and 2.x.
     */
 
-    respond_ipp(client, IPP_VERSION_NOT_SUPPORTED,
-                "Bad request version number %d.%d.",
-               client->request->request.any.version[0],
-               client->request->request.any.version[1]);
+    respond_ipp(client, IPP_STATUS_ERROR_VERSION_NOT_SUPPORTED,
+                "Bad request version number %d.%d.", major, minor);
   }
-  else if (client->request->request.any.request_id <= 0)
-    respond_ipp(client, IPP_BAD_REQUEST, "Bad request-id %d.",
-                client->request->request.any.request_id);
-  else if (!client->request->attrs)
-    respond_ipp(client, IPP_BAD_REQUEST, "No attributes in request.");
+  else if (ippGetRequestId(client->request) <= 0)
+    respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST, "Bad request-id %d.",
+                ippGetRequestId(client->request));
+  else if (!ippFirstAttribute(client->request))
+    respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+                "No attributes in request.");
   else
   {
    /*
@@ -3458,22 +4116,25 @@ process_ipp(_ipp_client_t *client)      /* I - Client */
     * don't repeat groups...
     */
 
-    for (attr = client->request->attrs, group = attr->group_tag;
+    for (attr = ippFirstAttribute(client->request),
+             group = ippGetGroupTag(attr);
         attr;
-        attr = attr->next)
-      if (attr->group_tag < group && attr->group_tag != IPP_TAG_ZERO)
+        attr = ippNextAttribute(client->request))
+    {
+      if (ippGetGroupTag(attr) < group && ippGetGroupTag(attr) != IPP_TAG_ZERO)
       {
        /*
        * Out of order; return an error...
        */
 
-       respond_ipp(client, IPP_BAD_REQUEST,
-                      "Attribute groups are out of order (%x < %x).",
-                      attr->group_tag, group);
+       respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+                   "Attribute groups are out of order (%x < %x).",
+                   ippGetGroupTag(attr), group);
        break;
       }
       else
-       group = attr->group_tag;
+       group = ippGetGroupTag(attr);
+    }
 
     if (!attr)
     {
@@ -3485,20 +4146,19 @@ process_ipp(_ipp_client_t *client)      /* I - Client */
       *     printer-uri/job-uri
       */
 
-      attr = client->request->attrs;
-      if (attr && attr->name &&
-          !strcmp(attr->name, "attributes-charset") &&
-         (attr->value_tag & IPP_TAG_MASK) == IPP_TAG_CHARSET)
+      attr = ippFirstAttribute(client->request);
+      name = ippGetName(attr);
+      if (attr && name && !strcmp(name, "attributes-charset") &&
+         ippGetValueTag(attr) == IPP_TAG_CHARSET)
        charset = attr;
       else
        charset = NULL;
 
-      if (attr)
-        attr = attr->next;
+      attr = ippNextAttribute(client->request);
+      name = ippGetName(attr);
 
-      if (attr && attr->name &&
-          !strcmp(attr->name, "attributes-natural-language") &&
-         (attr->value_tag & IPP_TAG_MASK) == IPP_TAG_LANGUAGE)
+      if (attr && name && !strcmp(name, "attributes-natural-language") &&
+         ippGetValueTag(attr) == IPP_TAG_LANGUAGE)
        language = attr;
       else
        language = NULL;
@@ -3512,25 +4172,17 @@ process_ipp(_ipp_client_t *client)      /* I - Client */
       else
        uri = NULL;
 
-      ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
-                  "attributes-charset", NULL,
-                  charset ? charset->values[0].string.text : "utf-8");
-
-      ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
-                  "attributes-natural-language", NULL,
-                  language ? language->values[0].string.text : "en");
-
       if (charset &&
-          _cups_strcasecmp(charset->values[0].string.text, "us-ascii") &&
-          _cups_strcasecmp(charset->values[0].string.text, "utf-8"))
+          _cups_strcasecmp(ippGetString(charset, 0, NULL), "us-ascii") &&
+          _cups_strcasecmp(ippGetString(charset, 0, NULL), "utf-8"))
       {
        /*
         * Bad character set...
        */
 
-       respond_ipp(client, IPP_BAD_REQUEST,
+       respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
                    "Unsupported character set \"%s\".",
-                   charset->values[0].string.text);
+                   ippGetString(charset, 0, NULL));
       }
       else if (!charset || !language || !uri)
       {
@@ -3540,61 +4192,85 @@ process_ipp(_ipp_client_t *client)      /* I - Client */
        * for all operations.
        */
 
-       respond_ipp(client, IPP_BAD_REQUEST, "Missing required attributes.");
-      }
-      else if (strcmp(uri->values[0].string.text, client->printer->uri) &&
-               strncmp(uri->values[0].string.text, client->printer->uri,
-                      client->printer->urilen))
-      {
-        respond_ipp(client, IPP_NOT_FOUND, "%s %s not found.", uri->name,
-                   uri->values[0].string.text);
+       respond_ipp(client, IPP_STATUS_ERROR_BAD_REQUEST,
+                   "Missing required attributes.");
       }
       else
       {
-       /*
-        * Try processing the operation...
-       */
-
-        if (client->http.expect == HTTP_CONTINUE)
+        char           scheme[32],     /* URI scheme */
+                       userpass[32],   /* Username/password in URI */
+                       host[256],      /* Host name in URI */
+                       resource[256];  /* Resource path in URI */
+       int             port;           /* Port number in URI */
+
+        name = ippGetName(uri);
+
+        if (httpSeparateURI(HTTP_URI_CODING_ALL, ippGetString(uri, 0, NULL),
+                            scheme, sizeof(scheme),
+                            userpass, sizeof(userpass),
+                            host, sizeof(host), &port,
+                            resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
+         respond_ipp(client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES,
+                     "Bad %s value '%s'.", name, ippGetString(uri, 0, NULL));
+        else if ((!strcmp(name, "job-uri") &&
+                  strncmp(resource, "/ipp/print/", 11)) ||
+                 (!strcmp(name, "printer-uri") &&
+                  strcmp(resource, "/ipp/print")))
+         respond_ipp(client, IPP_STATUS_ERROR_NOT_FOUND, "%s %s not found.",
+                     name, ippGetString(uri, 0, NULL));
+       else
        {
         /*
-         * Send 100-continue header...
+         * Try processing the operation...
          */
 
-         if (!respond_http(client, HTTP_CONTINUE, NULL, 0))
-           return (0);
-       }
-
-       switch (client->request->request.op.operation_id)
-       {
-         case IPP_PRINT_JOB :
-              ipp_print_job(client);
-              break;
-
-         case IPP_VALIDATE_JOB :
-              ipp_validate_job(client);
-              break;
-
-         case IPP_CANCEL_JOB :
-              ipp_cancel_job(client);
-              break;
-
-         case IPP_GET_JOB_ATTRIBUTES :
-              ipp_get_job_attributes(client);
-              break;
-
-         case IPP_GET_JOBS :
-              ipp_get_jobs(client);
-              break;
-
-         case IPP_GET_PRINTER_ATTRIBUTES :
-              ipp_get_printer_attributes(client);
-              break;
-
-         default :
-             respond_ipp(client, IPP_OPERATION_NOT_SUPPORTED,
-                         "Operation not supported.");
-             break;
+         switch (ippGetOperation(client->request))
+         {
+           case IPP_OP_PRINT_JOB :
+               ipp_print_job(client);
+               break;
+
+           case IPP_OP_PRINT_URI :
+               ipp_print_uri(client);
+               break;
+
+           case IPP_OP_VALIDATE_JOB :
+               ipp_validate_job(client);
+               break;
+
+           case IPP_OP_CREATE_JOB :
+               ipp_create_job(client);
+               break;
+
+           case IPP_OP_SEND_DOCUMENT :
+               ipp_send_document(client);
+               break;
+
+           case IPP_OP_SEND_URI :
+               ipp_send_uri(client);
+               break;
+
+           case IPP_OP_CANCEL_JOB :
+               ipp_cancel_job(client);
+               break;
+
+           case IPP_OP_GET_JOB_ATTRIBUTES :
+               ipp_get_job_attributes(client);
+               break;
+
+           case IPP_OP_GET_JOBS :
+               ipp_get_jobs(client);
+               break;
+
+           case IPP_OP_GET_PRINTER_ATTRIBUTES :
+               ipp_get_printer_attributes(client);
+               break;
+
+           default :
+               respond_ipp(client, IPP_STATUS_ERROR_OPERATION_NOT_SUPPORTED,
+                           "Operation not supported.");
+               break;
+         }
        }
       }
     }
@@ -3604,10 +4280,10 @@ process_ipp(_ipp_client_t *client)      /* I - Client */
   * Send the HTTP header and return...
   */
 
-  if (client->http.state != HTTP_POST_SEND)
-    httpFlush(&(client->http));                /* Flush trailing (junk) data */
+  if (httpGetState(client->http) != HTTP_STATE_POST_SEND)
+    httpFlush(client->http);           /* Flush trailing (junk) data */
 
-  return (respond_http(client, HTTP_OK, "application/ipp",
+  return (respond_http(client, HTTP_STATUS_OK, NULL, "application/ipp",
                        ippLength(client->response)));
 }
 
@@ -3619,18 +4295,92 @@ process_ipp(_ipp_client_t *client)      /* I - Client */
 static void *                          /* O - Thread exit status */
 process_job(_ipp_job_t *job)           /* I - Job */
 {
-  job->state          = IPP_JOB_PROCESSING;
-  job->printer->state = IPP_PRINTER_PROCESSING;
+  job->state          = IPP_JSTATE_PROCESSING;
+  job->printer->state = IPP_PSTATE_PROCESSING;
+
+  if (job->printer->command)
+  {
+   /*
+    * Execute a command with the job spool file and wait for it to complete...
+    */
+
+    int        pid,                    /* Process ID */
+               status;                 /* Exit status */
+    time_t     start,                  /* Start time */
+               end;                    /* End time */
+
+    fprintf(stderr, "Running command \"%s %s\".\n", job->printer->command,
+            job->filename);
+    time(&start);
+
+    if ((pid = fork()) == 0)
+    {
+     /*
+      * Child comes here...
+      */
+
+      execlp(job->printer->command, job->printer->command, job->filename,
+             (void *)NULL);
+      exit(errno);
+    }
+    else if (pid < 0)
+    {
+     /*
+      * Unable to fork process...
+      */
+
+      perror("Unable to start job processing command");
+    }
+    else
+    {
+     /*
+      * Wait for child to complete...
+      */
+
+#ifdef HAVE_WAITPID
+      while (waitpid(pid, &status, 0) < 0);
+#else
+      while (wait(&status) < 0);
+#endif /* HAVE_WAITPID */
+
+      if (status)
+      {
+        if (WIFEXITED(status))
+         fprintf(stderr, "Command \"%s\" exited with status %d.\n",
+                 job->printer->command, WEXITSTATUS(status));
+        else
+         fprintf(stderr, "Command \"%s\" terminated with signal %d.\n",
+                 job->printer->command, WTERMSIG(status));
+      }
+      else
+       fprintf(stderr, "Command \"%s\" completed successfully.\n",
+               job->printer->command);
+    }
+
+   /*
+    * Make sure processing takes at least 5 seconds...
+    */
+
+    time(&end);
+    if ((end - start) < 5)
+      sleep(5);
+  }
+  else
+  {
+   /*
+    * Sleep for a random amount of time to simulate job processing.
+    */
 
-  sleep(5);
+    sleep(5 + (rand() % 11));
+  }
 
   if (job->cancel)
-    job->state = IPP_JOB_CANCELED;
+    job->state = IPP_JSTATE_CANCELED;
   else
-    job->state = IPP_JOB_COMPLETED;
+    job->state = IPP_JSTATE_COMPLETED;
 
   job->completed           = time(NULL);
-  job->printer->state      = IPP_PRINTER_IDLE;
+  job->printer->state      = IPP_PSTATE_IDLE;
   job->printer->active_job = NULL;
 
   return (NULL);
@@ -3652,11 +4402,12 @@ register_printer(
     const char     *adminurl,          /* I - Web interface URL */
     int            color,              /* I - 1 = color, 0 = monochrome */
     int            duplex,             /* I - 1 = duplex, 0 = simplex */
-    const char     *regtype)           /* I - Service type */
+    const char     *subtype)           /* I - Service subtype */
 {
   DNSServiceErrorType  error;          /* Error from Bonjour */
   char                 make_model[256],/* Make and model together */
-                       product[256];   /* Product string */
+                       product[256],   /* Product string */
+                       regtype[256];   /* Bonjour service type */
 
 
  /*
@@ -3667,16 +4418,14 @@ register_printer(
   snprintf(product, sizeof(product), "(%s)", model);
 
   TXTRecordCreate(&(printer->ipp_txt), 1024, NULL);
-  TXTRecordSetValue(&(printer->ipp_txt), "txtvers", 1, "1");
-  TXTRecordSetValue(&(printer->ipp_txt), "qtotal", 1, "1");
-  TXTRecordSetValue(&(printer->ipp_txt), "rp", 3, "ipp");
+  TXTRecordSetValue(&(printer->ipp_txt), "rp", 9, "ipp/print");
   TXTRecordSetValue(&(printer->ipp_txt), "ty", (uint8_t)strlen(make_model),
                     make_model);
   TXTRecordSetValue(&(printer->ipp_txt), "adminurl", (uint8_t)strlen(adminurl),
                     adminurl);
-  TXTRecordSetValue(&(printer->ipp_txt), "note", (uint8_t)strlen(location),
-                    location);
-  TXTRecordSetValue(&(printer->ipp_txt), "priority", 1, "0");
+  if (*location)
+    TXTRecordSetValue(&(printer->ipp_txt), "note", (uint8_t)strlen(location),
+                     location);
   TXTRecordSetValue(&(printer->ipp_txt), "product", (uint8_t)strlen(product),
                     product);
   TXTRecordSetValue(&(printer->ipp_txt), "pdl", (uint8_t)strlen(formats),
@@ -3687,7 +4436,6 @@ register_printer(
                     make);
   TXTRecordSetValue(&(printer->ipp_txt), "usb_MDL", (uint8_t)strlen(model),
                     model);
-  TXTRecordSetValue(&(printer->ipp_txt), "air", 4, "none");
 
  /*
   * Create a shared service reference for Bonjour...
@@ -3728,6 +4476,11 @@ register_printer(
 
   printer->ipp_ref = printer->common_ref;
 
+  if (subtype && *subtype)
+    snprintf(regtype, sizeof(regtype), "_ipp._tcp,%s", subtype);
+  else
+    strlcpy(regtype, "_ipp._tcp", sizeof(regtype));
+
   if ((error = DNSServiceRegister(&(printer->ipp_ref),
                                   kDNSServiceFlagsShareConnection,
                                   0 /* interfaceIndex */, printer->dnssd_name,
@@ -3743,6 +4496,35 @@ register_printer(
     return (0);
   }
 
+#  ifdef HAVE_SSL
+ /*
+  * Then register the _ipps._tcp (IPP) service type with the real port number to
+  * advertise our IPP printer...
+  */
+
+  printer->ipps_ref = printer->common_ref;
+
+  if (subtype && *subtype)
+    snprintf(regtype, sizeof(regtype), "_ipps._tcp,%s", subtype);
+  else
+    strlcpy(regtype, "_ipps._tcp", sizeof(regtype));
+
+  if ((error = DNSServiceRegister(&(printer->ipps_ref),
+                                  kDNSServiceFlagsShareConnection,
+                                  0 /* interfaceIndex */, printer->dnssd_name,
+                                 regtype, NULL /* domain */,
+                                 NULL /* host */, htons(printer->port),
+                                 TXTRecordGetLength(&(printer->ipp_txt)),
+                                 TXTRecordGetBytesPtr(&(printer->ipp_txt)),
+                                 (DNSServiceRegisterReply)dnssd_callback,
+                                 printer)) != kDNSServiceErr_NoError)
+  {
+    fprintf(stderr, "Unable to register \"%s.%s\": %d\n",
+            printer->dnssd_name, regtype, error);
+    return (0);
+  }
+#  endif /* HAVE_SSL */
+
  /*
   * Similarly, register the _http._tcp,_printer (HTTP) service type with the
   * real port number to advertise our IPP printer...
@@ -3774,32 +4556,32 @@ register_printer(
  */
 
 int                                    /* O - 1 on success, 0 on failure */
-respond_http(_ipp_client_t *client,    /* I - Client */
-            http_status_t code,        /* I - HTTP status of response */
-            const char    *type,       /* I - MIME type of response */
-            size_t        length)      /* I - Length of response */
+respond_http(
+    _ipp_client_t *client,             /* I - Client */
+    http_status_t code,                        /* I - HTTP status of response */
+    const char    *content_encoding,   /* I - Content-Encoding of response */
+    const char    *type,               /* I - MIME media type of response */
+    size_t        length)              /* I - Length of response */
 {
   char message[1024];                  /* Text message */
 
 
-  fprintf(stderr, "%s %s\n", client->http.hostname, httpStatus(code));
+  fprintf(stderr, "%s %s\n", client->hostname, httpStatus(code));
 
-  if (code == HTTP_CONTINUE)
+  if (code == HTTP_STATUS_CONTINUE)
   {
    /*
     * 100-continue doesn't send any headers...
     */
 
-    return (httpPrintf(&(client->http), "HTTP/%d.%d 100 Continue\r\n\r\n",
-                      client->http.version / 100,
-                      client->http.version % 100) > 0);
+    return (httpWriteResponse(client->http, HTTP_STATUS_CONTINUE) == 0);
   }
 
  /*
   * Format an error message...
   */
 
-  if (!type && !length && code != HTTP_OK)
+  if (!type && !length && code != HTTP_STATUS_OK)
   {
     snprintf(message, sizeof(message), "%d - %s\n", code, httpStatus(code));
 
@@ -3810,60 +4592,30 @@ respond_http(_ipp_client_t *client,     /* I - Client */
     message[0] = '\0';
 
  /*
-  * Send the HTTP status header...
-  */
-
-  httpFlushWrite(&(client->http));
-
-  client->http.data_encoding = HTTP_ENCODE_FIELDS;
-
-  if (httpPrintf(&(client->http), "HTTP/%d.%d %d %s\r\n", client->http.version / 100,
-                 client->http.version % 100, code, httpStatus(code)) < 0)
-    return (0);
-
- /*
-  * Follow the header with the response fields...
+  * Send the HTTP response header...
   */
 
-  if (httpPrintf(&(client->http), "Date: %s\r\n", httpGetDateString(time(NULL))) < 0)
-    return (0);
-
-  if (client->http.keep_alive && client->http.version >= HTTP_1_0)
-  {
-    if (httpPrintf(&(client->http),
-                   "Connection: Keep-Alive\r\n"
-                   "Keep-Alive: timeout=10\r\n") < 0)
-      return (0);
-  }
+  httpClearFields(client->http);
 
-  if (code == HTTP_METHOD_NOT_ALLOWED || client->operation == HTTP_OPTIONS)
-  {
-    if (httpPrintf(&(client->http), "Allow: GET, HEAD, OPTIONS, POST\r\n") < 0)
-      return (0);
-  }
+  if (code == HTTP_STATUS_METHOD_NOT_ALLOWED ||
+      client->operation == HTTP_STATE_OPTIONS)
+    httpSetField(client->http, HTTP_FIELD_ALLOW, "GET, HEAD, OPTIONS, POST");
 
   if (type)
   {
     if (!strcmp(type, "text/html"))
-    {
-      if (httpPrintf(&(client->http),
-                     "Content-Type: text/html; charset=utf-8\r\n") < 0)
-       return (0);
-    }
-    else if (httpPrintf(&(client->http), "Content-Type: %s\r\n", type) < 0)
-      return (0);
-  }
+      httpSetField(client->http, HTTP_FIELD_CONTENT_TYPE,
+                   "text/html; charset=utf-8");
+    else
+      httpSetField(client->http, HTTP_FIELD_CONTENT_TYPE, type);
 
-  if (length == 0 && !message[0])
-  {
-    if (httpPrintf(&(client->http), "Transfer-Encoding: chunked\r\n\r\n") < 0)
-      return (0);
+    if (content_encoding)
+      httpSetField(client->http, HTTP_FIELD_CONTENT_ENCODING, content_encoding);
   }
-  else if (httpPrintf(&(client->http), "Content-Length: " CUPS_LLFMT "\r\n\r\n",
-                      CUPS_LLCAST length) < 0)
-    return (0);
 
-  if (httpFlushWrite(&(client->http)) < 0)
+  httpSetLength(client->http, length);
+
+  if (httpWriteResponse(client->http, code) < 0)
     return (0);
 
  /*
@@ -3876,7 +4628,10 @@ respond_http(_ipp_client_t *client,      /* I - Client */
     * Send a plain text message.
     */
 
-    if (httpPrintf(&(client->http), "%s", message) < 0)
+    if (httpPrintf(client->http, "%s", message) < 0)
+      return (0);
+
+    if (httpWrite2(client->http, "", 0) < 0)
       return (0);
   }
   else if (client->response)
@@ -3885,23 +4640,15 @@ respond_http(_ipp_client_t *client,     /* I - Client */
     * Send an IPP response...
     */
 
-    debug_attributes("Response", client->response);
+    debug_attributes("Response", client->response, 2);
 
-    client->http.data_encoding  = HTTP_ENCODE_LENGTH;
-    client->http.data_remaining = (off_t)ippLength(client->response);
-    client->response->state     = IPP_IDLE;
+    ippSetState(client->response, IPP_STATE_IDLE);
 
-    if (ippWrite(&(client->http), client->response) != IPP_DATA)
+    if (ippWrite(client->http, client->response) != IPP_STATE_DATA)
       return (0);
   }
-  else
-    client->http.data_encoding = HTTP_ENCODE_CHUNKED;
-
- /*
-  * Flush the data and return...
-  */
 
-  return (httpFlushWrite(&(client->http)) >= 0);
+  return (1);
 }
 
 
@@ -3915,36 +4662,57 @@ respond_ipp(_ipp_client_t *client,      /* I - Client */
            const char    *message,     /* I - printf-style status-message */
            ...)                        /* I - Additional args as needed */
 {
-  va_list      ap;                     /* Pointer to additional args */
-  char         formatted[1024];        /* Formatted errror message */
-
+  const char   *formatted = NULL;      /* Formatted message */
 
-  client->response->request.status.status_code = status;
 
-  if (!client->response->attrs)
-  {
-    ippAddString(client->response, IPP_TAG_OPERATION,
-                 IPP_TAG_CHARSET | IPP_TAG_COPY, "attributes-charset", NULL,
-                "utf-8");
-    ippAddString(client->response, IPP_TAG_OPERATION,
-                 IPP_TAG_LANGUAGE | IPP_TAG_COPY, "attributes-natural-language",
-                NULL, "en-us");
-  }
+  ippSetStatusCode(client->response, status);
 
   if (message)
   {
+    va_list            ap;             /* Pointer to additional args */
+    ipp_attribute_t    *attr;          /* New status-message attribute */
+
     va_start(ap, message);
-    vsnprintf(formatted, sizeof(formatted), message, ap);
+    if ((attr = ippFindAttribute(client->response, "status-message",
+                                IPP_TAG_TEXT)) != NULL)
+      ippSetStringfv(client->response, &attr, 0, message, ap);
+    else
+      attr = ippAddStringfv(client->response, IPP_TAG_OPERATION, IPP_TAG_TEXT,
+                           "status-message", NULL, message, ap);
     va_end(ap);
 
-    ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_TEXT,
-                "status-message", NULL, formatted);
+    formatted = ippGetString(attr, 0, NULL);
   }
+
+  if (formatted)
+    fprintf(stderr, "%s %s %s (%s)\n", client->hostname,
+           ippOpString(client->operation_id), ippErrorString(status),
+           formatted);
   else
-    formatted[0] = '\0';
+    fprintf(stderr, "%s %s %s\n", client->hostname,
+           ippOpString(client->operation_id), ippErrorString(status));
+}
+
+
+/*
+ * 'respond_unsupported()' - Respond with an unsupported attribute.
+ */
 
-  fprintf(stderr, "%s %s %s (%s)\n", client->http.hostname,
-          ippOpString(client->operation_id), ippErrorString(status), formatted);
+static void
+respond_unsupported(
+    _ipp_client_t   *client,           /* I - Client */
+    ipp_attribute_t *attr)             /* I - Atribute */
+{
+  ipp_attribute_t      *temp;          /* Copy of attribute */
+
+
+  respond_ipp(client, IPP_STATUS_ERROR_ATTRIBUTES_OR_VALUES,
+              "Unsupported %s %s%s value.", ippGetName(attr),
+              ippGetCount(attr) > 1 ? "1setOf " : "",
+             ippTagString(ippGetValueTag(attr)));
+
+  temp = ippCopyAttribute(client->response, attr, 0);
+  ippSetGroupTag(client->response, &temp, IPP_TAG_UNSUPPORTED_GROUP);
 }
 
 
@@ -3989,7 +4757,7 @@ run_printer(_ipp_printer_t *printer)      /* I - Printer */
     else
       timeout = -1;
 
-    if (poll(polldata, num_fds, timeout) < 0 && errno != EINTR)
+    if (poll(polldata, (nfds_t)num_fds, timeout) < 0 && errno != EINTR)
     {
       perror("poll() failed");
       break;
@@ -4042,7 +4810,8 @@ usage(int status)                 /* O - Exit status */
 {
   if (!status)
   {
-    puts(CUPS_SVERSION " - Copyright 2010 by Apple Inc. All rights reserved.");
+    puts(CUPS_SVERSION " - Copyright 2010-2013 by Apple Inc. All rights "
+         "reserved.");
     puts("");
   }
 
@@ -4051,17 +4820,22 @@ usage(int status)                       /* O - Exit status */
   puts("Options:");
   puts("-2                      Supports 2-sided printing (default=1-sided)");
   puts("-M manufacturer         Manufacturer name (default=Test)");
+  puts("-P                      PIN printing mode");
+  puts("-c command              Run command for every print job");
   printf("-d spool-directory      Spool directory "
          "(default=/tmp/ippserver.%d)\n", (int)getpid());
   puts("-f type/subtype[,...]   List of supported types "
        "(default=application/pdf,image/jpeg)");
   puts("-h                      Show program help");
   puts("-i iconfile.png         PNG icon file (default=printer.png)");
+  puts("-k                      Keep job spool files");
   puts("-l location             Location of printer (default=empty string)");
   puts("-m model                Model name (default=Printer)");
   puts("-n hostname             Hostname for printer");
   puts("-p port                 Port number (default=auto)");
-  puts("-r regtype              Bonjour service type (default=_ipp._tcp)");
+#ifdef HAVE_DNSSD
+  puts("-r subtype              Bonjour service subtype (default=_print)");
+#endif /* HAVE_DNSSD */
   puts("-s speed[,color-speed]  Speed in pages per minute (default=10,0)");
   puts("-v[vvv]                 Be (very) verbose");
 
@@ -4070,52 +4844,62 @@ usage(int status)                       /* O - Exit status */
 
 
 /*
- * 'valid_job_attributes()' - Determine whether the job attributes are valid.
+ * 'valid_doc_attributes()' - Determine whether the document attributes are
+ *                            valid.
  *
- * When one or more job attributes are invalid, this function adds a suitable
- * response and attributes to the unsupported group.
+ * When one or more document attributes are invalid, this function adds a
+ * suitable response and attributes to the unsupported group.
  */
 
 static int                             /* O - 1 if valid, 0 if not */
-valid_job_attributes(
+valid_doc_attributes(
     _ipp_client_t *client)             /* I - Client */
 {
-  int                  i;              /* Looping var */
-  ipp_attribute_t      *attr,          /* Current attribute */
-                       *supported;     /* document-format-supported */
-  const char           *format = NULL; /* document-format value */
   int                  valid = 1;      /* Valid attributes? */
+  ipp_op_t             op = ippGetOperation(client->request);
+                                       /* IPP operation */
+  const char           *op_name = ippOpString(op);
+                                       /* IPP operation name */
+  ipp_attribute_t      *attr,          /* Current attribute */
+                       *supported;     /* xxx-supported attribute */
+  const char           *compression = NULL,
+                                       /* compression value */
+                       *format = NULL; /* document-format value */
 
 
  /*
   * Check operation attributes...
   */
 
-#define respond_unsupported(client, attr) \
-  if (valid) \
-  { \
-    respond_ipp(client, IPP_ATTRIBUTES, "Unsupported %s %s%s value.", \
-               attr->name, attr->num_values > 1 ? "1setOf " : "", \
-               ippTagString(attr->value_tag)); \
-    valid = 0; \
-  } \
-  copy_attribute(client->response, attr, IPP_TAG_UNSUPPORTED_GROUP, 0)
-
   if ((attr = ippFindAttribute(client->request, "compression",
                                IPP_TAG_ZERO)) != NULL)
   {
    /*
-    * If compression is specified, only accept "none"...
+    * If compression is specified, only accept a supported value in a Print-Job
+    * or Send-Document request...
     */
 
-    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_KEYWORD ||
-        strcmp(attr->values[0].string.text, "none"))
+    compression = ippGetString(attr, 0, NULL);
+    supported   = ippFindAttribute(client->printer->attrs,
+                                   "compression-supported", IPP_TAG_KEYWORD);
+
+    if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_KEYWORD ||
+        ippGetGroupTag(attr) != IPP_TAG_OPERATION ||
+        (op != IPP_OP_PRINT_JOB && op != IPP_OP_SEND_DOCUMENT &&
+         op != IPP_OP_VALIDATE_JOB) ||
+        !ippContainsString(supported, compression))
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
     else
-      fprintf(stderr, "%s Print-Job compression=\"%s\"\n",
-              client->http.hostname, attr->values[0].string.text);
+    {
+      fprintf(stderr, "%s %s compression=\"%s\"\n",
+              client->hostname, op_name, compression);
+
+      if (strcmp(compression, "none"))
+        httpSetField(client->http, HTTP_FIELD_CONTENT_ENCODING, compression);
+    }
   }
 
  /*
@@ -4125,24 +4909,35 @@ valid_job_attributes(
   if ((attr = ippFindAttribute(client->request, "document-format",
                                IPP_TAG_ZERO)) != NULL)
   {
-    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_MIMETYPE)
+    if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_MIMETYPE ||
+        ippGetGroupTag(attr) != IPP_TAG_OPERATION)
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
     else
     {
-      format = attr->values[0].string.text;
+      format = ippGetString(attr, 0, NULL);
 
       fprintf(stderr, "%s %s document-format=\"%s\"\n",
-             client->http.hostname,
-             ippOpString(client->request->request.op.operation_id), format);
+             client->hostname, op_name, format);
     }
   }
   else
-    format = "application/octet-stream";
+  {
+    format = ippGetString(ippFindAttribute(client->printer->attrs,
+                                           "document-format-default",
+                                           IPP_TAG_MIMETYPE), 0, NULL);
+    if (!format)
+      format = "application/octet-stream"; /* Should never happen */
+
+    attr = ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE,
+                       "document-format", NULL, format);
+  }
 
   if (!strcmp(format, "application/octet-stream") &&
-      client->request->request.op.operation_id != IPP_VALIDATE_JOB)
+      (ippGetOperation(client->request) == IPP_OP_PRINT_JOB ||
+       ippGetOperation(client->request) == IPP_OP_SEND_DOCUMENT))
   {
    /*
     * Auto-type the file using the first 4 bytes of the file...
@@ -4151,7 +4946,7 @@ valid_job_attributes(
     unsigned char      header[4];      /* First 4 bytes of file */
 
     memset(header, 0, sizeof(header));
-    _httpPeek(&(client->http), (char *)header, sizeof(header));
+    httpPeek(client->http, (char *)header, sizeof(header));
 
     if (!memcmp(header, "%PDF", 4))
       format = "application/pdf";
@@ -4165,33 +4960,52 @@ valid_job_attributes(
 
     if (format)
       fprintf(stderr, "%s %s Auto-typed document-format=\"%s\"\n",
-             client->http.hostname,
-             ippOpString(client->request->request.op.operation_id), format);
+             client->hostname, op_name, format);
 
     if (!attr)
-      ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE,
-                   "document-format", NULL, format);
+      attr = ippAddString(client->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE,
+                          "document-format", NULL, format);
     else
-    {
-      _cupsStrFree(attr->values[0].string.text);
-      attr->values[0].string.text = _cupsStrAlloc(format);
-    }
+      ippSetString(client->request, &attr, 0, format);
   }
 
-  if ((supported = ippFindAttribute(client->printer->attrs,
+  if (op != IPP_OP_CREATE_JOB &&
+      (supported = ippFindAttribute(client->printer->attrs,
                                     "document-format-supported",
-                                   IPP_TAG_MIMETYPE)) != NULL)
+                                   IPP_TAG_MIMETYPE)) != NULL &&
+      !ippContainsString(supported, format))
   {
-    for (i = 0; i < supported->num_values; i ++)
-      if (!_cups_strcasecmp(format, supported->values[i].string.text))
-       break;
-
-    if (i >= supported->num_values)
-    {
-      respond_unsupported(client, attr);
-    }
+    respond_unsupported(client, attr);
+    valid = 0;
   }
 
+  return (valid);
+}
+
+
+/*
+ * 'valid_job_attributes()' - Determine whether the job attributes are valid.
+ *
+ * When one or more job attributes are invalid, this function adds a suitable
+ * response and attributes to the unsupported group.
+ */
+
+static int                             /* O - 1 if valid, 0 if not */
+valid_job_attributes(
+    _ipp_client_t *client)             /* I - Client */
+{
+  int                  i,              /* Looping var */
+                       valid = 1;      /* Valid attributes? */
+  ipp_attribute_t      *attr,          /* Current attribute */
+                       *supported;     /* xxx-supported attribute */
+
+
+ /*
+  * Check operation attributes...
+  */
+
+  valid = valid_doc_attributes(client);
+
  /*
   * Check the various job template attributes...
   */
@@ -4199,90 +5013,98 @@ valid_job_attributes(
   if ((attr = ippFindAttribute(client->request, "copies",
                                IPP_TAG_ZERO)) != NULL)
   {
-    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_INTEGER ||
-        attr->values[0].integer < 1 || attr->values[0].integer > 999)
+    if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_INTEGER ||
+        ippGetInteger(attr, 0) < 1 || ippGetInteger(attr, 0) > 999)
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
   }
 
   if ((attr = ippFindAttribute(client->request, "ipp-attribute-fidelity",
                                IPP_TAG_ZERO)) != NULL)
   {
-    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_BOOLEAN)
+    if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_BOOLEAN)
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
   }
 
   if ((attr = ippFindAttribute(client->request, "job-hold-until",
                                IPP_TAG_ZERO)) != NULL)
   {
-    if (attr->num_values != 1 ||
-        (attr->value_tag != IPP_TAG_NAME &&
-        attr->value_tag != IPP_TAG_NAMELANG &&
-        attr->value_tag != IPP_TAG_KEYWORD) ||
-       strcmp(attr->values[0].string.text, "no-hold"))
+    if (ippGetCount(attr) != 1 ||
+        (ippGetValueTag(attr) != IPP_TAG_NAME &&
+        ippGetValueTag(attr) != IPP_TAG_NAMELANG &&
+        ippGetValueTag(attr) != IPP_TAG_KEYWORD) ||
+       strcmp(ippGetString(attr, 0, NULL), "no-hold"))
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
   }
 
   if ((attr = ippFindAttribute(client->request, "job-name",
                                IPP_TAG_ZERO)) != NULL)
   {
-    if (attr->num_values != 1 ||
-        (attr->value_tag != IPP_TAG_NAME &&
-        attr->value_tag != IPP_TAG_NAMELANG))
+    if (ippGetCount(attr) != 1 ||
+        (ippGetValueTag(attr) != IPP_TAG_NAME &&
+        ippGetValueTag(attr) != IPP_TAG_NAMELANG))
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
   }
 
   if ((attr = ippFindAttribute(client->request, "job-priority",
                                IPP_TAG_ZERO)) != NULL)
   {
-    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_INTEGER ||
-        attr->values[0].integer < 1 || attr->values[0].integer > 100)
+    if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_INTEGER ||
+        ippGetInteger(attr, 0) < 1 || ippGetInteger(attr, 0) > 100)
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
   }
 
   if ((attr = ippFindAttribute(client->request, "job-sheets",
                                IPP_TAG_ZERO)) != NULL)
   {
-    if (attr->num_values != 1 ||
-        (attr->value_tag != IPP_TAG_NAME &&
-        attr->value_tag != IPP_TAG_NAMELANG &&
-        attr->value_tag != IPP_TAG_KEYWORD) ||
-       strcmp(attr->values[0].string.text, "none"))
+    if (ippGetCount(attr) != 1 ||
+        (ippGetValueTag(attr) != IPP_TAG_NAME &&
+        ippGetValueTag(attr) != IPP_TAG_NAMELANG &&
+        ippGetValueTag(attr) != IPP_TAG_KEYWORD) ||
+       strcmp(ippGetString(attr, 0, NULL), "none"))
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
   }
 
   if ((attr = ippFindAttribute(client->request, "media",
                                IPP_TAG_ZERO)) != NULL)
   {
-    if (attr->num_values != 1 ||
-        (attr->value_tag != IPP_TAG_NAME &&
-        attr->value_tag != IPP_TAG_NAMELANG &&
-        attr->value_tag != IPP_TAG_KEYWORD))
+    if (ippGetCount(attr) != 1 ||
+        (ippGetValueTag(attr) != IPP_TAG_NAME &&
+        ippGetValueTag(attr) != IPP_TAG_NAMELANG &&
+        ippGetValueTag(attr) != IPP_TAG_KEYWORD))
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
     else
     {
       for (i = 0;
            i < (int)(sizeof(media_supported) / sizeof(media_supported[0]));
           i ++)
-        if (!strcmp(attr->values[0].string.text, media_supported[i]))
+        if (!strcmp(ippGetString(attr, 0, NULL), media_supported[i]))
          break;
 
       if (i >= (int)(sizeof(media_supported) / sizeof(media_supported[0])))
       {
        respond_unsupported(client, attr);
+       valid = 0;
       }
     }
   }
@@ -4290,9 +5112,11 @@ valid_job_attributes(
   if ((attr = ippFindAttribute(client->request, "media-col",
                                IPP_TAG_ZERO)) != NULL)
   {
-    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_BEGIN_COLLECTION)
+    if (ippGetCount(attr) != 1 ||
+        ippGetValueTag(attr) != IPP_TAG_BEGIN_COLLECTION)
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
     /* TODO: check for valid media-col */
   }
@@ -4300,24 +5124,26 @@ valid_job_attributes(
   if ((attr = ippFindAttribute(client->request, "multiple-document-handling",
                                IPP_TAG_ZERO)) != NULL)
   {
-    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_KEYWORD ||
-        (strcmp(attr->values[0].string.text,
+    if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_KEYWORD ||
+        (strcmp(ippGetString(attr, 0, NULL),
                "separate-documents-uncollated-copies") &&
-        strcmp(attr->values[0].string.text,
+        strcmp(ippGetString(attr, 0, NULL),
                "separate-documents-collated-copies")))
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
   }
 
   if ((attr = ippFindAttribute(client->request, "orientation-requested",
                                IPP_TAG_ZERO)) != NULL)
   {
-    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_ENUM ||
-        attr->values[0].integer < IPP_PORTRAIT ||
-        attr->values[0].integer > IPP_REVERSE_PORTRAIT)
+    if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_ENUM ||
+        ippGetInteger(attr, 0) < IPP_ORIENT_PORTRAIT ||
+        ippGetInteger(attr, 0) > IPP_ORIENT_REVERSE_PORTRAIT)
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
   }
 
@@ -4325,16 +5151,18 @@ valid_job_attributes(
                                IPP_TAG_ZERO)) != NULL)
   {
     respond_unsupported(client, attr);
+      valid = 0;
   }
 
   if ((attr = ippFindAttribute(client->request, "print-quality",
                                IPP_TAG_ZERO)) != NULL)
   {
-    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_ENUM ||
-        attr->values[0].integer < IPP_QUALITY_DRAFT ||
-        attr->values[0].integer > IPP_QUALITY_HIGH)
+    if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_ENUM ||
+        ippGetInteger(attr, 0) < IPP_QUALITY_DRAFT ||
+        ippGetInteger(attr, 0) > IPP_QUALITY_HIGH)
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
   }
 
@@ -4342,32 +5170,35 @@ valid_job_attributes(
                                IPP_TAG_ZERO)) != NULL)
   {
     respond_unsupported(client, attr);
+    valid = 0;
   }
 
   if ((attr = ippFindAttribute(client->request, "sides",
                                IPP_TAG_ZERO)) != NULL)
   {
-    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_KEYWORD)
+    const char *sides = NULL;          /* "sides" value... */
+
+    if (ippGetCount(attr) != 1 || ippGetValueTag(attr) != IPP_TAG_KEYWORD)
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
 
-    if ((supported = ippFindAttribute(client->printer->attrs, "sides",
+    sides = ippGetString(attr, 0, NULL);
+
+    if ((supported = ippFindAttribute(client->printer->attrs, "sides-supported",
                                       IPP_TAG_KEYWORD)) != NULL)
     {
-      for (i = 0; i < supported->num_values; i ++)
-        if (!strcmp(attr->values[0].string.text,
-                   supported->values[i].string.text))
-         break;
-
-      if (i >= supported->num_values)
+      if (!ippContainsString(supported, sides))
       {
        respond_unsupported(client, attr);
+       valid = 0;
       }
     }
-    else
+    else if (strcmp(sides, "one-sided"))
     {
       respond_unsupported(client, attr);
+      valid = 0;
     }
   }
 
@@ -4376,5 +5207,5 @@ valid_job_attributes(
 
 
 /*
- * End of "$Id$".
+ * End of "$Id: ippserver.c 11986 2014-07-02 15:52:01Z msweet $".
  */