]> git.ipfire.org Git - thirdparty/cups.git/commitdiff
Merge changes from CUPS 1.5svn-r9374.
authormsweet <msweet@a1ca3aef-8c08-0410-bb20-df032aa958be>
Wed, 17 Nov 2010 18:58:56 +0000 (18:58 +0000)
committermsweet <msweet@a1ca3aef-8c08-0410-bb20-df032aa958be>
Wed, 17 Nov 2010 18:58:56 +0000 (18:58 +0000)
git-svn-id: svn+ssh://src.apple.com/svn/cups/easysw/current@2859 a1ca3aef-8c08-0410-bb20-df032aa958be

20 files changed:
CHANGES-1.4.txt
backend/ipp.c
cups/adminutil.c
cups/http-addr.c
cups/http-private.h
cups/http-support.c
cups/http.c
cups/ipp-support.c
cups/ipp.c
cups/ipp.h
cups/thread-private.h
cups/thread.c
man/ipptool.man
scheduler/dirsvc.c
test/Dependencies
test/Makefile
test/ippserver.c [new file with mode: 0644]
test/ipptool.c
test/printer.opacity [new file with mode: 0644]
test/printer.png [new file with mode: 0644]

index 1cd4d6cf549adb3bb0eb7e9a136dd76291339ca3..674530c5b9303cd7f2b4c1a00e0becf270096a83 100644 (file)
@@ -3,12 +3,16 @@ CHANGES-1.4.txt
 
 CHANGES IN CUPS V1.4.6
 
+       - The cupsAdminSetServerSettings() function disabled sharing when
+         debug logging was enabled (STR #3712)
 
 
 CHANGES IN CUPS V1.4.5
 
        - Documentation fixes (STR #3542, STR #3650)
        - Localization fixes (STR #3635, STR #3636, STR #3647, STR #3666)
+       - Security: Fixed a memory corruption bug reported in CVE-2010-2941
+         (STR #3648)
        - The CUPS API incorrectly mapped the HTTP_UNAUTHORIZED status to the
          IPP_NOT_AUTHORIZED status code, when IPP_NOT_AUTHENTICATED would be
          the correct mapping (STR #3684)
index 5701ce5d436bf0a72b8135a578d88740ff4e08f4..810e48b0dbff1aa417e6204e9d5a9c01f5184aaa 100644 (file)
@@ -1719,7 +1719,8 @@ monitor_printer(
   * Make a copy of the printer connection...
   */
 
-  http = _httpCreate(monitor->hostname, monitor->port, monitor->encryption);
+  http = _httpCreate(monitor->hostname, monitor->port, monitor->encryption,
+                     AF_UNSPEC);
   cupsSetPasswordCB(password_cb);
 
  /*
index 68efcfd44fb5b0cb452e29cdf0a6efeddd0a312f..b8d28126bc1204ce18f0dec23a15ff9610aa3d02 100644 (file)
  *
  * Contents:
  *
- *   cupsAdminCreateWindowsPPD()   - Create the Windows PPD file for a printer.
- *   cupsAdminExportSamba()        - Export a printer to Samba.
- *   cupsAdminGetServerSettings()  - Get settings from the server.
- *   _cupsAdminGetServerSettings() - Get settings from the server (private).
- *   cupsAdminSetServerSettings()  - Set settings on the server.
- *   _cupsAdminSetServerSettings() - Set settings on the server (private).
- *   do_samba_command()            - Do a SAMBA command.
- *   get_cupsd_conf()              - Get the current cupsd.conf file.
- *   invalidate_cupsd_cache()      - Invalidate the cached cupsd.conf settings.
- *   write_option()                - Write a CUPS option to a PPD file.
+ *   cupsAdminCreateWindowsPPD()  - Create the Windows PPD file for a printer.
+ *   cupsAdminExportSamba()       - Export a printer to Samba.
+ *   cupsAdminGetServerSettings() - Get settings from the server.
+ *   cupsAdminSetServerSettings() - Set settings on the server.
+ *   do_samba_command()           - Do a SAMBA command.
+ *   get_cupsd_conf()             - Get the current cupsd.conf file.
+ *   invalidate_cupsd_cache()     - Invalidate the cached cupsd.conf settings.
+ *   write_option()               - Write a CUPS option to a PPD file.
  */
 
 /*
  * Local functions...
  */
 
-extern int             _cupsAdminGetServerSettings(http_t *http,
-                                                   int *num_settings,
-                                                   cups_option_t **settings);
-extern int             _cupsAdminSetServerSettings(http_t *http,
-                                                   int num_settings,
-                                                   cups_option_t *settings);
 static int             do_samba_command(const char *command,
                                         const char *address,
                                         const char *subcommand,
@@ -910,7 +902,7 @@ cupsAdminGetServerSettings(
     if (!cg->http)
     {
       if ((cg->http = _httpCreate(cupsServer(), ippPort(),
-                                  cupsEncryption())) == NULL)
+                                  cupsEncryption(), AF_UNSPEC)) == NULL)
       {
        if (errno)
          _cupsSetError(IPP_SERVICE_UNAVAILABLE, NULL, 0);
@@ -1021,7 +1013,7 @@ cupsAdminGetServerSettings(
             && *value != '/'
 #endif /* AF_LOCAL */
 #ifdef AF_INET6
-            && strcmp(value, "::1")
+            && strcmp(value, "[::1]")
 #endif /* AF_INET6 */
            )
          remote_access = 1;
@@ -1281,12 +1273,18 @@ cupsAdminSetServerSettings(
   else
     old_debug_logging = 0;
 
+  DEBUG_printf(("1cupsAdminSetServerSettings: old debug_logging=%d",
+                old_debug_logging));
+
   if ((val = cupsGetOption(CUPS_SERVER_REMOTE_ADMIN, cupsd_num_settings,
                            cupsd_settings)) != NULL)
     old_remote_admin = atoi(val);
   else
     old_remote_admin = 0;
 
+  DEBUG_printf(("1cupsAdminSetServerSettings: old remote_admin=%d",
+                old_remote_admin));
+
   if ((val = cupsGetOption(CUPS_SERVER_REMOTE_ANY, cupsd_num_settings,
                            cupsd_settings)) != NULL)
     remote_any = atoi(val);
@@ -1302,18 +1300,27 @@ cupsAdminSetServerSettings(
   else
     old_remote_printers = 1;
 
+  DEBUG_printf(("1cupsAdminSetServerSettings: old remote_printers=%d",
+                old_remote_printers));
+
   if ((val = cupsGetOption(CUPS_SERVER_SHARE_PRINTERS, cupsd_num_settings,
                            cupsd_settings)) != NULL)
     old_share_printers = atoi(val);
   else
     old_share_printers = 0;
 
+  DEBUG_printf(("1cupsAdminSetServerSettings: old share_printers=%d",
+                old_share_printers));
+
   if ((val = cupsGetOption(CUPS_SERVER_USER_CANCEL_ANY, cupsd_num_settings,
                            cupsd_settings)) != NULL)
     old_user_cancel_any = atoi(val);
   else
     old_user_cancel_any = 0;
 
+  DEBUG_printf(("1cupsAdminSetServerSettings: old user_cancel_any=%d",
+                old_user_cancel_any));
+
   cupsFreeOptions(cupsd_num_settings, cupsd_settings);
 
  /*
@@ -1337,16 +1344,22 @@ cupsAdminSetServerSettings(
   else
     debug_logging = -1;
 
+  DEBUG_printf(("1cupsAdminSetServerSettings: debug_logging=%d",
+                debug_logging));
+
   if ((val = cupsGetOption(CUPS_SERVER_REMOTE_ANY, num_settings,
                            settings)) != NULL)
     remote_any = atoi(val);
 
+  DEBUG_printf(("1cupsAdminSetServerSettings: remote_any=%d",
+                remote_any));
+
   if ((val = cupsGetOption(CUPS_SERVER_REMOTE_ADMIN, num_settings,
                            settings)) != NULL)
   {
     remote_admin = atoi(val);
 
-    if (remote_admin == old_remote_admin && remote_any < 0)
+    if (remote_admin == old_remote_admin)
     {
      /*
       * No change to this setting...
@@ -1358,6 +1371,9 @@ cupsAdminSetServerSettings(
   else
     remote_admin = -1;
 
+  DEBUG_printf(("1cupsAdminSetServerSettings: remote_admin=%d",
+                remote_admin));
+
   if ((val = cupsGetOption(CUPS_SERVER_REMOTE_PRINTERS, num_settings,
                            settings)) != NULL)
   {
@@ -1375,12 +1391,15 @@ cupsAdminSetServerSettings(
   else
     remote_printers = -1;
 
+  DEBUG_printf(("1cupsAdminSetServerSettings: remote_printers=%d",
+                remote_printers));
+
   if ((val = cupsGetOption(CUPS_SERVER_SHARE_PRINTERS, num_settings,
                            settings)) != NULL)
   {
     share_printers = atoi(val);
 
-    if (share_printers == old_share_printers && remote_any < 0)
+    if (share_printers == old_share_printers)
     {
      /*
       * No change to this setting...
@@ -1392,6 +1411,9 @@ cupsAdminSetServerSettings(
   else
     share_printers = -1;
 
+  DEBUG_printf(("1cupsAdminSetServerSettings: share_printers=%d",
+                share_printers));
+
   if ((val = cupsGetOption(CUPS_SERVER_USER_CANCEL_ANY, num_settings,
                            settings)) != NULL)
   {
@@ -1409,6 +1431,9 @@ cupsAdminSetServerSettings(
   else
     user_cancel_any = -1;
 
+  DEBUG_printf(("1cupsAdminSetServerSettings: user_cancel_any=%d",
+                user_cancel_any));
+
  /*
   * Create a temporary file for the new cupsd.conf file...
   */
@@ -1460,7 +1485,7 @@ cupsAdminSetServerSettings(
   while (cupsFileGetConf(cupsd, line, sizeof(line), &value, &linenum))
   {
     if ((!strcasecmp(line, "Port") || !strcasecmp(line, "Listen")) &&
-        (remote_admin >= 0 || remote_any >= 0 || share_printers >= 0))
+        (remote_admin >= 0 || remote_any > 0 || share_printers >= 0))
     {
       if (!wrote_port_listen)
       {
@@ -1675,7 +1700,7 @@ cupsAdminSetServerSettings(
                         remote_any > 0 ? "all" : "@LOCAL");
       }
       else if (in_root_location &&
-               (remote_admin >= 0 || remote_any >= 0 || share_printers >= 0))
+               (remote_admin >= 0 || remote_any > 0 || share_printers >= 0))
       {
        wrote_root_location = 1;
 
@@ -1777,7 +1802,7 @@ cupsAdminSetServerSettings(
       in_cancel_job = 0;
     }
     else if ((((in_admin_location || in_conf_location || in_root_location) &&
-               (remote_admin >= 0 || remote_any >= 0)) ||
+               (remote_admin >= 0 || remote_any > 0)) ||
               (in_root_location && share_printers >= 0)) &&
              (!strcasecmp(line, "Allow") || !strcasecmp(line, "Deny") ||
              !strcasecmp(line, "Order")))
@@ -1823,7 +1848,7 @@ cupsAdminSetServerSettings(
        /*
         * Record the non-policy, non-location directives that we find
        * in the server settings, since we cache this info and record it
-       * in _cupsAdminGetServerSettings()...
+       * in cupsAdminGetServerSettings()...
        */
 
        cupsd_num_settings = cupsAddOption(line, value, cupsd_num_settings,
@@ -1882,7 +1907,7 @@ cupsAdminSetServerSettings(
   }
 
   if (!wrote_port_listen &&
-      (remote_admin >= 0 || remote_any >= 0 || share_printers >= 0))
+      (remote_admin >= 0 || remote_any > 0 || share_printers >= 0))
   {
     if (remote_admin > 0 || remote_any > 0 || share_printers > 0)
     {
@@ -1903,7 +1928,7 @@ cupsAdminSetServerSettings(
   }
 
   if (!wrote_root_location &&
-      (remote_admin >= 0 || remote_any >= 0 || share_printers >= 0))
+      (remote_admin >= 0 || remote_any > 0 || share_printers >= 0))
   {
     if (remote_admin > 0 && share_printers > 0)
       cupsFilePuts(temp,
index 7f3897c65f1c1f2efbf693f3edd1a41da17ce876..dfd89f957d2cf1f28a162019815c9d1cf7b8de17 100644 (file)
 #ifdef HAVE_RESOLV_H
 #  include <resolv.h>
 #endif /* HAVE_RESOLV_H */
+#ifdef HAVE_COREFOUNDATION
+#  include <CoreFoundation/CoreFoundation.h>
+#endif /* HAVE_COREFOUNDATION */
+#ifdef HAVE_SYSTEMCONFIGURATION
+#  include <SystemConfiguration/SystemConfiguration.h>
+#endif /* HAVE_SYSTEMCONFIGURATION */
 
 
 /*
@@ -579,9 +585,6 @@ httpGetHostname(http_t *http,               /* I - HTTP connection or NULL */
                 char   *s,             /* I - String buffer for name */
                 int    slen)           /* I - Size of buffer */
 {
-  struct hostent       *host;          /* Host entry to get FQDN */
-
-
   if (!s || slen <= 1)
     return (NULL);
 
@@ -603,12 +606,50 @@ httpGetHostname(http_t *http,             /* I - HTTP connection or NULL */
 
     if (!strchr(s, '.'))
     {
+#ifdef HAVE_SYSTEMCONFIGURATION
+     /*
+      * The hostname is not a FQDN, so use the local hostname from the
+      * SystemConfiguration framework...
+      */
+
+      SCDynamicStoreRef        sc = SCDynamicStoreCreate(kCFAllocatorDefault,
+                                                  CFSTR("libcups"), NULL, NULL);
+                                       /* System configuration data */
+      CFStringRef      local = sc ? SCDynamicStoreCopyLocalHostName(sc) : NULL;
+                                       /* Local host name */
+      char             localStr[1024]; /* Local host name C string */
+
+      if (local && CFStringGetCString(local, localStr, sizeof(localStr),
+                                      kCFStringEncodingUTF8))
+      {
+       /*
+        * Append ".local." to the hostname we get...
+       */
+
+        snprintf(s, slen, "%s.local.", localStr);
+      }
+
+      if (local)
+        CFRelease(local);
+      if (sc)
+        CFRelease(sc);
+
+#else
      /*
       * The hostname is not a FQDN, so look it up...
       */
 
+      struct hostent   *host;          /* Host entry to get FQDN */
+
       if ((host = gethostbyname(s)) != NULL && host->h_name)
+      {
+       /*
+        * Use the resolved hostname...
+       */
+
        strlcpy(s, host->h_name, slen);
+      }
+#endif /* HAVE_SYSTEMCONFIGURATION */
     }
   }
 
index 483592f51331c906dce51db29174d80735cc1155..51b612987004f830772244e531479d031c095553 100644 (file)
@@ -311,7 +311,10 @@ extern int         _httpAddrPort(http_addr_t *addr);
 extern http_tls_credentials_t
                        _httpConvertCredentials(cups_array_t *credentials);
 extern http_t          *_httpCreate(const char *host, int port,
-                                    http_encryption_t encryption);
+                                    http_encryption_t encryption,
+                                    int family);
+extern char            *_httpDecodeURI(char *dst, const char *src,
+                                       size_t dstsize);
 extern void            _httpDisconnect(http_t *http);
 extern char            *_httpEncodeURI(char *dst, const char *src,
                                        size_t dstsize);
index 452a946f3adf51683f536b8ee79ce95ebeb4009b..443ce6c2150be5ed49816fa1b0e05d49b7f58536 100644 (file)
@@ -36,6 +36,7 @@
  *   httpStatus()         - Return a short string describing a HTTP status code.
  *   _cups_hstrerror()    - hstrerror() emulation function for Solaris and
  *                          others...
+ *   _httpDecodeURI()     - Percent-decode a HTTP request URI.
  *   _httpEncodeURI()     - Percent-encode a HTTP request URI.
  *   _httpResolveURI()    - Resolve a DNS-SD URI.
  *   http_copy_decode()   - Copy and decode a URI.
@@ -1288,6 +1289,22 @@ _cups_hstrerror(int error)               /* I - Error number */
 #endif /* !HAVE_HSTRERROR */
 
 
+/*
+ * '_httpDecodeURI()' - Percent-decode a HTTP request URI.
+ */
+
+char *                                 /* O - Decoded URI or NULL on error */
+_httpDecodeURI(char       *dst,                /* I - Destination buffer */
+               const char *src,                /* I - Source URI */
+              size_t     dstsize)      /* I - Size of destination buffer */
+{
+  if (http_copy_decode(dst, src, (int)dstsize, NULL, 1))
+    return (dst);
+  else
+    return (NULL);
+}
+
+
 /*
  * '_httpEncodeURI()' - Percent-encode a HTTP request URI.
  */
index 06ceb021239afd65411bb8f0486bc6777fc4ecd5..dc117224caae958369e1cdd10c881f7ea6e8a268 100644 (file)
@@ -475,7 +475,7 @@ httpConnectEncrypt(
   * Create the HTTP structure...
   */
 
-  if ((http = _httpCreate(host, port, encryption)) == NULL)
+  if ((http = _httpCreate(host, port, encryption, AF_UNSPEC)) == NULL)
     return (NULL);
 
  /*
@@ -628,7 +628,8 @@ http_t *                            /* O - HTTP connection */
 _httpCreate(
     const char        *host,           /* I - Hostname */
     int               port,            /* I - Port number */
-    http_encryption_t encryption)      /* I - Encryption to use */
+    http_encryption_t encryption,      /* I - Encryption to use */
+    int               family)          /* I - Address family or AF_UNSPEC */
 {
   http_t               *http;          /* New HTTP connection */
   http_addrlist_t      *addrlist;      /* Host address data */
@@ -649,7 +650,7 @@ _httpCreate(
 
   sprintf(service, "%d", port);
 
-  if ((addrlist = httpAddrGetList(host, AF_UNSPEC, service)) == NULL)
+  if ((addrlist = httpAddrGetList(host, family, service)) == NULL)
     return (NULL);
 
  /*
index c12917f26552d069915954a26f3df37a3dea3847..42b4d47a4767d284efaeef3f9143269fe01e3128 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * "$Id: ipp-support.c 7847 2008-08-19 04:22:14Z mike $"
+ * "$Id: ipp-support.c 9371 2010-11-17 06:21:32Z mike $"
  *
  *   Internet Printing Protocol support functions for CUPS.
  *
@@ -343,7 +343,7 @@ _ippAttrString(ipp_attribute_t *attr,       /* I - Attribute */
         bufptr ++;
     }
 
-    switch (attr->value_tag)
+    switch (attr->value_tag & ~IPP_TAG_COPY)
     {
       case IPP_TAG_ENUM :
           if (!strcmp(attr->name, "printer-state") &&
@@ -763,5 +763,51 @@ ippTagValue(const char *name)              /* I - Tag name */
 
 
 /*
- * End of "$Id: ipp-support.c 7847 2008-08-19 04:22:14Z mike $".
+ * 'ipp_col_string()' - Convert a collection to a string.
+ */
+
+static size_t                          /* O - Number of bytes */
+ipp_col_string(ipp_t  *col,            /* I - Collection attribute */
+               char   *buffer,         /* I - Buffer or NULL */
+               size_t bufsize)         /* I - Size of buffer */
+{
+  char                 *bufptr,        /* Position in buffer */
+                       *bufend,        /* End of buffer */
+                       temp[256];      /* Temporary string */
+  ipp_attribute_t      *attr;          /* Current member attribute */
+
+
+  bufptr = buffer;
+  bufend = buffer + bufsize - 1;
+
+  if (buffer && bufptr < bufend)
+    *bufptr = '{';
+  bufptr ++;
+
+  for (attr = col->attrs; attr; attr = attr->next)
+  {
+    if (!attr->name)
+      continue;
+
+    if (buffer && bufptr < bufend)
+      bufptr += snprintf(bufptr, bufend - bufptr + 1, "%s=", attr->name);
+    else
+      bufptr += strlen(attr->name) + 1;
+
+    if (buffer && bufptr < bufend)
+      bufptr += _ippAttrString(attr, bufptr, bufend - bufptr + 1);
+    else
+      bufptr += _ippAttrString(attr, temp, sizeof(temp));
+  }
+
+  if (buffer && bufptr < bufend)
+    *bufptr = '}';
+  bufptr ++;
+
+  return (bufptr - buffer);
+}
+
+
+/*
+ * End of "$Id: ipp-support.c 9371 2010-11-17 06:21:32Z mike $".
  */
index 3e90b8b4d23d221c47558ba502c424b895858e8e..8f4360599a73d67e9ae81adb3931b74011e245b2 100644 (file)
@@ -1271,7 +1271,9 @@ ippReadIO(void       *src,                /* I - Data source */
 
              attr->value_tag = tag;
            }
-           else if ((value_tag >= IPP_TAG_TEXTLANG &&
+           else if (value_tag == IPP_TAG_TEXTLANG ||
+                    value_tag == IPP_TAG_NAMELANG ||
+                    (value_tag >= IPP_TAG_TEXT &&
                      value_tag <= IPP_TAG_MIMETYPE))
             {
             /*
@@ -1279,8 +1281,9 @@ ippReadIO(void       *src,                /* I - Data source */
              * forms; accept sets of differing values...
              */
 
-             if ((tag < IPP_TAG_TEXTLANG || tag > IPP_TAG_MIMETYPE) &&
-                 tag != IPP_TAG_NOVALUE)
+             if (tag != IPP_TAG_TEXTLANG && tag != IPP_TAG_NAMELANG &&
+                 (tag < IPP_TAG_TEXT || tag > IPP_TAG_MIMETYPE) &&
+                 tag != IPP_TAG_NOVALUE)
              {
                DEBUG_printf(("1ippReadIO: 1setOf value tag %x(%s) != %x(%s)",
                              value_tag, ippTagString(value_tag), tag,
@@ -2762,6 +2765,7 @@ _ippFreeAttr(ipp_attribute_t *attr)       /* I - Attribute to free */
   {
     case IPP_TAG_TEXT :
     case IPP_TAG_NAME :
+    case IPP_TAG_RESERVED_STRING :
     case IPP_TAG_KEYWORD :
     case IPP_TAG_URI :
     case IPP_TAG_URISCHEME :
index 9eacc3c13d24df927f03b34d07a78f0aad2410c0..a6d4ed4596ef93e786dda0ebee5c11af1d3538a3 100644 (file)
@@ -93,7 +93,8 @@ typedef enum ipp_tag_e                        /**** Format tags for attributes ****/
   IPP_TAG_END_COLLECTION,              /* End of collection value */
   IPP_TAG_TEXT = 0x41,                 /* Text value */
   IPP_TAG_NAME,                                /* Name value */
-  IPP_TAG_KEYWORD = 0x44,              /* Keyword value */
+  IPP_TAG_RESERVED_STRING,             /* Reserved for future string value @private@ */
+  IPP_TAG_KEYWORD,                     /* Keyword value */
   IPP_TAG_URI,                         /* URI value */
   IPP_TAG_URISCHEME,                   /* URI scheme value */
   IPP_TAG_CHARSET,                     /* Character set value */
index 859e6c1a8b4f31e2e5d015d611b064e82c2738e1..48f8a39a64517fef18f4dfba42dd2926375ab29b 100644 (file)
@@ -35,8 +35,10 @@ extern "C" {
 #    include <pthread.h>
 typedef void *(*_cups_thread_func_t)(void *arg);
 typedef pthread_mutex_t _cups_mutex_t;
+typedef pthread_rwlock_t _cups_rwlock_t;
 typedef pthread_key_t  _cups_threadkey_t;
 #    define _CUPS_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER
+#    define _CUPS_RWLOCK_INITIALIZER PTHREAD_RWLOCK_INITIALIZER
 #    define _CUPS_THREADKEY_INITIALIZER -1
 #    define _cupsThreadGetData(k) pthread_getspecific(k)
 #    define _cupsThreadSetData(k,p) pthread_setspecific(k,p)
@@ -51,16 +53,20 @@ typedef struct _cups_mutex_s
   CRITICAL_SECTION     m_criticalSection;
                                        /* Win32 Critical Section */
 } _cups_mutex_t;
+typedef _cups_mutex_t _cups_rwlock_t;  /* TODO: Implement Win32 reader/writer lock */
 typedef DWORD  _cups_threadkey_t;
 #    define _CUPS_MUTEX_INITIALIZER { 0, 0 }
+#    define _CUPS_RWLOCK_INITIALIZER { 0, 0 }
 #    define _CUPS_THREADKEY_INITIALIZER 0
 #    define _cupsThreadGetData(k) TlsGetValue(k)
 #    define _cupsThreadSetData(k,p) TlsSetValue(k,p)
 
 #  else
 typedef char   _cups_mutex_t;
+typedef char   _cups_rwlock_t;
 typedef void   *_cups_threadkey_t;
 #    define _CUPS_MUTEX_INITIALIZER 0
+#    define _CUPS_RWLOCK_INITIALIZER 0
 #    define _CUPS_THREADKEY_INITIALIZER (void *)0
 #    define _cupsThreadGetData(k) k
 #    define _cupsThreadSetData(k,p) k=p
@@ -71,8 +77,13 @@ typedef void *_cups_threadkey_t;
  * Functions...
  */
 
+extern void    _cupsMutexInit(_cups_mutex_t *mutex);
 extern void    _cupsMutexLock(_cups_mutex_t *mutex);
 extern void    _cupsMutexUnlock(_cups_mutex_t *mutex);
+extern void    _cupsRWInit(_cups_rwlock_t *rwlock);
+extern void    _cupsRWLockRead(_cups_rwlock_t *rwlock);
+extern void    _cupsRWLockWrite(_cups_rwlock_t *rwlock);
+extern void    _cupsRWUnlock(_cups_rwlock_t *rwlock);
 extern int     _cupsThreadCreate(_cups_thread_func_t func, void *arg);
 
 
index deb3a8ebc972169b206472db4be7ff1f8bb1cd49..7574298fafb839cffd5d1f8719a40d9654387abd 100644 (file)
  *
  * Contents:
  *
+ *   _cupsMutexInit()    - Initialize a mutex.
  *   _cupsMutexLock()    - Lock a mutex.
  *   _cupsMutexUnlock()  - Unlock a mutex.
+ *   _cupsRWInit()       - Initialize a reader/writer lock.
+ *   _cupsRWLockRead()   - Acquire a reader/writer lock for reading.
+ *   _cupsRWLockWrite()  - Acquire a reader/writer lock for writing.
+ *   _cupsRWUnlock()     - Release a reader/writer lock.
  *   _cupsThreadCreate() - Create a thread.
  */
 
 
 
 #if defined(HAVE_PTHREAD_H)
+/*
+ * '_cupsMutexInit()' - Initialize a mutex.
+ */
+
+void
+_cupsMutexInit(_cups_mutex_t *mutex)   /* I - Mutex */
+{
+  pthread_mutex_init(mutex, NULL);
+}
+
+
 /*
  * '_cupsMutexLock()' - Lock a mutex.
  */
@@ -49,6 +65,50 @@ _cupsMutexUnlock(_cups_mutex_t *mutex)       /* I - Mutex */
 }
 
 
+/*
+ * '_cupsRWInit()' - Initialize a reader/writer lock.
+ */
+
+void
+_cupsRWInit(_cups_rwlock_t *rwlock)    /* I - Reader/writer lock */
+{
+  pthread_rwlock_init(rwlock, NULL);
+}
+
+
+/*
+ * '_cupsRWLockRead()' - Acquire a reader/writer lock for reading.
+ */
+
+void
+_cupsRWLockRead(_cups_rwlock_t *rwlock)        /* I - Reader/writer lock */
+{
+  pthread_rwlock_rdlock(rwlock);
+}
+
+
+/*
+ * '_cupsRWLockWrite()' - Acquire a reader/writer lock for writing.
+ */
+
+void
+_cupsRWLockWrite(_cups_rwlock_t *rwlock)/* I - Reader/writer lock */
+{
+  pthread_rwlock_wrlock(rwlock);
+}
+
+
+/*
+ * '_cupsRWUnlock()' - Release a reader/writer lock.
+ */
+
+void
+_cupsRWUnlock(_cups_rwlock_t *rwlock)  /* I - Reader/writer lock */
+{
+  pthread_rwlock_unlock(rwlock);
+}
+
+
 /*
  * '_cupsThreadCreate()' - Create a thread.
  */
@@ -68,6 +128,18 @@ _cupsThreadCreate(
 #  include <process.h>
 
 
+/*
+ * '_cupsMutexInit()' - Initialize a mutex.
+ */
+
+void
+_cupsMutexInit(_cups_mutex_t *mutex)   /* I - Mutex */
+{
+  InitializeCriticalSection(&mutex->m_criticalSection);
+  mutex->m_init = 1;
+}
+
+
 /*
  * '_cupsMutexLock()' - Lock a mutex.
  */
@@ -103,6 +175,50 @@ _cupsMutexUnlock(_cups_mutex_t *mutex)     /* I - Mutex */
 }
 
 
+/*
+ * '_cupsRWInit()' - Initialize a reader/writer lock.
+ */
+
+void
+_cupsRWInit(_cups_rwlock_t *rwlock)    /* I - Reader/writer lock */
+{
+  _cupsMutexInit((_cups_mutex_t *)rwlock);
+}
+
+
+/*
+ * '_cupsRWLockRead()' - Acquire a reader/writer lock for reading.
+ */
+
+void
+_cupsRWLockRead(_cups_rwlock_t *rwlock)        /* I - Reader/writer lock */
+{
+  _cupsMutexLock((_cups_mutex_t *)rwlock);
+}
+
+
+/*
+ * '_cupsRWLockWrite()' - Acquire a reader/writer lock for writing.
+ */
+
+void
+_cupsRWLockWrite(_cups_rwlock_t *rwlock)/* I - Reader/writer lock */
+{
+  _cupsMutexLock((_cups_mutex_t *)rwlock);
+}
+
+
+/*
+ * '_cupsRWUnlock()' - Release a reader/writer lock.
+ */
+
+void
+_cupsRWUnlock(_cups_rwlock_t *rwlock)  /* I - Reader/writer lock */
+{
+  _cupsMutexUnlock((_cups_mutex_t *)rwlock);
+}
+
+
 /*
  * '_cupsThreadCreate()' - Create a thread.
  */
@@ -118,6 +234,17 @@ _cupsThreadCreate(
 
 
 #else
+/*
+ * '_cupsMutexInit()' - Initialize a mutex.
+ */
+
+void
+_cupsMutexInit(_cups_mutex_t *mutex)   /* I - Mutex */
+{
+  (void)mutex;
+}
+
+
 /*
  * '_cupsMutexLock()' - Lock a mutex.
  */
@@ -125,6 +252,7 @@ _cupsThreadCreate(
 void
 _cupsMutexLock(_cups_mutex_t *mutex)   /* I - Mutex */
 {
+  (void)mutex;
 }
 
 
@@ -135,6 +263,51 @@ _cupsMutexLock(_cups_mutex_t *mutex)       /* I - Mutex */
 void
 _cupsMutexUnlock(_cups_mutex_t *mutex) /* I - Mutex */
 {
+  (void)mutex;
+}
+
+
+/*
+ * '_cupsRWInit()' - Initialize a reader/writer lock.
+ */
+
+void
+_cupsRWInit(_cups_rwlock_t *rwlock)    /* I - Reader/writer lock */
+{
+  (void)rwlock;
+}
+
+
+/*
+ * '_cupsRWLockRead()' - Acquire a reader/writer lock for reading.
+ */
+
+void
+_cupsRWLockRead(_cups_rwlock_t *rwlock)        /* I - Reader/writer lock */
+{
+  (void)rwlock;
+}
+
+
+/*
+ * '_cupsRWLockWrite()' - Acquire a reader/writer lock for writing.
+ */
+
+void
+_cupsRWLockWrite(_cups_rwlock_t *rwlock)/* I - Reader/writer lock */
+{
+  (void)rwlock;
+}
+
+
+/*
+ * '_cupsRWUnlock()' - Release a reader/writer lock.
+ */
+
+void
+_cupsRWUnlock(_cups_rwlock_t *rwlock)  /* I - Reader/writer lock */
+{
+  (void)rwlock;
 }
 #endif /* HAVE_PTHREAD_H */
 
index 31bd93de63096d3085004363989aaa1187086361..7b190031c0c51ecd36abddd4225c2e3f23e89b29 100644 (file)
 .\"   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/".
 .\"
-.TH ipptool 1 "CUPS" "17 October 2010" "Apple Inc."
+.TH ipptool 1 "CUPS" "9 November 2010" "Apple Inc."
 .SH NAME
 ipptool - perform internet printing protocol requests
 .SH SYNOPSIS
 .B ipptool
-[ -C ] [ -E ] [ -I ] [ -L ] [ -S ] [ -T
+[ -4 ] [ -6 ] [ -C ] [ -E ] [ -I ] [ -L ] [ -S ] [ -T
 .I seconds
 ] [ -V
 .I version
@@ -39,6 +39,12 @@ ipptool - perform internet printing protocol requests
 .SH OPTIONS
 The following options are recognized by \fIipptool\fR:
 .TP 5
+-4
+Specifies that \fIipptool\fR must connect to the printer or server using IPv4.
+.TP 5
+-6
+Specifies that \fIipptool\fR must connect to the printer or server using IPv6.
+.TP 5
 -C
 Specifies that requests should be sent using the HTTP/1.1 "Transfer-Encoding: chunked" header, which is required for conformance by all versions of IPP. The default is to use "Transfer-Encoding: chunked" for requests with attached files and "Content-Length:" for requests without attached files.
 .TP 5
index 2f7f0e6c4dbc2dae6044f50140265dab2236534d..300fa6976e83091cff175c2967301d41d6e0f449 100644 (file)
@@ -1915,13 +1915,13 @@ cupsdUpdateDNSSDName(void)
 {
   DNSServiceErrorType error;           /* Error from service creation */
   char         webif[1024];            /* Web interface share name */
-#ifdef HAVE_COREFOUNDATION_H
+#  ifdef HAVE_SYSTEMCONFIGURATION
   SCDynamicStoreRef sc;                        /* Context for dynamic store */
   CFDictionaryRef btmm;                        /* Back-to-My-Mac domains */
   CFStringEncoding nameEncoding;       /* Encoding of computer name */
   CFStringRef  nameRef;                /* Host name CFString */
   char         nameBuffer[1024];       /* C-string buffer */
-#endif /* HAVE_COREFOUNDATION_H */
+#  endif /* HAVE_SYSTEMCONFIGURATION */
 
 
  /*
@@ -1937,7 +1937,7 @@ cupsdUpdateDNSSDName(void)
   * Get the computer name as a c-string...
   */
 
-#ifdef HAVE_COREFOUNDATION_H
+#  ifdef HAVE_SYSTEMCONFIGURATION
   sc = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("cupsd"), NULL, NULL);
 
   if (sc)
@@ -2028,7 +2028,7 @@ cupsdUpdateDNSSDName(void)
     CFRelease(sc);
   }
   else
-#endif /* HAVE_COREFOUNDATION_H */
+#  endif /* HAVE_SYSTEMCONFIGURATION */
   {
     cupsdSetString(&DNSSDComputerName, ServerName);
     cupsdSetString(&DNSSDHostName, ServerName);
index ac77ec7fcc9937a2d05732556e11672aa3962f95..1c29663623ad23493432920be7fc222c45123e5d 100644 (file)
@@ -1,5 +1,13 @@
 # DO NOT DELETE THIS LINE -- make depend depends on it.
 
+ippserver.o: ../cups/cups-private.h ../cups/cups.h ../cups/file.h
+ippserver.o: ../cups/versioning.h ../cups/ipp.h ../cups/http.h
+ippserver.o: ../cups/array.h ../cups/language.h ../cups/string-private.h
+ippserver.o: ../config.h ../cups/debug-private.h ../cups/ppd-private.h
+ippserver.o: ../cups/ppd.h ../cups/cups.h ../cups/pwg-private.h
+ippserver.o: ../cups/http-private.h ../cups/http.h ../cups/md5-private.h
+ippserver.o: ../cups/ipp-private.h ../cups/ipp.h ../cups/language-private.h
+ippserver.o: ../cups/transcode.h ../cups/thread-private.h
 ipptool.o: ../cups/cups-private.h ../cups/cups.h ../cups/file.h
 ipptool.o: ../cups/versioning.h ../cups/ipp.h ../cups/http.h ../cups/array.h
 ipptool.o: ../cups/language.h ../cups/string-private.h ../config.h
index f621fa66c39ee9add8dc2208f6252d1dc14e1f0b..dfbe4245a07a724381ee3024fbe23b4a1133c6a4 100644 (file)
@@ -31,13 +31,20 @@ TESTFILES   =       \
                        testfile.pdf \
                        testfile.ps \
                        testfile.txt
+OBJS           =       \
+                       ippserver.o \
+                       ipptool.o
+TARGETS                =       \
+                       ippserver \
+                       ipptool \
+                       ipptool-static
 
 
 #
 # Make all targets...
 #
 
-all:   ipptool ipptool-static
+all:           $(TARGETS)
 
 
 #
@@ -59,7 +66,7 @@ unittests:
 #
 
 clean:
-       $(RM) ipptool ipptool.o ipptool-static
+       $(RM) $(TARGETS) $(OBJS)
 
 
 #
@@ -67,7 +74,7 @@ clean:
 #
 
 depend:
-       makedepend -Y -I.. -fDependencies ipptool.c >/dev/null 2>&1
+       makedepend -Y -I.. -fDependencies $(OBJS:.o=.c) >/dev/null 2>&1
 
 
 #
@@ -124,6 +131,16 @@ install-libs:
 uninstall:
 
 
+#
+# ippserver
+#
+
+ippserver:     ippserver.o ../cups/$(LIBCUPSSTATIC)
+       echo Linking $@...
+       $(CC) $(LDFLAGS) -o $@ ippserver.o  ../cups/$(LIBCUPSSTATIC) \
+               $(LIBGSSAPI) $(SSLLIBS) $(DNSSDLIBS) $(COMMONLIBS) $(LIBZ)
+
+
 #
 # ipptool
 #
diff --git a/test/ippserver.c b/test/ippserver.c
new file mode 100644 (file)
index 0000000..724162d
--- /dev/null
@@ -0,0 +1,4299 @@
+/*
+ * "$Id$"
+ *
+ *   Sample IPP/2.0 server for CUPS.
+ *
+ *   Copyright 2010 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/".
+ *
+ * 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.
+ */
+
+/*
+ * Include necessary headers...
+ */
+
+#include <cups/cups-private.h>
+#include <dns_sd.h>
+#include <sys/stat.h>
+#include <poll.h>
+#ifdef HAVE_SYS_MOUNT_H
+#  include <sys/mount.h>
+#endif /* HAVE_SYS_MOUNT_H */
+#ifdef HAVE_SYS_STATFS_H
+#  include <sys/statfs.h>
+#endif /* HAVE_SYS_STATFS_H */
+#ifdef HAVE_SYS_STATVFS_H
+#  include <sys/statvfs.h>
+#endif /* HAVE_SYS_STATVFS_H */
+#ifdef HAVE_SYS_VFS_H
+#  include <sys/vfs.h>
+#endif /* HAVE_SYS_VFS_H */
+
+
+/*
+ * Constants...
+ */
+
+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,
+                                       /* input-tray-missing */
+  _IPP_PRINTER_MARKER_SUPPLY_EMPTY = 0x0008,
+                                       /* marker-supply-empty */
+  _IPP_PRINTER_MARKER_SUPPLY_LOW = 0x0010,
+                                       /* marker-suply-low */
+  _IPP_PRINTER_MARKER_WASTE_ALMOST_FULL = 0x0020,
+                                       /* marker-waste-almost-full */
+  _IPP_PRINTER_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,
+                                       /* 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 */
+};
+typedef unsigned int _ipp_preasons_t;  /* Bitfield for printer-state-reasons */
+
+typedef enum _ipp_media_class_e
+{
+  _IPP_GENERAL,                                /* General-purpose size */
+  _IPP_PHOTO_ONLY,                     /* Photo-only size */
+  _IPP_ENV_ONLY                                /* Envelope-only size */
+} _ipp_media_class_t;
+
+static const char * const media_supported[] =
+{                                      /* media-supported values */
+  "iso_a4_210x297mm",                  /* A4 */
+  "iso_a5_148x210mm",                  /* A5 */
+  "iso_a6_105x148mm",                  /* A6 */
+  "iso_dl_110x220mm",                  /* DL */
+  "na_legal_8.5x14in",                 /* Legal */
+  "na_letter_8.5x11in",                        /* Letter */
+  "na_number-10_4.125x9.5in",          /* #10 */
+  "na_index-3x5_3x5in",                        /* 3x5 */
+  "oe_photo-l_3.5x5in",                        /* L */
+  "na_index-4x6_4x6in",                        /* 4x6 */
+  "na_5x7_5x7in"                       /* 5x7 */
+};
+static const int media_col_sizes[][3] =
+{                                      /* media-col-database sizes */
+  { 21000, 29700, _IPP_GENERAL },      /* A4 */
+  { 14800, 21000, _IPP_PHOTO_ONLY },   /* A5 */
+  { 10500, 14800, _IPP_PHOTO_ONLY },   /* A6 */
+  { 11000, 22000, _IPP_ENV_ONLY },     /* DL */
+  { 21590, 35560, _IPP_GENERAL },      /* Legal */
+  { 21590, 27940, _IPP_GENERAL },      /* Letter */
+  { 10477, 24130, _IPP_ENV_ONLY },     /* #10 */
+  {  7630, 12700, _IPP_PHOTO_ONLY },   /* 3x5 */
+  {  8890, 12700, _IPP_PHOTO_ONLY },   /* L */
+  { 10160, 15240, _IPP_PHOTO_ONLY },   /* 4x6 */
+  { 12700, 17780, _IPP_PHOTO_ONLY }    /* 5x7 */
+};
+static const char * const media_type_supported[] =
+                                     /* media-type-supported values */
+{
+  "auto",
+  "cardstock",
+  "envelope",
+  "labels",
+  "other",
+  "photographic-glossy",
+  "photographic-high-gloss",
+  "photographic-matte",
+  "photographic-satin",
+  "photographic-semi-gloss",
+  "stationery",
+  "stationery-letterhead",
+  "transparency"
+};
+
+
+/*
+ * Structures...
+ */
+
+typedef struct _ipp_job_s _ipp_job_t;
+
+typedef struct _ipp_printer_s          /**** Printer data ****/
+{
+  int                  ipv4,           /* IPv4 listener */
+                       ipv6;           /* IPv6 listener */
+  DNSServiceRef                common_ref,     /* Shared service connection */
+                       ipp_ref,        /* Bonjour IPP service */
+                       http_ref,       /* Bonjour HTTP service */
+                       printer_ref;    /* Bonjour LPD service */
+  TXTRecordRef         ipp_txt;        /* Bonjour IPP TXT record */
+  char                 *name,          /* printer-name */
+                       *dnssd_name,    /* printer-dnssd-name */
+                       *icon,          /* Icon filename */
+                       *directory,     /* Spool directory */
+                       *hostname,      /* Hostname */
+                       *uri;           /* printer-uri-supported */
+  int                  port;           /* Port */
+  size_t               urilen;         /* Length of printer URI */
+  ipp_t                        *attrs;         /* Static attributes */
+  ipp_pstate_t         state;          /* printer-state value */
+  _ipp_preasons_t      state_reasons;  /* printer-state-reasons values */  
+  cups_array_t         *jobs;          /* Jobs */
+  _ipp_job_t           *active_job;    /* Current active/pending job */
+  int                  next_job_id;    /* Next job-id value */
+  _cups_rwlock_t       rwlock;         /* Printer lock */
+} _ipp_printer_t;
+
+struct _ipp_job_s                      /**** Job data ****/
+{
+  int                  id;             /* Job ID */
+  char                 *name,          /* job-name */
+                       *username,      /* job-originating-user-name */
+                       *format;        /* document-format */
+  ipp_jstate_t         state;          /* job-state value */
+  time_t               processing,     /* time-at-processing value */
+                       completed;      /* time-at-completed value */
+  ipp_t                        *attrs;         /* Static attributes */
+  int                  cancel;         /* Non-zero when job canceled */
+  char                 *filename;      /* Print file name */
+  int                  fd;             /* Print file descriptor */
+  _ipp_printer_t       *printer;       /* Printer */
+};
+
+typedef struct _ipp_client_s           /**** Client data ****/
+{
+  http_t               http;           /* HTTP connection */
+  ipp_t                        *request,       /* IPP request */
+                       *response;      /* IPP response */
+  time_t               start;          /* Request start time */
+  http_state_t         operation;      /* Request operation */
+  ipp_op_t             operation_id;   /* IPP operation-id */
+  char                 uri[1024];      /* Request URI */
+  http_addr_t          addr;           /* Client address */
+  _ipp_printer_t       *printer;       /* Printer */
+  _ipp_job_t           *job;           /* Current job, if any */
+} _ipp_client_t;
+
+
+/*
+ * Local functions...
+ */
+
+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,
+                                           _ipp_job_t *job, cups_array_t *ra);
+static _ipp_client_t   *create_client(_ipp_printer_t *printer, int sock);
+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_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,
+                                       const char *regtype,
+                                       const char *directory);
+static cups_array_t    *create_requested_array(_ipp_client_t *client);
+static void            debug_attributes(const char *title, ipp_t *ipp);
+static void            delete_client(_ipp_client_t *client);
+static void            delete_job(_ipp_job_t *job);
+static void            delete_printer(_ipp_printer_t *printer);
+static void            dnssd_callback(DNSServiceRef sdRef,
+                                      DNSServiceFlags flags,
+                                      DNSServiceErrorType errorCode,
+                                      const char *name,
+                                      const char *regtype,
+                                      const char *domain,
+                                      _ipp_printer_t *printer);
+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__ */
+;
+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_send_document(_ipp_client_t *client);
+#endif /* 0 */
+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);
+static int             process_ipp(_ipp_client_t *client);
+static void            *process_job(_ipp_job_t *job);
+static int             register_printer(_ipp_printer_t *printer,
+                                        const char *location, const char *make,
+                                        const char *model, const char *formats,
+                                        const char *adminurl, int color,
+                                        int duplex, const char *regtype);
+static int             respond_http(_ipp_client_t *client, http_status_t code,
+                                    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__ */
+;
+static void            run_printer(_ipp_printer_t *printer);
+static void            usage(int status);
+static int             valid_job_attributes(_ipp_client_t *client);
+
+
+/*
+ * Globals...
+ */
+
+static int             KeepFiles = 0,
+                       Verbosity = 0;
+
+
+/*
+ * 'main()' - Main entry to the sample server.
+ */
+
+int                                    /* O - Exit status */
+main(int  argc,                                /* I - Number of command-line args */
+     char *argv[])                     /* I - Command-line arguments */
+{
+  int          i;                      /* Looping var */
+  const char   *opt,                   /* Current option character */
+               *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",
+                                       /* Supported formats */
+               *regtype = "_ipp._tcp"; /* Bonjour service type */
+  int          port = 8631,            /* Port number (0 = auto) TODO: FIX */
+               duplex = 0,             /* Duplex mode */
+               ppm = 10,               /* Pages per minute for mono */
+               ppm_color = 0;          /* Pages per minute for color */
+  char         directory[1024] = "";   /* Spool directory */
+  _ipp_printer_t *printer;             /* Printer object */
+
+
+ /*
+  * Parse command-line arguments...
+  */
+
+  for (i = 1; i < argc; i ++)
+    if (argv[i][0] == '-')
+    {
+      for (opt = argv[i] + 1; *opt; opt ++)
+        switch (*opt)
+       {
+         case '2' : /* -2 (enable 2-sided printing) */
+             duplex = 1;
+             break;
+
+         case 'M' : /* -M manufacturer */
+             i ++;
+             if (i >= argc)
+               usage(1);
+             make = argv[i];
+             break;
+
+         case 'd' : /* -d spool-directory */
+             i ++;
+             if (i >= argc)
+               usage(1);
+             strlcpy(directory, argv[i], sizeof(directory));
+             break;
+
+         case 'f' : /* -f type/subtype[,...] */
+             i ++;
+             if (i >= argc)
+               usage(1);
+             formats = argv[i];
+             break;
+
+          case 'h' : /* -h (show help) */
+             usage(0);
+             break;
+
+         case 'i' : /* -i icon.png */
+             i ++;
+             if (i >= argc)
+               usage(1);
+             icon = argv[i];
+             break;
+
+         case 'k' : /* -k (keep files) */
+             KeepFiles = 1;
+             break;
+
+         case 'l' : /* -l location */
+             i ++;
+             if (i >= argc)
+               usage(1);
+             location = argv[i];
+             break;
+
+         case 'm' : /* -m model */
+             i ++;
+             if (i >= argc)
+               usage(1);
+             model = argv[i];
+             break;
+
+         case 'n' : /* -n hostname */
+             i ++;
+             if (i >= argc)
+               usage(1);
+             servername = argv[i];
+             break;
+
+         case 'p' : /* -p port */
+             i ++;
+             if (i >= argc || !isdigit(argv[i][0] & 255))
+               usage(1);
+             port = atoi(argv[i]);
+             break;
+
+         case 'r' : /* -r regtype */
+             i ++;
+             if (i >= argc)
+               usage(1);
+             regtype = argv[i];
+             break;
+
+         case 's' : /* -s speed[,color-speed] */
+             i ++;
+             if (i >= argc)
+               usage(1);
+             if (sscanf(argv[i], "%d,%d", &ppm, &ppm_color) < 1)
+               usage(1);
+             break;
+
+         case 'v' : /* -v (be verbose) */
+             Verbosity ++;
+             break;
+
+          default : /* Unknown */
+             fprintf(stderr, "Unknown option \"-%c\".\n", *opt);
+             usage(1);
+             break;
+       }
+    }
+    else if (!name)
+    {
+      name = argv[i];
+    }
+    else
+    {
+      fprintf(stderr, "Unexpected command-line argument \"%s\"\n", argv[i]);
+      usage(1);
+    }
+
+  if (!name)
+    usage(1);
+
+ /*
+  * Apply defaults as needed...
+  */
+
+  if (!directory[0])
+  {
+    snprintf(directory, sizeof(directory), "/tmp/ippserver.%d", (int)getpid());
+
+    if (mkdir(directory, 0777) && errno != EEXIST)
+    {
+      fprintf(stderr, "Unable to create spool directory \"%s\": %s\n",
+             directory, strerror(errno));
+      usage(1);
+    }
+
+    if (Verbosity)
+      fprintf(stderr, "Using spool directory \"%s\".\n", directory);
+  }
+
+ /*
+  * Create the printer...
+  */
+
+  if ((printer = create_printer(servername, name, location, make, model, icon,
+                                formats, ppm, ppm_color, duplex, port, regtype,
+                               directory)) == NULL)
+    return (1);
+
+ /*
+  * Run the print service...
+  */
+
+  run_printer(printer);
+
+ /*
+  * Destroy the printer and exit...
+  */
+
+  delete_printer(printer);
+
+  return (0);
+}
+
+
+/*
+ * 'clean_jobs()' - Clean out old (completed) jobs.
+ */
+
+static void
+clean_jobs(_ipp_printer_t *printer)    /* I - Printer */
+{
+  _ipp_job_t   *job;                   /* Current job */
+  time_t       cleantime;              /* Clean time */
+
+
+  if (cupsArrayCount(printer->jobs) == 0)
+    return;
+
+  cleantime = time(NULL) - 60;
+
+  _cupsRWLockWrite(&(printer->rwlock));
+  for (job = (_ipp_job_t *)cupsArrayFirst(printer->jobs);
+       job;
+       job = (_ipp_job_t *)cupsArrayNext(printer->jobs))
+    if (job->completed && job->completed < cleantime)
+    {
+      cupsArrayRemove(printer->jobs, job);
+      delete_job(job);
+    }
+    else
+      break;
+  _cupsRWUnlock(&(printer->rwlock));
+}
+
+
+/*
+ * 'compare_jobs()' - Compare two jobs.
+ */
+
+static int                             /* O - Result of comparison */
+compare_jobs(_ipp_job_t *a,            /* I - First job */
+             _ipp_job_t *b)            /* I - Second job */
+{
+  return (b->id - a->id);
+}
+
+
+/*
+ * '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.
+ */
+
+static void
+copy_attributes(ipp_t        *to,      /* I - Destination request */
+               ipp_t        *from,     /* I - Source request */
+               cups_array_t *ra,       /* I - Requested attributes */
+               ipp_tag_t    group_tag, /* I - Group to copy */
+               int          quickcopy) /* I - Do a quick copy? */
+{
+  ipp_attribute_t      *fromattr;      /* Source attribute */
+
+
+  if (!to || !from)
+    return;
+
+  for (fromattr = from->attrs; fromattr; fromattr = fromattr->next)
+  {
+   /*
+    * Filter attributes as needed...
+    */
+
+    if ((group_tag != IPP_TAG_ZERO && fromattr->group_tag != group_tag &&
+         fromattr->group_tag != IPP_TAG_ZERO) || !fromattr->name)
+      continue;
+
+    if (!ra || cupsArrayFind(ra, fromattr->name))
+      copy_attribute(to, fromattr, fromattr->group_tag, quickcopy);
+  }
+}
+
+
+/*
+ * 'copy_job_attrs()' - Copy job attributes to the response.
+ */
+
+static void
+copy_job_attributes(
+    _ipp_client_t *client,             /* I - Client */
+    _ipp_job_t    *job,                        /* I - Job */
+    cups_array_t  *ra)                 /* I - requested-attributes */
+{
+  copy_attributes(client->response, job->attrs, ra, 0, IPP_TAG_ZERO);
+
+  if (!ra || cupsArrayFind(ra, "job-printer-up-time"))
+    ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_INTEGER,
+                  "job-printer-up-time", (int)time(NULL));
+
+  if (!ra || cupsArrayFind(ra, "job-state"))
+    ippAddInteger(client->response, IPP_TAG_JOB, IPP_TAG_ENUM,
+                 "job-state", job->state);
+
+  if (!ra || cupsArrayFind(ra, "job-state-reasons"))
+  {
+    switch (job->state)
+    {
+      case IPP_JOB_PENDING :
+         ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD,
+                      "job-state-reasons", NULL, "none");
+         break;
+
+      case IPP_JOB_HELD :
+          if (job->fd >= 0)
+           ippAddString(client->response, IPP_TAG_JOB, 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,
+                        "job-state-reasons", NULL, "job-hold-until-specified");
+         break;
+
+      case IPP_JOB_PROCESSING :
+         if (job->cancel)
+           ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD,
+                        "job-state-reasons", NULL, "processing-to-stop-point");
+         else
+           ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD,
+                        "job-state-reasons", NULL, "job-printing");
+         break;
+
+      case IPP_JOB_STOPPED :
+         ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD,
+                      "job-state-reasons", NULL, "job-stopped");
+         break;
+
+      case IPP_JOB_CANCELED :
+         ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD,
+                      "job-state-reasons", NULL, "job-canceled-by-user");
+         break;
+
+      case IPP_JOB_ABORTED :
+         ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD,
+                      "job-state-reasons", NULL, "aborted-by-system");
+         break;
+
+      case IPP_JOB_COMPLETED :
+         ippAddString(client->response, IPP_TAG_JOB, IPP_TAG_KEYWORD,
+                      "job-state-reasons", NULL, "job-completed-successfully");
+         break;
+    }
+  }
+
+  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);
+
+  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);
+}
+
+
+/*
+ * 'create_client()' - Accept a new network connection and create a client
+ *                     object.
+ */
+
+static _ipp_client_t *                 /* O - Client */
+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)
+  {
+    perror("Unable to allocate memory for client");
+    return (NULL);
+  }
+
+  client->printer       = printer;
+  client->http.activity = time(NULL);
+  client->http.hostaddr = &(client->addr);
+  client->http.blocking = 1;
+
+ /*
+  * 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)
+  {
+    perror("Unable to accept client connection");
+
+    free(client);
+
+    return (NULL);
+  }
+
+  httpAddrString(&(client->addr), client->http.hostname,
+                sizeof(client->http.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));
+
+  return (client);
+}
+
+
+/*
+ * 'create_job()' - Create a new job object from a Print-Job or Create-Job
+ *                  request.
+ */
+
+static _ipp_job_t *                    /* O - Job */
+create_job(_ipp_client_t *client)      /* I - Client */
+{
+  _ipp_job_t           *job;           /* Job */
+  ipp_attribute_t      *attr;          /* Job attribute */
+  char                 uri[1024];      /* job-uri value */
+
+
+  _cupsRWLockWrite(&(client->printer->rwlock));
+  if (client->printer->active_job &&
+      client->printer->active_job->state < IPP_JOB_CANCELED)
+  {
+   /*
+    * Only accept a single job at a time...
+    */
+
+    _cupsRWLockWrite(&(client->printer->rwlock));
+    return (NULL);
+  }
+
+ /*
+  * Allocate and initialize the job object...
+  */
+
+  if ((job = calloc(1, sizeof(_ipp_job_t))) == NULL)
+  {
+    perror("Unable to allocate memory for job");
+    return (NULL);
+  }
+
+  job->printer    = client->printer;
+  job->attrs      = client->request;
+  job->state      = IPP_JOB_HELD;
+  job->fd         = -1;
+  client->request = NULL;
+
+ /*
+  * 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;
+
+ /*
+  * Get the requesting-user-name, document format, and priority...
+  */
+
+  if ((attr = ippFindAttribute(job->attrs, "requesting-user-name",
+                               IPP_TAG_NAME)) != NULL)
+  {
+    _cupsStrFree(attr->name);
+    attr->name = _cupsStrAlloc("job-originating-user-name");
+  }
+  else
+    attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME,
+                        "job-originating-user-name", NULL, "anonymous");
+
+  if (attr)
+    job->username = attr->values[0].string.text;
+  else
+    job->username = "anonymous";
+
+  if ((attr = ippFindAttribute(job->attrs, "document-format",
+                               IPP_TAG_MIMETYPE)) != NULL)
+    job->format = attr->values[0].string.text;
+  else
+    job->format = "application/octet-stream";
+
+ /*
+  * Add job description attributes and add to the jobs array...
+  */
+
+  job->id = client->printer->next_job_id ++;
+
+  snprintf(uri, sizeof(uri), "%s/%d", client->printer->uri, job->id);
+
+  ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id);
+  ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL, uri);
+  ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL,
+               client->printer->uri);
+  ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "time-at-creation",
+                (int)time(NULL));
+
+  cupsArrayAdd(client->printer->jobs, job);
+  client->printer->active_job = job;
+
+  _cupsRWUnlock(&(client->printer->rwlock));
+
+  return (job);
+}
+
+
+/*
+ * 'create_listener()' - Create a listener socket.
+ */
+
+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);
+
+  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);
+
+    fprintf(stderr, "Listening on port %d.\n", *port);
+  }
+
+  memset(&address, 0, sizeof(address));
+  if (family == AF_INET)
+  {
+    address.ipv4.sin_family = family;
+    address.ipv4.sin_port   = htons(*port);
+  }
+  else
+  {
+    address.ipv6.sin6_family = family;
+    address.ipv6.sin6_port   = htons(*port);
+  }
+
+  if (bind(sock, (struct sockaddr *)&address, httpAddrLength(&address)))
+  {
+    close(sock);
+    return (-1);
+  }
+
+  if (listen(sock, 5))
+  {
+    close(sock);
+    return (-1);
+  }
+
+  return (sock);
+}
+
+
+/*
+ * 'create_media_col()' - Create a media-col value.
+ */
+
+static ipp_t *                         /* O - media-col collection */
+create_media_col(const char *media,    /* I - Media name */
+                const char *type,      /* I - Nedua type */
+                int        width,      /* I - x-dimension in 2540ths */
+                int        length,     /* I - y-dimension in 2540ths */
+                int        margins)    /* I - Value for margins */
+{
+  ipp_t        *media_col = ippNew(),          /* media-col value */
+       *media_size = ippNew();         /* 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" : "");
+
+  ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-key", NULL,
+               media_key);
+  ippAddCollection(media_col, IPP_TAG_PRINTER, "media-size", media_size);
+  ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                "media-bottom-margin", margins);
+  ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                "media-left-margin", margins);
+  ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                "media-right-margin", margins);
+  ippAddInteger(media_col, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                "media-top-margin", margins);
+  ippAddString(media_col, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, "media-type",
+               NULL, type);
+
+  ippDelete(media_size);
+
+  return (media_col);
+}
+
+
+/*
+ * 'create_printer()' - Create, register, and listen for connections to a
+ *                      printer object.
+ */
+
+static _ipp_printer_t *                        /* O - Printer */
+create_printer(const char *servername, /* I - Server hostname (NULL for default) */
+               const char *name,       /* I - printer-name */
+              const char *location,    /* I - printer-location */
+              const char *make,        /* I - printer-make-and-model */
+              const char *model,       /* I - printer-make-and-model */
+              const char *icon,        /* I - printer-icons */
+              const char *docformats,  /* I - document-format-supported */
+              int        ppm,          /* I - Pages per minute in grayscale */
+              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 */
+              const char *regtype,     /* I - Bonjour service type */
+              const char *directory)   /* I - Spool directory */
+{
+  int                  i, j;           /* Looping vars */
+  _ipp_printer_t       *printer;       /* Printer */
+  char                 hostname[256],  /* Hostname */
+                       uri[1024],      /* Printer URI */
+                       icons[1024],    /* printer-icons URI */
+                       adminurl[1024], /* printer-more-info URI */
+                       device_id[1024],/* printer-device-id */
+                       make_model[128];/* printer-make-and-model */
+  int                  num_formats;    /* Number of document-format-supported values */
+  char                 *defformat,     /* document-format-default value */
+                       *formats[100],  /* document-format-supported values */
+                       *ptr;           /* Pointer into string */
+  const char           *prefix;        /* Prefix string */
+  int                  num_database;   /* Number of database values */
+  ipp_attribute_t      *media_col_database;
+                                       /* media-col-database value */
+  ipp_t                        *media_col_default;
+                                       /* media-col-default value */
+  ipp_value_t          *media_col_value;
+                                       /* Current media-col-database value */
+  int                  k_supported;    /* Maximum file size supported */
+#ifdef HAVE_STATFS
+  struct statfs                spoolinfo;      /* FS info for spool directory */
+  double               spoolsize;      /* FS size */
+#elif defined(HAVE_STATVFS)
+  struct statvfs       spoolinfo;      /* FS info for spool directory */
+  double               spoolsize;      /* FS size */
+#endif /* HAVE_STATFS */
+  static const int     orients[4] =    /* orientation-requested-supported values */
+  {
+    IPP_PORTRAIT,
+    IPP_LANDSCAPE,
+    IPP_REVERSE_LANDSCAPE,
+    IPP_REVERSE_PORTRAIT
+  };
+  static const char * const versions[] =/* ipp-versions-supported values */
+  {
+    "1.0",
+    "1.1",
+    "2.0"
+  };
+  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
+  };
+  static const char * const charsets[] =/* charset-supported values */
+  {
+    "us-ascii",
+    "utf-8"
+  };
+  static const char * const job_creation[] =
+  {                                    /* job-creation-attributes-supported values */
+    "copies",
+    "ipp-attribute-fidelity",
+    "job-name",
+    "job-priority",
+    "media",
+    "media-col",
+    "multiple-document-handling",
+    "orientation-requested",
+    "print-quality",
+    "sides"
+  };
+  static const char * const media_col_supported[] =
+  {                                    /* media-col-supported values */
+    "media-bottom-margin",
+    "media-left-margin",
+    "media-right-margin",
+    "media-size",
+    "media-top-margin",
+    "media-type"
+  };
+  static const int     media_xxx_margin_supported[] =
+  {                                    /* media-xxx-margin-supported values */
+    0,
+    635
+  };
+  static const char * const multiple_document_handling[] =
+  {                                    /* multiple-document-handling-supported values */
+    "separate-documents-uncollated-copies",
+    "separate-documents-collated-copies"
+  };
+  static const int     print_quality_supported[] =
+  {                                    /* print-quality-supported values */
+    IPP_QUALITY_DRAFT,
+    IPP_QUALITY_NORMAL,
+    IPP_QUALITY_HIGH
+  };
+  static const char * const sides_supported[] =
+  {                                    /* sides-supported values */
+    "one-sided",
+    "two-sided-long-edge",
+    "two-sided-short-edge"
+  };
+  static const char * const which_jobs[] =
+  {                                    /* which-jobs-supported values */
+    "completed",
+    "not-completed",
+    "aborted",
+    "all",
+    "canceled",
+    "pending",
+    "pending-held",
+    "processing",
+    "processing-stopped"
+  };
+
+
+ /*
+  * Allocate memory for the printer...
+  */
+
+  if ((printer = calloc(1, sizeof(_ipp_printer_t))) == NULL)
+  {
+    perror("Unable to allocate memory for printer");
+    return (NULL);
+  }
+
+  printer->ipv4          = -1;
+  printer->ipv6          = -1;
+  printer->name          = _cupsStrAlloc(name);
+  printer->dnssd_name    = _cupsStrRetain(printer->name);
+  printer->directory     = _cupsStrAlloc(directory);
+  printer->hostname      = _cupsStrAlloc(servername ? servername :
+                                             httpGetHostname(NULL, hostname,
+                                                             sizeof(hostname)));
+  printer->port          = port;
+  printer->state         = IPP_PRINTER_IDLE;
+  printer->state_reasons = _IPP_PRINTER_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->urilen = strlen(uri);
+
+  _cupsRWInit(&(printer->rwlock));
+
+ /*
+  * Create the listener sockets...
+  */
+
+  if ((printer->ipv4 = create_listener(AF_INET, &(printer->port))) < 0)
+  {
+    perror("Unable to create IPv4 listener");
+    goto bad_printer;
+  }
+
+  if ((printer->ipv6 = create_listener(AF_INET6, &(printer->port))) < 0)
+  {
+    perror("Unable to create IPv6 listener");
+    goto bad_printer;
+  }
+
+ /*
+  * Prepare values for the printer attributes...
+  */
+
+  httpAssembleURI(HTTP_URI_CODING_ALL, icons, sizeof(icons), "http", NULL,
+                  printer->hostname, printer->port, "/icon.png");
+  httpAssembleURI(HTTP_URI_CODING_ALL, adminurl, sizeof(adminurl), "http", NULL,
+                  printer->hostname, printer->port, "/");
+
+  if (Verbosity)
+  {
+    fprintf(stderr, "printer-more-info=\"%s\"\n", adminurl);
+    fprintf(stderr, "printer-uri=\"%s\"\n", uri);
+  }
+
+  snprintf(make_model, sizeof(make_model), "%s %s", make, model);
+
+  num_formats = 1;
+  formats[0]  = strdup(docformats);
+  defformat   = formats[0];
+  for (ptr = strchr(formats[0], ','); ptr; ptr = strchr(ptr, ','))
+  {
+    *ptr++ = '\0';
+    formats[num_formats++] = ptr;
+
+    if (!strcasecmp(ptr, "application/octet-stream"))
+      defformat = ptr;
+  }
+
+  snprintf(device_id, sizeof(device_id), "MFG:%s;MDL:%s;", make, model);
+  ptr    = device_id + strlen(device_id);
+  prefix = "CMD:";
+  for (i = 0; i < num_formats; i ++)
+  {
+    if (!strcasecmp(formats[i], "application/pdf"))
+      snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPDF", prefix);
+    else if (!strcasecmp(formats[i], "application/postscript"))
+      snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPS", prefix);
+    else if (!strcasecmp(formats[i], "application/vnd.hp-PCL"))
+      snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPCL", prefix);
+    else if (!strcasecmp(formats[i], "image/jpeg"))
+      snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sJPEG", prefix);
+    else if (!strcasecmp(formats[i], "image/png"))
+      snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%sPNG", prefix);
+    else if (strcasecmp(formats[i], "application/octet-stream"))
+      snprintf(ptr, sizeof(device_id) - (ptr - device_id), "%s%s", prefix,
+               formats[i]);
+
+    ptr += strlen(ptr);
+    prefix = ",";
+  }
+  strlcat(device_id, ";", sizeof(device_id));
+
+ /*
+  * Get the maximum spool size based on the size of the filesystem used for
+  * the spool directory.  If the host OS doesn't support the statfs call
+  * or the filesystem is larger than 2TiB, always report INT_MAX.
+  */
+
+#ifdef HAVE_STATFS
+  if (statfs(printer->directory, &spoolinfo))
+    k_supported = INT_MAX;
+  else if ((spoolsize = (double)spoolinfo.f_bsize *
+                        spoolinfo.f_blocks / 1024) > INT_MAX)
+    k_supported = INT_MAX;
+  else
+    k_supported = (int)spoolsize;
+
+#elif defined(HAVE_STATVFS)
+  if (statvfs(printer->directory, &spoolinfo))
+    k_supported = INT_MAX;
+  else if ((spoolsize = (double)spoolinfo.f_frsize *
+                        spoolinfo.f_blocks / 1024) > INT_MAX)
+    k_supported = INT_MAX;
+  else
+    k_supported = (int)spoolsize;
+
+#else
+  k_supported = INT_MAX;
+#endif /* HAVE_STATFS */
+
+ /*
+  * Create the printer attributes.  This list of attributes is sorted to improve
+  * performance when the client provides a requested-attributes attribute...
+  */
+
+  printer->attrs = ippNew();
+
+  /* charset-configured */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_CHARSET | IPP_TAG_COPY,
+               "charset-configured", NULL, "utf-8");
+
+  /* charset-supported */
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_CHARSET | IPP_TAG_COPY,
+                "charset-supported", sizeof(charsets) / sizeof(charsets[0]),
+               NULL, charsets);
+
+  /* color-supported */
+  ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "color-supported",
+                ppm_color > 0);
+
+  /* compression-supported */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+              "compression-supported", NULL, "none");
+
+  /* copies-default */
+  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                "copies-default", 1);
+
+  /* copies-supported */
+  ippAddRange(printer->attrs, IPP_TAG_PRINTER, "copies-supported", 1, 999);
+
+  /* document-format-default */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE | IPP_TAG_COPY,
+               "document-format-default", NULL, "application/octet-stream");
+
+  /* document-format-supported */
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_MIMETYPE,
+                "document-format-supported", num_formats, NULL,
+               (const char * const *)formats);
+
+  /* finishings-default */
+  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
+                "finishings-default", IPP_FINISHINGS_NONE);
+
+  /* finishings-supported */
+  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
+                "finishings-supported", IPP_FINISHINGS_NONE);
+
+  /* generated-natural-language-supported */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE | IPP_TAG_COPY,
+               "generated-natural-language-supported", NULL, "en");
+
+  /* ipp-versions-supported */
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+                "ipp-versions-supported",
+               sizeof(versions) / sizeof(versions[0]), NULL, versions);
+
+  /* job-creation-attributes-supported */
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+                "job-creation-attributes-supported",
+               sizeof(job_creation) / sizeof(job_creation[0]),
+               NULL, job_creation);
+
+  /* job-k-octets-supported */
+  ippAddRange(printer->attrs, IPP_TAG_PRINTER, "job-k-octets-supported", 0,
+             k_supported);
+
+  /* job-priority-default */
+  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                "job-priority-default", 50);
+
+  /* job-priority-supported */
+  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                "job-priority-supported", 100);
+
+  /* job-sheets-default */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY,
+               "job-sheets-default", NULL, "none");
+
+  /* job-sheets-supported */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY,
+               "job-sheets-supported", NULL, "none");
+
+  /* media-bottom-margin-supported */
+  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                 "media-bottom-margin-supported",
+                (int)(sizeof(media_xxx_margin_supported) /
+                      sizeof(media_xxx_margin_supported[0])),
+                media_xxx_margin_supported);
+
+  /* media-col-database */
+  for (num_database = 0, i = 0;
+       i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0]));
+       i ++)
+  {
+    if (media_col_sizes[i][2] == _IPP_ENV_ONLY)
+      num_database += 2;               /* auto + envelope */
+    else if (media_col_sizes[i][2] == _IPP_PHOTO_ONLY)
+      num_database += 12;              /* auto + photographic-* + borderless */
+    else
+      num_database += (int)(sizeof(media_type_supported) /
+                            sizeof(media_type_supported[0])) + 6;
+                                       /* All types + borderless */
+  }
+
+  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;
+       i < (int)(sizeof(media_col_sizes) / sizeof(media_col_sizes[0]));
+       i ++)
+  {
+    for (j = 0;
+         j < (int)(sizeof(media_type_supported) /
+                  sizeof(media_type_supported[0]));
+         j ++)
+    {
+      if (media_col_sizes[i][2] == _IPP_ENV_ONLY &&
+          strcmp(media_type_supported[j], "auto") &&
+         strcmp(media_type_supported[j], "envelope"))
+       continue;
+      else if (media_col_sizes[i][2] == _IPP_PHOTO_ONLY &&
+               strcmp(media_type_supported[j], "auto") &&
+              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 ++;
+
+      if (media_col_sizes[i][2] != _IPP_ENV_ONLY &&
+         (!strcmp(media_type_supported[j], "auto") ||
+          !strncmp(media_type_supported[j], "photographic-", 13)))
+      {
+       /*
+        * 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 ++;
+      }
+    }
+  }
+
+  /* media-col-default */
+  media_col_default = create_media_col(media_supported[0],
+                                       media_type_supported[0],
+                                      media_col_sizes[0][0],
+                                      media_col_sizes[0][1],
+                                      media_xxx_margin_supported[1]);
+
+  ippAddCollection(printer->attrs, IPP_TAG_PRINTER, "media-col-default",
+                   media_col_default);
+  ippDelete(media_col_default);
+
+  /* media-col-supported */
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+                "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,
+               "media-default", NULL, media_supported[0]);
+
+  /* media-left-margin-supported */
+  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                 "media-left-margin-supported",
+                (int)(sizeof(media_xxx_margin_supported) /
+                      sizeof(media_xxx_margin_supported[0])),
+                media_xxx_margin_supported);
+
+  /* media-right-margin-supported */
+  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                 "media-right-margin-supported",
+                (int)(sizeof(media_xxx_margin_supported) /
+                      sizeof(media_xxx_margin_supported[0])),
+                media_xxx_margin_supported);
+
+  /* media-supported */
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+                "media-supported",
+               (int)(sizeof(media_supported) / sizeof(media_supported[0])),
+               NULL, media_supported);
+
+  /* media-top-margin-supported */
+  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                 "media-top-margin-supported",
+                (int)(sizeof(media_xxx_margin_supported) /
+                      sizeof(media_xxx_margin_supported[0])),
+                media_xxx_margin_supported);
+
+  /* multiple-document-handling-supported */
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+                "multiple-document-handling-supported",
+                sizeof(multiple_document_handling) /
+                   sizeof(multiple_document_handling[0]), NULL,
+               multiple_document_handling);
+
+  /* multiple-document-jobs-supported */
+  ippAddBoolean(printer->attrs, IPP_TAG_PRINTER,
+                "multiple-document-jobs-supported", 0);
+
+  /* natural-language-configured */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_LANGUAGE | IPP_TAG_COPY,
+               "natural-language-configured", NULL, "en");
+
+  /* number-up-default */
+  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                "number-up-default", 1);
+
+  /* number-up-supported */
+  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                "number-up-supported", 1);
+
+  /* operations-supported */
+  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
+                "operations-supported", sizeof(ops) / sizeof(ops[0]), ops);
+
+  /* orientation-requested-default */
+  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NOVALUE,
+                "orientation-requested-default", 0);
+
+  /* orientation-requested-supported */
+  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
+                 "orientation-requested-supported", 4, orients);
+
+  /* output-bin-default */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+               "output-bin-default", NULL, "face-down");
+
+  /* output-bin-supported */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+               "output-bin-supported", NULL, "face-down");
+
+  /* pages-per-minute */
+  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                "pages-per-minute", ppm);
+
+  /* pages-per-minute-color */
+  if (ppm_color > 0)
+    ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                  "pages-per-minute-color", ppm_color);
+
+  /* pdl-override-supported */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+               "pdl-override-supported", NULL, "attempted");
+
+  /* print-quality-default */
+  ippAddInteger(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
+                "print-quality-default", IPP_QUALITY_NORMAL);
+
+  /* print-quality-supported */
+  ippAddIntegers(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_ENUM,
+                 "print-quality-supported",
+                (int)(sizeof(print_quality_supported) /
+                      sizeof(print_quality_supported[0])),
+                print_quality_supported);
+
+  /* printer-device-id */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
+              "printer-device-id", NULL, device_id);
+
+  /* printer-icons */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI,
+               "printer-icons", NULL, icons);
+
+  /* printer-is-accepting-jobs */
+  ippAddBoolean(printer->attrs, IPP_TAG_PRINTER, "printer-is-accepting-jobs",
+                1);
+
+  /* printer-info */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT, "printer-info",
+               NULL, name);
+
+  /* printer-location */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
+               "printer-location", NULL, location);
+
+  /* printer-make-and-model */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_TEXT,
+               "printer-make-and-model", NULL, make_model);
+
+  /* printer-more-info */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI,
+               "printer-more-info", NULL, adminurl);
+
+  /* printer-name */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_NAME, "printer-name",
+               NULL, name);
+
+  /* printer-resolution-default */
+  ippAddResolution(printer->attrs, IPP_TAG_PRINTER,
+                   "printer-resolution-default", IPP_RES_PER_INCH, 600, 600);
+
+  /* printer-resolution-supported */
+  ippAddResolution(printer->attrs, IPP_TAG_PRINTER,
+                   "printer-resolution-supported", IPP_RES_PER_INCH, 600, 600);
+
+  /* printer-uri-supported */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_URI,
+               "printer-uri-supported", NULL, uri);
+
+  /* sides-default */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+               "sides-default", NULL, "one-sided");
+
+  /* sides-supported */
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+                "sides-supported", duplex ? 3 : 1, NULL, sides_supported);
+
+  /* uri-authentication-supported */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+               "uri-authentication-supported", NULL, "none");
+
+  /* uri-security-supported */
+  ippAddString(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD,
+               "uri-security-supported", NULL, "none");
+
+  /* which-jobs-supported */
+  ippAddStrings(printer->attrs, IPP_TAG_PRINTER, IPP_TAG_KEYWORD | IPP_TAG_COPY,
+                "which-jobs-supported",
+                sizeof(which_jobs) / sizeof(which_jobs[0]), NULL, which_jobs);
+
+  free(formats[0]);
+
+  debug_attributes("Printer", printer->attrs);
+
+ /*
+  * Register the printer with Bonjour...
+  */
+
+  if (!register_printer(printer, location, make, model, docformats, adminurl,
+                        ppm_color > 0, duplex, regtype))
+    goto bad_printer;
+
+ /*
+  * Return it!
+  */
+
+  return (printer);
+
+
+ /*
+  * If we get here we were unable to create the printer...
+  */
+
+  bad_printer:
+
+  delete_printer(printer);
+  return (NULL);
+}
+
+
+/*
+ * '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_tag_t            group_tag;      /* Current group */
+  ipp_attribute_t      *attr;          /* Current attribute */
+  char                 buffer[2048];   /* String buffer for value */
+
+
+  if (Verbosity <= 1)
+    return;
+
+  fprintf(stderr, "%s:\n", title);
+  for (attr = ipp->attrs, group_tag = IPP_TAG_ZERO; attr; attr = attr->next)
+  {
+    if (attr->group_tag != group_tag)
+    {
+      group_tag = attr->group_tag;
+      fprintf(stderr, "  %s\n", ippTagString(group_tag));
+    }
+
+    if (attr->name)
+    {
+      _ippAttrString(attr, buffer, sizeof(buffer));
+      fprintf(stderr, "    %s (%s%s) %s\n", attr->name,
+             attr->num_values > 1 ? "1setOf " : "",
+             ippTagString(attr->value_tag), buffer);
+    }
+  }
+}
+
+
+/*
+ * 'delete_client()' - Close the socket and free all memory used by a client
+ *                     object.
+ */
+
+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");
+
+ /*
+  * Flush pending writes before closing...
+  */
+
+  httpFlushWrite(&(client->http));
+
+  if (client->http.fd >= 0)
+    close(client->http.fd);
+
+ /*
+  * Free memory...
+  */
+
+  httpClearCookie(&(client->http));
+  httpClearFields(&(client->http));
+
+  ippDelete(client->request);
+
+  ippDelete(client->response);
+
+  free(client);
+}
+
+
+/*
+ * 'delete_job()' - Remove from the printer and free all memory used by a job
+ *                  object.
+ */
+
+static void
+delete_job(_ipp_job_t *job)            /* I - Job */
+{
+  if (Verbosity)
+    fprintf(stderr, "Removing job #%d from history.\n", job->id);
+
+  ippDelete(job->attrs);
+
+  if (job->filename)
+  {
+    if (!KeepFiles)
+      unlink(job->filename);
+
+    free(job->filename);
+  }
+
+  free(job);
+}
+
+
+/*
+ * 'delete_printer()' - Unregister, close listen sockets, and free all memory
+ *                      used by a printer object.
+ */
+
+static void
+delete_printer(_ipp_printer_t *printer)        /* I - Printer */
+{
+  if (printer->ipv4 >= 0)
+    close(printer->ipv4);
+
+  if (printer->ipv6 >= 0)
+    close(printer->ipv6);
+
+  if (printer->printer_ref)
+    DNSServiceRefDeallocate(printer->printer_ref);
+
+  if (printer->ipp_ref)
+    DNSServiceRefDeallocate(printer->ipp_ref);
+
+  if (printer->http_ref)
+    DNSServiceRefDeallocate(printer->http_ref);
+
+  if (printer->common_ref)
+    DNSServiceRefDeallocate(printer->common_ref);
+
+  TXTRecordDeallocate(&(printer->ipp_txt));
+
+  if (printer->name)
+    _cupsStrFree(printer->name);
+  if (printer->dnssd_name)
+    _cupsStrFree(printer->dnssd_name);
+  if (printer->icon)
+    _cupsStrFree(printer->icon);
+  if (printer->directory)
+    _cupsStrFree(printer->directory);
+  if (printer->hostname)
+    _cupsStrFree(printer->hostname);
+  if (printer->uri)
+    _cupsStrFree(printer->uri);
+
+  ippDelete(printer->attrs);
+  cupsArrayDelete(printer->jobs);
+
+  free(printer);
+}
+
+
+/*
+ * 'dnssd_callback()' - Handle Bonjour registration events.
+ */
+
+static void
+dnssd_callback(
+    DNSServiceRef       sdRef,         /* I - Service reference */
+    DNSServiceFlags     flags,         /* I - Status flags */
+    DNSServiceErrorType errorCode,     /* I - Error, if any */
+    const char          *name,         /* I - Service name */
+    const char          *regtype,      /* I - Service type */
+    const char          *domain,       /* I - Domain for service */
+    _ipp_printer_t      *printer)      /* I - Printer */
+{
+  if (errorCode)
+  {
+    fprintf(stderr, "DNSServiceRegister for %s failed with error %d.\n",
+            regtype, (int)errorCode);
+    return;
+  }
+  else if (strcasecmp(name, printer->dnssd_name))
+  {
+    if (Verbosity)
+      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);
+  }
+}
+
+
+/*
+ * 'find_job()' - Find a job specified in a request.
+ */
+
+static _ipp_job_t *                    /* O - Job or NULL */
+find_job(_ipp_client_t *client)                /* I - Client */
+{
+  ipp_attribute_t      *attr;          /* job-id or job-uri attribute */
+  _ipp_job_t           key,            /* Job search key */
+                       *job;           /* Matching job, if any */
+
+
+  key.id = 0;
+
+  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);
+  }
+  else if ((attr = ippFindAttribute(client->request, "job-id",
+                                    IPP_TAG_INTEGER)) != NULL)
+    key.id = attr->values[0].integer;
+
+  _cupsRWLockRead(&(client->printer->rwlock));
+  job = (_ipp_job_t *)cupsArrayFind(client->printer->jobs, &key);
+  _cupsRWUnlock(&(client->printer->rwlock));
+
+  return (job);
+}
+
+
+/*
+ * 'html_escape()' - Write a HTML-safe string.
+ */
+
+static void
+html_escape(_ipp_client_t *client,     /* I - Client */
+           const char    *s,           /* I - String to write */
+           size_t        slen)         /* I - Number of characters to write */
+{
+  const char   *start,                 /* Start of segment */
+               *end;                   /* End of string */
+
+
+  start = s;
+  end   = s + (slen > 0 ? slen : strlen(s));
+
+  while (*s && s < end)
+  {
+    if (*s == '&' || *s == '<')
+    {
+      if (s > start)
+        httpWrite2(&(client->http), start, s - start);
+
+      if (*s == '&')
+        httpWrite2(&(client->http), "&amp;", 5);
+      else
+        httpWrite2(&(client->http), "&lt;", 4);
+
+      start = s + 1;
+    }
+
+    s ++;
+  }
+
+  if (s > start)
+    httpWrite2(&(client->http), start, s - start);
+}
+
+
+/*
+ * 'html_printf()' - Send formatted text to the client, quoting as needed.
+ */
+
+static void
+html_printf(_ipp_client_t *client,     /* I - Client */
+           const char    *format,      /* I - Printf-style format string */
+           ...)                        /* I - Additional arguments as needed */
+{
+  va_list      ap;                     /* Pointer to arguments */
+  const char   *start;                 /* Start of string */
+  char         size,                   /* Size character (h, l, L) */
+               type;                   /* Format type character */
+  int          width,                  /* Width of field */
+               prec;                   /* Number of characters of precision */
+  char         tformat[100],           /* Temporary format string for sprintf() */
+               *tptr,                  /* Pointer into temporary format */
+               temp[1024];             /* Buffer for formatted numbers */
+  char         *s;                     /* Pointer to string */
+
+
+ /*
+  * Loop through the format string, formatting as needed...
+  */
+
+  va_start(ap, format);
+  start = format;
+
+  while (*format)
+  {
+    if (*format == '%')
+    {
+      if (format > start)
+        httpWrite2(&(client->http), start, format - start);
+
+      tptr    = tformat;
+      *tptr++ = *format++;
+
+      if (*format == '%')
+      {
+        httpWrite2(&(client->http), "%", 1);
+        format ++;
+       continue;
+      }
+      else if (strchr(" -+#\'", *format))
+        *tptr++ = *format++;
+
+      if (*format == '*')
+      {
+       /*
+        * Get width from argument...
+       */
+
+       format ++;
+       width = va_arg(ap, int);
+
+       snprintf(tptr, sizeof(tformat) - (tptr - tformat), "%d", width);
+       tptr += strlen(tptr);
+      }
+      else
+      {
+       width = 0;
+
+       while (isdigit(*format & 255))
+       {
+         if (tptr < (tformat + sizeof(tformat) - 1))
+           *tptr++ = *format;
+
+         width = width * 10 + *format++ - '0';
+       }
+      }
+
+      if (*format == '.')
+      {
+       if (tptr < (tformat + sizeof(tformat) - 1))
+         *tptr++ = *format;
+
+        format ++;
+
+        if (*format == '*')
+       {
+         /*
+         * Get precision from argument...
+         */
+
+         format ++;
+         prec = va_arg(ap, int);
+
+         snprintf(tptr, sizeof(tformat) - (tptr - tformat), "%d", prec);
+         tptr += strlen(tptr);
+       }
+       else
+       {
+         prec = 0;
+
+         while (isdigit(*format & 255))
+         {
+           if (tptr < (tformat + sizeof(tformat) - 1))
+             *tptr++ = *format;
+
+           prec = prec * 10 + *format++ - '0';
+         }
+       }
+      }
+
+      if (*format == 'l' && format[1] == 'l')
+      {
+        size = 'L';
+
+       if (tptr < (tformat + sizeof(tformat) - 2))
+       {
+         *tptr++ = 'l';
+         *tptr++ = 'l';
+       }
+
+       format += 2;
+      }
+      else if (*format == 'h' || *format == 'l' || *format == 'L')
+      {
+       if (tptr < (tformat + sizeof(tformat) - 1))
+         *tptr++ = *format;
+
+        size = *format++;
+      }
+      else
+        size = 0;
+
+
+      if (!*format)
+      {
+        start = format;
+        break;
+      }
+
+      if (tptr < (tformat + sizeof(tformat) - 1))
+        *tptr++ = *format;
+
+      type  = *format++;
+      *tptr = '\0';
+      start = format;
+
+      switch (type)
+      {
+       case 'E' : /* Floating point formats */
+       case 'G' :
+       case 'e' :
+       case 'f' :
+       case 'g' :
+           if ((width + 2) > sizeof(temp))
+             break;
+
+           sprintf(temp, tformat, va_arg(ap, double));
+
+            httpWrite2(&(client->http), temp, strlen(temp));
+           break;
+
+        case 'B' : /* Integer formats */
+       case 'X' :
+       case 'b' :
+        case 'd' :
+       case 'i' :
+       case 'o' :
+       case 'u' :
+       case 'x' :
+           if ((width + 2) > sizeof(temp))
+             break;
+
+#  ifdef HAVE_LONG_LONG
+            if (size == 'L')
+             sprintf(temp, tformat, va_arg(ap, long long));
+           else
+#  endif /* HAVE_LONG_LONG */
+            if (size == 'l')
+             sprintf(temp, tformat, va_arg(ap, long));
+           else
+             sprintf(temp, tformat, va_arg(ap, int));
+
+            httpWrite2(&(client->http), temp, strlen(temp));
+           break;
+
+       case 'p' : /* Pointer value */
+           if ((width + 2) > sizeof(temp))
+             break;
+
+           sprintf(temp, tformat, va_arg(ap, void *));
+
+            httpWrite2(&(client->http), temp, strlen(temp));
+           break;
+
+        case 'c' : /* Character or character array */
+            if (width <= 1)
+            {
+              temp[0] = va_arg(ap, int);
+              temp[1] = '\0';
+              html_escape(client, temp, 1);
+            }
+            else
+              html_escape(client, va_arg(ap, char *), (size_t)width);
+           break;
+
+       case 's' : /* String */
+           if ((s = va_arg(ap, char *)) == NULL)
+             s = "(null)";
+
+            html_escape(client, s, strlen(s));
+           break;
+      }
+    }
+    else
+      format ++;
+  }
+
+  if (format > start)
+    httpWrite2(&(client->http), start, format - start);
+
+  va_end(ap);
+}
+
+
+/*
+ * 'ipp_cancel_job()' - Cancel a job.
+ */
+
+static void
+ipp_cancel_job(_ipp_client_t *client)  /* I - Client */
+{
+  _ipp_job_t           *job;           /* Job information */
+
+
+ /*
+  * Get the job...
+  */
+
+  if ((job = find_job(client)) == NULL)
+    return;
+
+ /*
+  * See if the job is already completed, canceled, or aborted; if so,
+  * we can't cancel...
+  */
+
+  switch (job->state)
+  {
+    case IPP_JOB_CANCELED :
+       respond_ipp(client, IPP_NOT_POSSIBLE,
+                   "Job #%d is already canceled - can\'t cancel.", job->id);
+        break;
+
+    case IPP_JOB_ABORTED :
+       respond_ipp(client, IPP_NOT_POSSIBLE,
+                   "Job #%d is already aborted - can\'t cancel.", job->id);
+        break;
+
+    case IPP_JOB_COMPLETED :
+       respond_ipp(client, IPP_NOT_POSSIBLE,
+                   "Job #%d is already completed - can\'t cancel.", job->id);
+        break;
+
+    default :
+       /*
+        * Cancel the job...
+       */
+
+       _cupsRWLockWrite(&(client->printer->rwlock));
+
+       if (job->state == IPP_JOB_PROCESSING ||
+           (job->state == IPP_JOB_HELD && job->fd >= 0))
+          job->cancel = 1;
+       else
+       {
+         job->state     = IPP_JOB_CANCELED;
+         job->completed = time(NULL);
+       }
+
+       _cupsRWUnlock(&(client->printer->rwlock));
+
+       respond_ipp(client, IPP_OK, NULL);
+        break;
+  }
+}
+
+
+#if 0
+/*
+ * 'ipp_create_job()' - Create a job object.
+ */
+
+static void
+ipp_create_job(_ipp_client_t *client)  /* I - Client */
+{
+}
+#endif /* 0 */
+
+
+/*
+ * '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_NOT_FOUND, "Job not found.");
+    return;
+  }
+
+  respond_ipp(client, IPP_OK, NULL);
+
+  ra = create_requested_array(client);
+  copy_job_attributes(client, job, ra);
+  cupsArrayDelete(ra);
+}
+
+
+/*
+ * 'ipp_get_jobs()' - Get a list of job objects.
+ */
+
+static void
+ipp_get_jobs(_ipp_client_t *client)    /* I - Client */
+{
+  ipp_attribute_t      *attr;          /* Current attribute */
+  int                  job_comparison; /* Job comparison */
+  ipp_jstate_t         job_state;      /* job-state value */
+  int                  first_job_id,   /* First job ID */
+                       limit,          /* Maximum number of jobs to return */
+                       count;          /* Number of jobs that match */
+  const char           *username;      /* Username */
+  _ipp_job_t           *job;           /* Current job pointer */
+  cups_array_t         *ra;            /* Requested attributes array */
+
+
+ /*
+  * See if the "which-jobs" attribute have been specified...
+  */
+
+  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);
+
+  if (!attr || !strcmp(attr->values[0].string.text, "not-completed"))
+  {
+    job_comparison = -1;
+    job_state      = IPP_JOB_STOPPED;
+  }
+  else if (!strcmp(attr->values[0].string.text, "completed"))
+  {
+    job_comparison = 1;
+    job_state      = IPP_JOB_CANCELED;
+  }
+  else if (!strcmp(attr->values[0].string.text, "aborted"))
+  {
+    job_comparison = 0;
+    job_state      = IPP_JOB_ABORTED;
+  }
+  else if (!strcmp(attr->values[0].string.text, "all"))
+  {
+    job_comparison = 1;
+    job_state      = IPP_JOB_PENDING;
+  }
+  else if (!strcmp(attr->values[0].string.text, "canceled"))
+  {
+    job_comparison = 0;
+    job_state      = IPP_JOB_CANCELED;
+  }
+  else if (!strcmp(attr->values[0].string.text, "pending"))
+  {
+    job_comparison = 0;
+    job_state      = IPP_JOB_PENDING;
+  }
+  else if (!strcmp(attr->values[0].string.text, "pending-held"))
+  {
+    job_comparison = 0;
+    job_state      = IPP_JOB_HELD;
+  }
+  else if (!strcmp(attr->values[0].string.text, "processing"))
+  {
+    job_comparison = 0;
+    job_state      = IPP_JOB_PROCESSING;
+  }
+  else if (!strcmp(attr->values[0].string.text, "processing-stopped"))
+  {
+    job_comparison = 0;
+    job_state      = IPP_JOB_STOPPED;
+  }
+  else
+  {
+    respond_ipp(client, IPP_ATTRIBUTES,
+                "The which-jobs value \"%s\" is not supported.",
+                attr->values[0].string.text);
+    ippAddString(client->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD,
+                 "which-jobs", NULL, attr->values[0].string.text);
+    return;
+  }
+
+ /*
+  * See if they want to limit the number of jobs reported...
+  */
+
+  if ((attr = ippFindAttribute(client->request, "limit",
+                               IPP_TAG_INTEGER)) != NULL)
+  {
+    limit = attr->values[0].integer;
+
+    fprintf(stderr, "%s Get-Jobs limit=%d", client->http.hostname, limit);
+  }
+  else
+    limit = 0;
+
+  if ((attr = ippFindAttribute(client->request, "first-job-id",
+                               IPP_TAG_INTEGER)) != NULL)
+  {
+    first_job_id = attr->values[0].integer;
+
+    fprintf(stderr, "%s Get-Jobs first-job-id=%d", client->http.hostname,
+            first_job_id);
+  }
+  else
+    first_job_id = 1;
+
+ /*
+  * See if we only want to see jobs for a specific user...
+  */
+
+  username = NULL;
+
+  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");
+
+    if (attr->values[0].boolean)
+    {
+      if ((attr = ippFindAttribute(client->request, "requesting-user-name",
+                                       IPP_TAG_NAME)) == NULL)
+      {
+       respond_ipp(client, IPP_BAD_REQUEST,
+                   "Need requesting-user-name with my-jobs.");
+       return;
+      }
+
+      username = attr->values[0].string.text;
+
+      fprintf(stderr, "%s Get-Jobs requesting-user-name=\"%s\"\n",
+              client->http.hostname, username);
+    }
+  }
+
+ /*
+  * 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");
+  }
+
+  respond_ipp(client, IPP_OK, NULL);
+
+  _cupsRWLockRead(&(client->printer->rwlock));
+
+  for (count = 0, job = (_ipp_job_t *)cupsArrayFirst(client->printer->jobs);
+       (limit <= 0 || count < limit) && job;
+       job = (_ipp_job_t *)cupsArrayNext(client->printer->jobs))
+  {
+   /*
+    * Filter out jobs that don't match...
+    */
+
+    if ((job_comparison < 0 && job->state > job_state) ||
+       (job_comparison == 0 && job->state != job_state) ||
+       (job_comparison > 0 && job->state < job_state) ||
+       job->id < first_job_id ||
+       (username && job->username && strcasecmp(username, job->username)))
+      continue;
+
+    if (count > 0)
+      ippAddSeparator(client->response);
+
+    count ++;
+    copy_job_attributes(client, job, ra);
+  }
+
+  cupsArrayDelete(ra);
+
+  _cupsRWUnlock(&(client->printer->rwlock));
+}
+
+
+/*
+ * 'ipp_get_printer_attributes()' - Get the attributes for a printer object.
+ */
+
+static void
+ipp_get_printer_attributes(
+    _ipp_client_t *client)             /* I - Client */
+{
+  cups_array_t         *ra;            /* Requested attributes array */
+  _ipp_printer_t       *printer;       /* Printer */
+
+
+ /*
+  * Send the attributes...
+  */
+
+  ra      = create_requested_array(client);
+  printer = client->printer;
+
+  respond_ipp(client, IPP_OK, NULL);
+
+  _cupsRWLockRead(&(printer->rwlock));
+
+  copy_attributes(client->response, printer->attrs, ra, IPP_TAG_ZERO,
+                 IPP_TAG_COPY);
+
+  if (!ra || cupsArrayFind(ra, "printer-state"))
+    ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_ENUM,
+                  "printer-state", printer->state);
+
+  if (!ra || cupsArrayFind(ra, "printer-state-reasons"))
+  {
+    if (printer->state_reasons == _IPP_PRINTER_NONE)
+      ippAddString(client->response, IPP_TAG_PRINTER, 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)
+       reasons[num_reasons ++] = "other";
+      if (printer->state_reasons & _IPP_PRINTER_COVER_OPEN)
+       reasons[num_reasons ++] = "cover-open";
+      if (printer->state_reasons & _IPP_PRINTER_INPUT_TRAY_MISSING)
+       reasons[num_reasons ++] = "input-tray-missing";
+      if (printer->state_reasons & _IPP_PRINTER_MARKER_SUPPLY_EMPTY)
+       reasons[num_reasons ++] = "marker-supply-empty-warning";
+      if (printer->state_reasons & _IPP_PRINTER_MARKER_SUPPLY_LOW)
+       reasons[num_reasons ++] = "marker-supply-low-report";
+      if (printer->state_reasons & _IPP_PRINTER_MARKER_WASTE_ALMOST_FULL)
+       reasons[num_reasons ++] = "marker-waste-almost-full-report";
+      if (printer->state_reasons & _IPP_PRINTER_MARKER_WASTE_FULL)
+       reasons[num_reasons ++] = "marker-waste-full-warning";
+      if (printer->state_reasons & _IPP_PRINTER_MEDIA_EMPTY)
+       reasons[num_reasons ++] = "media-empty-warning";
+      if (printer->state_reasons & _IPP_PRINTER_MEDIA_JAM)
+       reasons[num_reasons ++] = "media-jam-warning";
+      if (printer->state_reasons & _IPP_PRINTER_MEDIA_LOW)
+       reasons[num_reasons ++] = "media-low-report";
+      if (printer->state_reasons & _IPP_PRINTER_MEDIA_NEEDED)
+       reasons[num_reasons ++] = "media-needed-report";
+      if (printer->state_reasons & _IPP_PRINTER_MOVING_TO_PAUSED)
+       reasons[num_reasons ++] = "moving-to-paused";
+      if (printer->state_reasons & _IPP_PRINTER_PAUSED)
+       reasons[num_reasons ++] = "paused";
+      if (printer->state_reasons & _IPP_PRINTER_SPOOL_AREA_FULL)
+       reasons[num_reasons ++] = "spool-area-full";
+      if (printer->state_reasons & _IPP_PRINTER_TONER_EMPTY)
+       reasons[num_reasons ++] = "toner-empty-warning";
+      if (printer->state_reasons & _IPP_PRINTER_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);
+    }
+  }
+
+  if (!ra || cupsArrayFind(ra, "printer-up-time"))
+    ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                  "printer-up-time", (int)time(NULL));
+
+  if (!ra || cupsArrayFind(ra, "queued-job-count"))
+    ippAddInteger(client->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER,
+                  "queued-job-count",
+                 printer->active_job &&
+                     printer->active_job->state < IPP_JOB_CANCELED);
+
+  _cupsRWUnlock(&(printer->rwlock));
+
+  cupsArrayDelete(ra);
+}
+
+
+/*
+ * 'ipp_print_job()' - Create a job object with an attached document.
+ */
+
+static void
+ipp_print_job(_ipp_client_t *client)   /* I - Client */
+{
+  _ipp_job_t           *job;           /* New job */
+  char                 filename[1024], /* Filename buffer */
+                       buffer[4096];   /* Copy buffer */
+  ssize_t              bytes;          /* Bytes read */
+  cups_array_t         *ra;            /* Attributes to send in response */
+
+
+ /*
+  * Validate print job attributes...
+  */
+
+  if (!valid_job_attributes(client))
+  {
+    httpFlush(&(client->http));
+    return;
+  }
+
+ /*
+  * Do we have a file to print?
+  */
+
+  if (client->http.state == HTTP_POST_SEND)
+  {
+    respond_ipp(client, IPP_BAD_REQUEST, "No file in request.");
+    return;
+  }
+
+ /*
+  * Print the job...
+  */
+
+  if ((job = create_job(client)) == NULL)
+  {
+    respond_ipp(client, IPP_PRINTER_BUSY, "Currently printing another job.");
+    return;
+  }
+
+ /*
+  * Create a file for the request data...
+  */
+
+  if (!strcasecmp(job->format, "image/jpeg"))
+    snprintf(filename, sizeof(filename), "%s/%d.jpg",
+             client->printer->directory, job->id);
+  else if (!strcasecmp(job->format, "image/png"))
+    snprintf(filename, sizeof(filename), "%s/%d.png",
+             client->printer->directory, job->id);
+  else if (!strcasecmp(job->format, "application/pdf"))
+    snprintf(filename, sizeof(filename), "%s/%d.pdf", 
+             client->printer->directory, job->id);
+  else if (!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_JOB_ABORTED;
+
+    respond_ipp(client, IPP_INTERNAL_ERROR,
+                "Unable to create print file: %s", strerror(errno));
+    return;
+  }
+
+  while ((bytes = httpRead2(&(client->http), buffer, sizeof(buffer))) > 0)
+  {
+    if (write(job->fd, buffer, bytes) < bytes)
+    {
+      int error = errno;               /* Write error */
+
+      job->state = IPP_JOB_ABORTED;
+
+      close(job->fd);
+      job->fd = -1;
+
+      unlink(filename);
+
+      respond_ipp(client, IPP_INTERNAL_ERROR,
+                  "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_JOB_ABORTED;
+
+    close(job->fd);
+    job->fd = -1;
+
+    unlink(filename);
+
+    respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to read print file.");
+    return;
+  }
+
+  if (close(job->fd))
+  {
+    int error = errno;         /* Write error */
+
+    job->state = IPP_JOB_ABORTED;
+    job->fd    = -1;
+
+    unlink(filename);
+
+    respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to write print file: %s",
+                strerror(error));
+    return;
+  }
+
+  job->fd       = -1;
+  job->filename = strdup(filename);
+  job->state    = IPP_JOB_PENDING;
+
+ /*
+  * Process the job...
+  */
+
+  if (!_cupsThreadCreate((_cups_thread_func_t)process_job, job))
+  {
+    job->state = IPP_JOB_ABORTED;
+    respond_ipp(client, IPP_INTERNAL_ERROR, "Unable to process job.");
+    return;
+  }
+
+ /*
+  * Return the job info...
+  */
+
+  respond_ipp(client, IPP_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);
+}
+
+
+#if 0
+/*
+ * 'ipp_send_document()' - Add an attached document to a job object created with
+ *                         Create-Job.
+ */
+
+static void
+ipp_send_document(_ipp_client_t *client)/* I - Client */
+{
+}
+#endif /* 0 */
+
+
+/*
+ * 'ipp_validate_job()' - Validate job creation attributes.
+ */
+
+static void
+ipp_validate_job(_ipp_client_t *client)        /* I - Client */
+{
+}
+
+
+/*
+ * '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))
+    if (!process_http(client))
+      break;
+
+ /*
+  * Close the conection to the client and return...
+  */
+
+  delete_client(client);
+
+  return (NULL);
+}
+
+
+/*
+ * 'process_http()' - Process a HTTP request.
+ */
+
+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...
+  */
+
+  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;
+
+ /*
+  * Read a request from the connection...
+  */
+
+  while ((ptr = httpGets(line, sizeof(line) - 1, &(client->http))) != NULL)
+    if (*ptr)
+      break;
+
+  if (!ptr)
+    return (0);
+
+ /*
+  * Parse the request line...
+  */
+
+  fprintf(stderr, "%s %s\n", client->http.hostname, line);
+
+  switch (sscanf(line, "%63s%1023s%63s", operation, uri, version))
+  {
+    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 (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;
+  }
+
+ /*
+  * Handle full URLs in the request line...
+  */
+
+  if (!strncmp(client->uri, "http:", 5) || !strncmp(client->uri, "ipp:", 4))
+  {
+    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);
+    }
+  }
+  else
+  {
+   /*
+    * 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);
+    }
+  }
+
+ /*
+  * Process the request...
+  */
+
+  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
+  {
+    fprintf(stderr, "%s Bad operation \"%s\".\n", client->http.hostname,
+            operation);
+    respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
+    return (0);
+  }
+
+  client->start       = time(NULL);
+  client->operation   = client->http.state;
+  client->http.status = HTTP_OK;
+
+ /*
+  * Parse incoming parameters until the status changes...
+  */
+
+  while ((status = httpUpdate(&(client->http))) == HTTP_CONTINUE);
+
+  if (status != HTTP_OK)
+  {
+    respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
+    return (0);
+  }
+
+  if (!client->http.fields[HTTP_FIELD_HOST][0] &&
+      client->http.version >= HTTP_1_1)
+  {
+   /*
+    * HTTP/1.1 and higher require the "Host:" field...
+    */
+
+    respond_http(client, HTTP_BAD_REQUEST, NULL, 0);
+    return (0);
+  }
+
+ /*
+  * Handle HTTP Upgrade...
+  */
+
+  if (!strcasecmp(client->http.fields[HTTP_FIELD_CONNECTION], "Upgrade"))
+  {
+    if (!respond_http(client, HTTP_NOT_IMPLEMENTED, NULL, 0))
+      return (0);
+  }
+
+ /*
+  * Handle new transfers...
+  */
+
+  switch (client->operation)
+  {
+    case HTTP_OPTIONS :
+       /*
+       * Do HEAD/OPTIONS command...
+       */
+
+       return (respond_http(client, HTTP_OK, NULL, 0));
+
+    case HTTP_HEAD :
+        if (!strcmp(client->uri, "/icon.png"))
+         return (respond_http(client, HTTP_OK, "image/png", 0));
+       else if (!strcmp(client->uri, "/"))
+         return (respond_http(client, HTTP_OK, "text/html", 0));
+       else
+         return (respond_http(client, HTTP_NOT_FOUND, NULL, 0));
+       break;
+
+    case HTTP_GET :
+        if (!strcmp(client->uri, "/icon.png"))
+       {
+        /*
+         * Send PNG icon file.
+         */
+
+          int          fd;             /* Icon file */
+         struct stat   fileinfo;       /* Icon file information */
+         char          buffer[4096];   /* Copy buffer */
+         ssize_t       bytes;          /* Bytes */
+
+          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))
+           {
+             close(fd);
+             return (0);
+           }
+
+           while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
+             httpWrite2(&(client->http), buffer, bytes);
+
+           httpFlushWrite(&(client->http));
+
+           close(fd);
+         }
+         else
+           return (respond_http(client, HTTP_NOT_FOUND, NULL, 0));
+       }
+       else if (!strcmp(client->uri, "/"))
+       {
+        /*
+         * Show web status page...
+         */
+
+          if (!respond_http(client, HTTP_OK, "text/html", 0))
+           return (0);
+
+          html_printf(client,
+                     "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" "
+                     "\"http://www.w3.org/TR/html4/strict.dtd\">\n"
+                     "<html>\n"
+                     "<head>\n"
+                     "<title>%s</title>\n"
+                     "<link rel=\"SHORTCUT ICON\" href=\"/icon.png\" "
+                     "type=\"image/png\">\n"
+                     "</head>\n"
+                     "<body>\n"
+                     "</body>\n"
+                     "<h1>%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 ?
+                         "Printing" : "Stopped",
+                     cupsArrayCount(client->printer->jobs));
+          httpWrite2(&(client->http), "", 0);
+
+         return (1);
+       }
+       else
+         return (respond_http(client, HTTP_NOT_FOUND, 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],
+                  "application/ipp"))
+        {
+        /*
+         * Not an IPP request...
+         */
+
+         return (respond_http(client, HTTP_BAD_REQUEST, NULL, 0));
+       }
+
+       /*
+        * Read the IPP request...
+       */
+
+       client->request = ippNew();
+
+        while ((state = ippRead(&(client->http), client->request)) != IPP_DATA)
+         if (state == IPP_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);
+           return (0);
+         }
+
+       /*
+        * Now that we have the IPP request, process the request...
+       */
+
+        return (process_ipp(client));
+
+    default :
+        break; /* Anti-compiler-warning-code */
+  }
+
+  return (1);
+}
+
+
+/*
+ * 'process_ipp()' - Process an IPP request.
+ */
+
+static int                             /* O - 1 on success, 0 on error */
+process_ipp(_ipp_client_t *client)     /* I - Client */
+{
+  ipp_tag_t            group;          /* Current group tag */
+  ipp_attribute_t      *attr;          /* Current attribute */
+  ipp_attribute_t      *charset;       /* Character set attribute */
+  ipp_attribute_t      *language;      /* Language attribute */
+  ipp_attribute_t      *uri;           /* Printer URI attribute */
+
+
+  debug_attributes("Request", client->request);
+
+ /*
+  * 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;
+
+ /*
+  * Then validate the request header and required attributes...
+  */
+
+  if (client->request->request.any.version[0] < 1 ||
+      client->request->request.any.version[0] > 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]);
+  }
+  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
+  {
+   /*
+    * Make sure that the attributes are provided in the correct order and
+    * don't repeat groups...
+    */
+
+    for (attr = client->request->attrs, group = attr->group_tag;
+        attr;
+        attr = attr->next)
+      if (attr->group_tag < group && attr->group_tag != 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);
+       break;
+      }
+      else
+       group = attr->group_tag;
+
+    if (!attr)
+    {
+     /*
+      * Then make sure that the first three attributes are:
+      *
+      *     attributes-charset
+      *     attributes-natural-language
+      *     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)
+       charset = attr;
+      else
+       charset = NULL;
+
+      if (attr)
+        attr = attr->next;
+
+      if (attr && attr->name &&
+          !strcmp(attr->name, "attributes-natural-language") &&
+         (attr->value_tag & IPP_TAG_MASK) == IPP_TAG_LANGUAGE)
+       language = attr;
+      else
+       language = NULL;
+
+      if ((attr = ippFindAttribute(client->request, "printer-uri",
+                                   IPP_TAG_URI)) != NULL)
+       uri = attr;
+      else if ((attr = ippFindAttribute(client->request, "job-uri",
+                                        IPP_TAG_URI)) != NULL)
+       uri = attr;
+      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 &&
+          strcasecmp(charset->values[0].string.text, "us-ascii") &&
+          strcasecmp(charset->values[0].string.text, "utf-8"))
+      {
+       /*
+        * Bad character set...
+       */
+
+       respond_ipp(client, IPP_BAD_REQUEST,
+                   "Unsupported character set \"%s\".",
+                   charset->values[0].string.text);
+      }
+      else if (!charset || !language || !uri)
+      {
+       /*
+       * Return an error, since attributes-charset,
+       * attributes-natural-language, and printer-uri/job-uri are required
+       * 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);
+      }
+      else
+      {
+       /*
+        * Try processing the operation...
+       */
+
+        if (client->http.expect == HTTP_CONTINUE)
+       {
+        /*
+         * Send 100-continue header...
+         */
+
+         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;
+       }
+      }
+    }
+  }
+
+ /*
+  * Send the HTTP header and return...
+  */
+
+  if (client->http.state != HTTP_POST_SEND)
+    httpFlush(&(client->http));                /* Flush trailing (junk) data */
+
+  return (respond_http(client, HTTP_OK, "application/ipp",
+                       ippLength(client->response)));
+}
+
+
+/*
+ * 'process_job()' - Process a print job.
+ */
+
+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;
+
+  sleep(5);
+
+  if (job->cancel)
+    job->state = IPP_JOB_CANCELED;
+  else
+    job->state = IPP_JOB_COMPLETED;
+
+  job->completed           = time(NULL);
+  job->printer->state      = IPP_PRINTER_IDLE;
+  job->printer->active_job = NULL;
+
+  return (NULL);
+}
+
+
+/*
+ * 'register_printer()' - Register a printer object via Bonjour.
+ */
+
+static int                             /* O - 1 on success, 0 on error */
+register_printer(
+    _ipp_printer_t *printer,           /* I - Printer */
+    const char     *location,          /* I - Location */
+    const char     *make,              /* I - Manufacturer */
+    const char     *model,             /* I - Model name */
+    const char     *formats,           /* I - Supported formats */
+    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 */
+{
+  DNSServiceErrorType  error;          /* Error from Bonjour */
+  char                 make_model[256],/* Make and model together */
+                       product[256];   /* Product string */
+
+
+ /*
+  * Build the TXT record for IPP...
+  */
+
+  snprintf(make_model, sizeof(make_model), "%s %s", make, model);
+  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), "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");
+  TXTRecordSetValue(&(printer->ipp_txt), "product", (uint8_t)strlen(product),
+                    product);
+  TXTRecordSetValue(&(printer->ipp_txt), "pdl", (uint8_t)strlen(formats),
+                    formats);
+  TXTRecordSetValue(&(printer->ipp_txt), "Color", 1, color ? "T" : "F");
+  TXTRecordSetValue(&(printer->ipp_txt), "Duplex", 1, duplex ? "T" : "F");
+  TXTRecordSetValue(&(printer->ipp_txt), "usb_MFG", (uint8_t)strlen(make),
+                    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...
+  */
+
+  if ((error = DNSServiceCreateConnection(&(printer->common_ref)))
+          != kDNSServiceErr_NoError)
+  {
+    fprintf(stderr, "Unable to create mDNSResponder connection: %d\n", error);
+    return (0);
+  }
+
+ /*
+  * Register the _printer._tcp (LPD) service type with a port number of 0 to
+  * defend our service name but not actually support LPD...
+  */
+
+  printer->printer_ref = printer->common_ref;
+
+  if ((error = DNSServiceRegister(&(printer->printer_ref),
+                                  kDNSServiceFlagsShareConnection,
+                                  0 /* interfaceIndex */, printer->dnssd_name,
+                                 "_printer._tcp", NULL /* domain */,
+                                 NULL /* host */, 0 /* port */, 0 /* txtLen */,
+                                 NULL /* txtRecord */,
+                                 (DNSServiceRegisterReply)dnssd_callback,
+                                 printer)) != kDNSServiceErr_NoError)
+  {
+    fprintf(stderr, "Unable to register \"%s._printer._tcp\": %d\n",
+            printer->dnssd_name, error);
+    return (0);
+  }
+
+ /*
+  * Then register the _ipp._tcp (IPP) service type with the real port number to
+  * advertise our IPP printer...
+  */
+
+  printer->ipp_ref = printer->common_ref;
+
+  if ((error = DNSServiceRegister(&(printer->ipp_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);
+  }
+
+ /*
+  * Similarly, register the _http._tcp,_printer (HTTP) service type with the
+  * real port number to advertise our IPP printer...
+  */
+
+  printer->http_ref = printer->common_ref;
+
+  if ((error = DNSServiceRegister(&(printer->http_ref),
+                                  kDNSServiceFlagsShareConnection,
+                                  0 /* interfaceIndex */, printer->dnssd_name,
+                                 "_http._tcp,_printer", NULL /* domain */,
+                                 NULL /* host */, htons(printer->port),
+                                 0 /* txtLen */, NULL, /* txtRecord */
+                                 (DNSServiceRegisterReply)dnssd_callback,
+                                 printer)) != kDNSServiceErr_NoError)
+  {
+    fprintf(stderr, "Unable to register \"%s.%s\": %d\n",
+            printer->dnssd_name, regtype, error);
+    return (0);
+  }
+
+  return (1);
+}
+
+
+/*
+ * 'respond_http()' - Send a HTTP response.
+ */
+
+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 */
+{
+  char message[1024];                  /* Text message */
+
+
+  fprintf(stderr, "%s %s\n", client->http.hostname, httpStatus(code));
+
+  if (code == HTTP_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);
+  }
+
+ /*
+  * Format an error message...
+  */
+
+  if (!type && !length && code != HTTP_OK)
+  {
+    snprintf(message, sizeof(message), "%d - %s\n", code, httpStatus(code));
+
+    type   = "text/plain";
+    length = strlen(message);
+  }
+  else
+    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...
+  */
+
+  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);
+  }
+
+  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 (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);
+  }
+
+  if (length == 0 && !message[0])
+  {
+    if (httpPrintf(&(client->http), "Transfer-Encoding: chunked\r\n\r\n") < 0)
+      return (0);
+  }
+  else if (httpPrintf(&(client->http), "Content-Length: " CUPS_LLFMT "\r\n\r\n",
+                      CUPS_LLCAST length) < 0)
+    return (0);
+
+  if (httpFlushWrite(&(client->http)) < 0)
+    return (0);
+
+ /*
+  * Send the response data...
+  */
+
+  if (message[0])
+  {
+   /*
+    * Send a plain text message.
+    */
+
+    if (httpPrintf(&(client->http), "%s", message) < 0)
+      return (0);
+  }
+  else if (client->response)
+  {
+   /*
+    * Send an IPP response...
+    */
+
+    debug_attributes("Response", client->response);
+
+    client->http.data_encoding  = HTTP_ENCODE_LENGTH;
+    client->http.data_remaining = (off_t)ippLength(client->response);
+    client->response->state     = IPP_IDLE;
+
+    if (ippWrite(&(client->http), client->response) != IPP_DATA)
+      return (0);
+  }
+  else
+    client->http.data_encoding = HTTP_ENCODE_CHUNKED;
+
+ /*
+  * Flush the data and return...
+  */
+
+  return (httpFlushWrite(&(client->http)) >= 0);
+}
+
+
+/*
+ * 'respond_ipp()' - Send an IPP response.
+ */
+
+static void
+respond_ipp(_ipp_client_t *client,     /* I - Client */
+            ipp_status_t  status,      /* I - status-code */
+           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 */
+
+
+  client->response->request.status.status_code = status;
+
+  if (!client->response->attrs)
+  {
+    ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+                 "attributes-charset", NULL, "utf-8");
+    ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+                 "attributes-natural-language", NULL, "en-US");
+  }
+
+  if (message)
+  {
+    va_start(ap, message);
+    vsnprintf(formatted, sizeof(formatted), message, ap);
+    va_end(ap);
+
+    ippAddString(client->response, IPP_TAG_OPERATION, IPP_TAG_TEXT,
+                "status-message", NULL, formatted);
+  }
+  else
+    formatted[0] = '\0';
+
+  fprintf(stderr, "%s %s %s (%s)\n", client->http.hostname,
+          ippOpString(client->operation_id), ippErrorString(status), formatted);
+}
+
+
+/*
+ * 'run_printer()' - Run the printer service.
+ */
+
+static void
+run_printer(_ipp_printer_t *printer)   /* I - Printer */
+{
+  struct pollfd        polldata[3];            /* poll() data */
+  int          timeout;                /* Timeout for poll() */
+  _ipp_client_t        *client;                /* New client */
+
+
+ /*
+  * Setup poll() data for the Bonjour service socket and IPv4/6 listeners...
+  */
+
+  polldata[0].fd     = printer->ipv4;
+  polldata[0].events = POLLIN;
+
+  polldata[1].fd     = printer->ipv6;
+  polldata[1].events = POLLIN;
+
+  polldata[2].fd     = DNSServiceRefSockFD(printer->common_ref);
+  polldata[2].events = POLLIN;
+
+ /*
+  * Loop until we are killed or have a hard error...
+  */
+
+  for (;;)
+  {
+    if (cupsArrayCount(printer->jobs))
+      timeout = 10;
+    else
+      timeout = -1;
+
+    if (poll(polldata, (int)(sizeof(polldata) / sizeof(polldata[0])),
+             timeout) < 0 && errno != EINTR)
+    {
+      perror("poll() failed");
+      break;
+    }
+
+    if (polldata[0].revents & POLLIN)
+    {
+      if ((client = create_client(printer, printer->ipv4)) != NULL)
+      {
+       if (!_cupsThreadCreate((_cups_thread_func_t)process_client, client))
+       {
+         perror("Unable to create client thread");
+         delete_client(client);
+       }
+      }
+    }
+
+    if (polldata[1].revents & POLLIN)
+    {
+      if ((client = create_client(printer, printer->ipv6)) != NULL)
+      {
+       if (!_cupsThreadCreate((_cups_thread_func_t)process_client, client))
+       {
+         perror("Unable to create client thread");
+         delete_client(client);
+       }
+      }
+    }
+
+    if (polldata[2].revents & POLLIN)
+      DNSServiceProcessResult(printer->common_ref);
+
+   /*
+    * Clean out old jobs...
+    */
+
+    clean_jobs(printer);
+  }
+}
+
+
+/*
+ * 'usage()' - Show program usage.
+ */
+
+static void
+usage(int status)                      /* O - Exit status */
+{
+  if (!status)
+  {
+    puts(CUPS_SVERSION " - Copyright 2010 by Apple Inc. All rights reserved.");
+    puts("");
+  }
+
+  puts("Usage: ippserver [options] \"name\"");
+  puts("");
+  puts("Options:");
+  puts("-2                      Supports 2-sided printing (default=1-sided)");
+  puts("-M manufacturer         Manufacturer name (default=Test)");
+  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("-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)");
+  puts("-s speed[,color-speed]  Speed in pages per minute (default=10,0)");
+  puts("-v[vvv]                 Be (very) verbose");
+
+  exit(status);
+}
+
+
+/*
+ * '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 */
+  ipp_attribute_t      *attr,          /* Current attribute */
+                       *supported;     /* document-format-supported */
+  const char           *format = NULL; /* document-format value */
+  int                  valid = 1;      /* Valid attributes? */
+
+
+ /*
+  * 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, 0, IPP_TAG_UNSUPPORTED_GROUP)
+
+  if ((attr = ippFindAttribute(client->request, "compression",
+                               IPP_TAG_ZERO)) != NULL)
+  {
+   /*
+    * If compression is specified, only accept "none"...
+    */
+
+    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_KEYWORD ||
+        strcmp(attr->values[0].string.text, "none"))
+    {
+      respond_unsupported(client, attr);
+    }
+    else
+      fprintf(stderr, "%s Print-Job compression=\"%s\"\n", client->http.hostname,
+             attr->values[0].string.text);
+  }
+
+ /*
+  * Is it a format we support?
+  */
+
+  if ((attr = ippFindAttribute(client->request, "document-format",
+                               IPP_TAG_ZERO)) != NULL)
+  {
+    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_MIMETYPE)
+    {
+      respond_unsupported(client, attr);
+    }
+    else
+      format = attr->values[0].string.text;
+  }
+  else
+    format = "application/octet-stream";
+
+  if (!strcmp(format, "application/octet-stream") &&
+      client->request->request.op.operation_id != IPP_VALIDATE_JOB)
+  {
+   /*
+    * Auto-type the file using the first 4 bytes of the file...
+    */
+
+    unsigned char      header[4];      /* First 4 bytes of file */
+
+    memset(header, 0, sizeof(header));
+    _httpPeek(&(client->http), (char *)header, sizeof(header));
+
+    if (!memcmp(header, "%PDF", 4))
+      format = "application/pdf";
+    else if (!memcmp(header, "%!", 2))
+      format = "application/postscript";
+    else if (!memcmp(header, "\377\330\377", 3) &&
+            header[3] >= 0xe0 && header[3] <= 0xef)
+      format = "image/jpeg";
+    else if (!memcmp(header, "\211PNG", 4))
+      format = "image/png";
+
+    if (format)
+      fprintf(stderr, "%s %s Auto-typed document-format=\"%s\"\n",
+             client->http.hostname,
+             ippOpString(client->request->request.op.operation_id), format);
+
+    if (!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);
+    }
+  }
+
+  if ((supported = ippFindAttribute(client->printer->attrs,
+                                    "document-format-supported",
+                                   IPP_TAG_MIMETYPE)) != NULL)
+  {
+    for (i = 0; i < attr->num_values; i ++)
+      if (!strcasecmp(format, attr->values[i].string.text))
+       break;
+
+    if (i >= attr->num_values)
+    {
+      respond_unsupported(client, attr);
+    }
+  }
+
+ /*
+  * Check the various job template 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)
+    {
+      respond_unsupported(client, attr);
+    }
+  }
+
+  if ((attr = ippFindAttribute(client->request, "ipp-attribute-fidelity",
+                               IPP_TAG_ZERO)) != NULL)
+  {
+    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_BOOLEAN)
+    {
+      respond_unsupported(client, attr);
+    }
+  }
+
+  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"))
+    {
+      respond_unsupported(client, attr);
+    }
+  }
+
+  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))
+    {
+      respond_unsupported(client, attr);
+    }
+  }
+
+  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)
+    {
+      respond_unsupported(client, attr);
+    }
+  }
+
+  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"))
+    {
+      respond_unsupported(client, attr);
+    }
+  }
+
+  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))
+    {
+      respond_unsupported(client, attr);
+    }
+    else
+    {
+      for (i = 0;
+           i < (int)(sizeof(media_supported) / sizeof(media_supported[0]));
+          i ++)
+        if (!strcmp(attr->values[0].string.text, media_supported[i]))
+         break;
+
+      if (i >= (int)(sizeof(media_supported) / sizeof(media_supported[0])))
+      {
+       respond_unsupported(client, attr);
+      }
+    }
+  }
+
+  if ((attr = ippFindAttribute(client->request, "media-col",
+                               IPP_TAG_ZERO)) != NULL)
+  {
+    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_BEGIN_COLLECTION)
+    {
+      respond_unsupported(client, attr);
+    }
+    /* TODO: check for valid media-col */
+  }
+
+  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,
+               "separate-documents-uncollated-copies") &&
+        strcmp(attr->values[0].string.text,
+               "separate-documents-collated-copies")))
+    {
+      respond_unsupported(client, attr);
+    }
+  }
+
+  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)
+    {
+      respond_unsupported(client, attr);
+    }
+  }
+
+  if ((attr = ippFindAttribute(client->request, "page-ranges",
+                               IPP_TAG_ZERO)) != NULL)
+  {
+    respond_unsupported(client, attr);
+  }
+
+  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)
+    {
+      respond_unsupported(client, attr);
+    }
+  }
+
+  if ((attr = ippFindAttribute(client->request, "printer-resolution",
+                               IPP_TAG_ZERO)) != NULL)
+  {
+    respond_unsupported(client, attr);
+  }
+
+  if ((attr = ippFindAttribute(client->request, "sides",
+                               IPP_TAG_ZERO)) != NULL)
+  {
+    if (attr->num_values != 1 || attr->value_tag != IPP_TAG_KEYWORD)
+    {
+      respond_unsupported(client, attr);
+    }
+
+    if ((supported = ippFindAttribute(client->printer->attrs, "sides",
+                                      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)
+      {
+       respond_unsupported(client, attr);
+      }
+    }
+    else
+    {
+      respond_unsupported(client, attr);
+    }
+  }
+
+  return (valid);
+}
+
+
+/*
+ * End of "$Id$".
+ */
index 6832f9d34931a104e8c1a53d2285478d15dc4b83..3ea4ec86cf5e4719f1fd6d242c0400d17f0deec8 100644 (file)
@@ -117,6 +117,7 @@ typedef struct _cups_vars_s         /**** Set of variables ****/
   int          port;                   /* Port number from URI */
   http_encryption_t encryption;                /* Encryption for connection? */
   double       timeout;                /* Timeout for connection */
+  int          family;                 /* Address family */
   cups_array_t *vars;                  /* Array of variables */
 } _cups_vars_t;
 
@@ -232,7 +233,8 @@ main(int  argc,                             /* I - Number of command-line args */
   _cupsSetLocale(argv);
 
   memset(&vars, 0, sizeof(vars));
-  vars.vars = cupsArrayNew((cups_array_func_t)compare_vars, NULL);
+  vars.family = AF_UNSPEC;
+  vars.vars   = cupsArrayNew((cups_array_func_t)compare_vars, NULL);
 
  /*
   * We need at least:
@@ -253,6 +255,16 @@ main(int  argc,                            /* I - Number of command-line args */
       {
         switch (*opt)
         {
+         case '4' : /* Connect using IPv4 only */
+             vars.family = AF_INET;
+             break;
+
+#ifdef AF_INET6
+         case '6' : /* Connect using IPv6 only */
+             vars.family = AF_INET6;
+             break;
+#endif /* AF_INET6 */
+
           case 'C' : /* Enable HTTP chunking */
               Transfer = _CUPS_TRANSFER_CHUNKED;
               break;
@@ -644,8 +656,16 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
   * Connect to the server...
   */
 
-  if ((http = httpConnectEncrypt(vars->hostname, vars->port,
-                                 vars->encryption)) == NULL)
+  if ((http = _httpCreate(vars->hostname, vars->port, vars->encryption,
+                         vars->family)) == NULL)
+  {
+    print_fatal_error("Unable to connect to %s on port %d - %s", vars->hostname,
+                      vars->port, strerror(errno));
+    pass = 0;
+    goto test_exit;
+  }
+
+  if (httpReconnect(http))
   {
     print_fatal_error("Unable to connect to %s on port %d - %s", vars->hostname,
                       vars->port, strerror(errno));
@@ -3743,10 +3763,12 @@ usage(void)
                  "\n"
                  "Options:\n"
                  "\n"
-                 "-C             Send requests using chunking (default)\n"
+                 "-4             Connect using IPv4.\n"
+                 "-6             Connect using IPv6.\n"
+                 "-C             Send requests using chunking (default).\n"
                  "-E             Test with TLS encryption.\n"
-                 "-I             Ignore errors\n"
-                 "-L             Send requests using content-length\n"
+                 "-I             Ignore errors.\n"
+                 "-L             Send requests using content-length.\n"
                  "-S             Test with SSL encryption.\n"
                  "-T             Set the receive/send timeout in seconds.\n"
                  "-V version     Set default IPP version.\n"
diff --git a/test/printer.opacity b/test/printer.opacity
new file mode 100644 (file)
index 0000000..595854a
Binary files /dev/null and b/test/printer.opacity differ
diff --git a/test/printer.png b/test/printer.png
new file mode 100644 (file)
index 0000000..18c05db
Binary files /dev/null and b/test/printer.png differ