]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - scheduler/ipp.c
Merge changes from CUPS 1.5svn-r9313.
[thirdparty/cups.git] / scheduler / ipp.c
index a2595d90ee4cee44d8baeb9d078632835f81cf06..f711b8bd0ad27e1679b33f8f641f1770df637e7a 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * "$Id: ipp.c 7944 2008-09-16 22:32:42Z mike $"
  *
- *   IPP routines for the Common UNIX Printing System (CUPS) scheduler.
+ *   IPP routines for the CUPS scheduler.
  *
- *   Copyright 2007-2009 by Apple Inc.
+ *   Copyright 2007-2010 by Apple Inc.
  *   Copyright 1997-2007 by Easy Software Products, all rights reserved.
  *
  *   This file contains Kerberos support code, copyright 2006 by
  *                                 printer.
  *   apply_printer_defaults()    - Apply printer default options to a job.
  *   authenticate_job()          - Set job authentication info.
- *   cancel_all_jobs()           - Cancel all print jobs.
+ *   cancel_all_jobs()           - Cancel all or selected print jobs.
  *   cancel_job()                - Cancel a print job.
  *   cancel_subscription()       - Cancel a subscription.
- *   check_quotas()              - Check quotas for a printer and user.
  *   check_rss_recipient()       - Check that we do not have a duplicate RSS
  *                                 feed URI.
+ *   check_quotas()              - Check quotas for a printer and user.
+ *   close_job()                 - Close a multi-file job.
  *   copy_attribute()            - Copy a single attribute.
  *   copy_attrs()                - Copy attributes from one request to another.
  *   copy_banner()               - Copy a banner file to the requests directory
  *   get_ppds()                  - Get the list of PPD files on the local
  *                                 system.
  *   get_printer_attrs()         - Get printer attributes.
+ *   get_printer_supported()     - Get printer supported values.
  *   get_printers()              - Get a list of printers or classes.
  *   get_subscription_attrs()    - Get subscription attributes.
  *   get_subscriptions()         - Get subscriptions.
  *   get_username()              - Get the username associated with a request.
  *   hold_job()                  - Hold a print job.
+ *   hold_new_jobs()             - Hold pending/new jobs on a printer or class.
  *   move_job()                  - Move a job to a new destination.
  *   ppd_parse_line()            - Parse a PPD default line.
  *   print_job()                 - Print a file to a printer or class.
  *   read_job_ticket()           - Read a job ticket embedded in a print file.
  *   reject_jobs()               - Reject print jobs to a printer.
+ *   release_held_new_jobs()     - Release pending/new jobs on a printer or
+ *                                 class.
  *   release_job()               - Release a held print job.
  *   renew_subscription()        - Renew an existing subscription...
  *   restart_job()               - Restart an old print job.
@@ -160,6 +165,7 @@ static int  check_rss_recipient(const char *recipient);
 static int     check_quotas(cupsd_client_t *con, cupsd_printer_t *p);
 static ipp_attribute_t *copy_attribute(ipp_t *to, ipp_attribute_t *attr,
                                        int quickcopy);
+static void    close_job(cupsd_client_t *con, ipp_attribute_t *uri);
 static void    copy_attrs(ipp_t *to, ipp_t *from, cups_array_t *ra,
                           ipp_tag_t group, int quickcopy);
 static int     copy_banner(cupsd_client_t *con, cupsd_job_t *job,
@@ -390,7 +396,7 @@ cupsdProcessIPPRequest(
                     charset->values[0].string.text);
       else
        ippAddString(con->response, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
-                    "attributes-charset", NULL, DefaultCharset);
+                    "attributes-charset", NULL, "utf-8");
 
       if (language)
        ippAddString(con->response, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
@@ -580,6 +586,8 @@ cupsdProcessIPPRequest(
              break;
 
          case IPP_PURGE_JOBS :
+         case IPP_CANCEL_JOBS :
+         case IPP_CANCEL_MY_JOBS :
               cancel_all_jobs(con, uri);
               break;
 
@@ -599,6 +607,10 @@ cupsdProcessIPPRequest(
               release_held_new_jobs(con, uri);
               break;
 
+         case IPP_CLOSE_JOB :
+              close_job(con, uri);
+              break;
+
          case CUPS_GET_DEFAULT :
               get_default(con);
               break;
@@ -830,6 +842,9 @@ cupsdTimeoutJob(cupsd_job_t *job)   /* I - Job to timeout */
   * See if we need to add the ending sheet...
   */
 
+  if (!cupsdLoadJob(job))
+    return (-1);
+
   printer = cupsdFindDest(job->dest);
   attr    = ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_NAME);
 
@@ -904,6 +919,9 @@ accept_jobs(cupsd_client_t  *con,   /* I - Client connection */
 
   cupsdAddPrinterHistory(printer);
 
+  cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, printer, NULL,
+                "Now accepting jobs.");
+
   if (dtype & CUPS_PRINTER_CLASS)
   {
     cupsdMarkDirty(CUPSD_DIRTY_CLASSES);
@@ -1102,7 +1120,8 @@ add_class(cupsd_client_t  *con,           /* I - Client connection */
     cupsdSetString(&pclass->info, attr->values[0].string.text);
 
   if ((attr = ippFindAttribute(con->request, "printer-is-accepting-jobs",
-                               IPP_TAG_BOOLEAN)) != NULL)
+                               IPP_TAG_BOOLEAN)) != NULL &&
+      attr->values[0].boolean != pclass->accepting)
   {
     cupsdLogMessage(CUPSD_LOG_INFO,
                     "Setting %s printer-is-accepting-jobs to %d (was %d.)",
@@ -1110,6 +1129,9 @@ add_class(cupsd_client_t  *con,           /* I - Client connection */
 
     pclass->accepting = attr->values[0].boolean;
     cupsdAddPrinterHistory(pclass);
+
+    cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, pclass, NULL, "%s accepting jobs.",
+                 pclass->accepting ? "Now" : "No longer");
   }
 
   if ((attr = ippFindAttribute(con->request, "printer-is-shared",
@@ -1154,6 +1176,9 @@ add_class(cupsd_client_t  *con,           /* I - Client connection */
     strlcpy(pclass->state_message, attr->values[0].string.text,
             sizeof(pclass->state_message));
     cupsdAddPrinterHistory(pclass);
+
+    cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, pclass, NULL, "%s",
+                  pclass->state_message);
   }
   if ((attr = ippFindAttribute(con->request, "member-uris",
                                IPP_TAG_URI)) != NULL)
@@ -1232,9 +1257,9 @@ add_class(cupsd_client_t  *con,           /* I - Client connection */
 
   if (modify)
   {
-    cupsdAddEvent(CUPSD_EVENT_PRINTER_MODIFIED, pclass, NULL,
-                  "Class \"%s\" modified by \"%s\".", pclass->name,
-                 get_username(con));
+    cupsdAddEvent(CUPSD_EVENT_PRINTER_MODIFIED,
+                 pclass, NULL, "Class \"%s\" modified by \"%s\".",
+                 pclass->name, get_username(con));
 
     cupsdLogMessage(CUPSD_LOG_INFO, "Class \"%s\" modified by \"%s\".",
                     pclass->name, get_username(con));
@@ -1243,9 +1268,9 @@ add_class(cupsd_client_t  *con,           /* I - Client connection */
   {
     cupsdAddPrinterHistory(pclass);
 
-    cupsdAddEvent(CUPSD_EVENT_PRINTER_ADDED, pclass, NULL,
-                  "New class \"%s\" added by \"%s\".", pclass->name,
-                 get_username(con));
+    cupsdAddEvent(CUPSD_EVENT_PRINTER_ADDED,
+                 pclass, NULL, "New class \"%s\" added by \"%s\".",
+                 pclass->name, get_username(con));
 
     cupsdLogMessage(CUPSD_LOG_INFO, "New class \"%s\" added by \"%s\".",
                     pclass->name, get_username(con));
@@ -1332,12 +1357,15 @@ add_job(cupsd_client_t  *con,           /* I - Client connection */
                *auth_info;             /* auth-info attribute */
   const char   *val;                   /* Default option value */
   int          priority;               /* Job priority */
-  char         *title;                 /* Job name/title */
   cupsd_job_t  *job;                   /* Current job */
   char         job_uri[HTTP_MAX_URI];  /* Job URI */
   int          kbytes;                 /* Size of print file */
   int          i;                      /* Looping var */
   int          lowerpagerange;         /* Page range bound */
+  int          exact;                  /* Did we have an exact match? */
+  ipp_attribute_t *media_col,          /* media-col attribute */
+               *media_margin;          /* media-*-margin attribute */
+  ipp_t                *unsup_col;             /* media-col in unsupported response */
 
 
   cupsdLogMessage(CUPSD_LOG_DEBUG2, "add_job(%p[%d], %p(%s), %p(%s/%s))",
@@ -1503,6 +1531,51 @@ add_job(cupsd_client_t  *con,            /* I - Client connection */
     }
   }
 
+ /*
+  * Do media selection as needed...
+  */
+
+  if (!ippFindAttribute(con->request, "PageRegion", IPP_TAG_ZERO) &&
+      !ippFindAttribute(con->request, "PageSize", IPP_TAG_ZERO) &&
+      _pwgGetPageSize(printer->pwg, con->request, NULL, &exact))
+  {
+    if (!exact &&
+        (media_col = ippFindAttribute(con->request, "media-col",
+                                     IPP_TAG_BEGIN_COLLECTION)) != NULL)
+    {
+      send_ipp_status(con, IPP_OK_SUBST, _("Unsupported margins."));
+
+      unsup_col = ippNew();
+      if ((media_margin = ippFindAttribute(media_col->values[0].collection,
+                                           "media-bottom-margin",
+                                          IPP_TAG_INTEGER)) != NULL)
+        ippAddInteger(unsup_col, IPP_TAG_ZERO, IPP_TAG_INTEGER,
+                     "media-bottom-margin", media_margin->values[0].integer);
+
+      if ((media_margin = ippFindAttribute(media_col->values[0].collection,
+                                           "media-left-margin",
+                                          IPP_TAG_INTEGER)) != NULL)
+        ippAddInteger(unsup_col, IPP_TAG_ZERO, IPP_TAG_INTEGER,
+                     "media-left-margin", media_margin->values[0].integer);
+
+      if ((media_margin = ippFindAttribute(media_col->values[0].collection,
+                                           "media-right-margin",
+                                          IPP_TAG_INTEGER)) != NULL)
+        ippAddInteger(unsup_col, IPP_TAG_ZERO, IPP_TAG_INTEGER,
+                     "media-right-margin", media_margin->values[0].integer);
+
+      if ((media_margin = ippFindAttribute(media_col->values[0].collection,
+                                           "media-top-margin",
+                                          IPP_TAG_INTEGER)) != NULL)
+        ippAddInteger(unsup_col, IPP_TAG_ZERO, IPP_TAG_INTEGER,
+                     "media-top-margin", media_margin->values[0].integer);
+
+      ippAddCollection(con->response, IPP_TAG_UNSUPPORTED_GROUP, "media-col",
+                       unsup_col);
+      ippDelete(unsup_col);
+    }
+  }
+
  /*
   * Make sure we aren't over our limit...
   */
@@ -2202,6 +2275,9 @@ add_job_subscriptions(
       ippAddSeparator(con->response);
       ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER,
                    "notify-subscription-id", sub->id);
+
+      cupsdLogMessage(CUPSD_LOG_DEBUG, "Added subscription %d for job %d",
+                      sub->id, job->id);
     }
 
     if (attr)
@@ -2604,7 +2680,8 @@ add_printer(cupsd_client_t  *con, /* I - Client connection */
   }
 
   if ((attr = ippFindAttribute(con->request, "printer-is-accepting-jobs",
-                               IPP_TAG_BOOLEAN)) != NULL)
+                               IPP_TAG_BOOLEAN)) != NULL &&
+      attr->values[0].boolean != printer->accepting)
   {
     cupsdLogMessage(CUPSD_LOG_INFO,
                     "Setting %s printer-is-accepting-jobs to %d (was %d.)",
@@ -2612,6 +2689,10 @@ add_printer(cupsd_client_t  *con,        /* I - Client connection */
 
     printer->accepting = attr->values[0].boolean;
     cupsdAddPrinterHistory(printer);
+
+    cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, printer, NULL,
+                  "%s accepting jobs.",
+                 printer->accepting ? "Now" : "No longer");
   }
 
   if ((attr = ippFindAttribute(con->request, "printer-is-shared",
@@ -2656,6 +2737,9 @@ add_printer(cupsd_client_t  *con, /* I - Client connection */
     strlcpy(printer->state_message, attr->values[0].string.text,
             sizeof(printer->state_message));
     cupsdAddPrinterHistory(printer);
+
+    cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, printer, NULL, "%s",
+                  printer->state_message);
   }
 
   if ((attr = ippFindAttribute(con->request, "printer-state-reasons",
@@ -2697,6 +2781,9 @@ add_printer(cupsd_client_t  *con, /* I - Client connection */
 
     if (PrintcapFormat == PRINTCAP_PLIST)
       cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP);
+
+    cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, printer, NULL,
+                  "Printer \"%s\" state changed.", printer->name);
   }
 
   set_printer_defaults(con, printer);
@@ -2851,15 +2938,21 @@ add_printer(cupsd_client_t  *con,       /* I - Client connection */
   {
    /*
     * If we changed the PPD/interface script, then remove the printer's cache
-    * file...
+    * file and clear the printer-state-reasons...
     */
 
     char cache_name[1024];             /* Cache filename for printer attrs */
 
-    snprintf(cache_name, sizeof(cache_name), "%s/%s.ipp", CacheDir,
+    snprintf(cache_name, sizeof(cache_name), "%s/%s.ipp4", CacheDir,
              printer->name);
     unlink(cache_name);
 
+    snprintf(cache_name, sizeof(cache_name), "%s/%s.pwg3", CacheDir,
+             printer->name);
+    unlink(cache_name);
+
+    cupsdSetPrinterReasons(printer, "none");
+
 #ifdef __APPLE__
    /*
     * (Re)register color profiles...
@@ -2936,9 +3029,9 @@ add_printer(cupsd_client_t  *con, /* I - Client connection */
 
   if (modify)
   {
-    cupsdAddEvent(CUPSD_EVENT_PRINTER_MODIFIED, printer, NULL,
-                  "Printer \"%s\" modified by \"%s\".", printer->name,
-                 get_username(con));
+    cupsdAddEvent(CUPSD_EVENT_PRINTER_MODIFIED,
+                  printer, NULL, "Printer \"%s\" modified by \"%s\".",
+                 printer->name, get_username(con));
 
     cupsdLogMessage(CUPSD_LOG_INFO, "Printer \"%s\" modified by \"%s\".",
                     printer->name, get_username(con));
@@ -2947,9 +3040,9 @@ add_printer(cupsd_client_t  *con, /* I - Client connection */
   {
     cupsdAddPrinterHistory(printer);
 
-    cupsdAddEvent(CUPSD_EVENT_PRINTER_ADDED, printer, NULL,
-                  "New printer \"%s\" added by \"%s\".", printer->name,
-                 get_username(con));
+    cupsdAddEvent(CUPSD_EVENT_PRINTER_ADDED,
+                  printer, NULL, "New printer \"%s\" added by \"%s\".",
+                 printer->name, get_username(con));
 
     cupsdLogMessage(CUPSD_LOG_INFO, "New printer \"%s\" added by \"%s\".",
                     printer->name, get_username(con));
@@ -3768,13 +3861,14 @@ authenticate_job(cupsd_client_t  *con,  /* I - Client connection */
 
 
 /*
- * 'cancel_all_jobs()' - Cancel all print jobs.
+ * 'cancel_all_jobs()' - Cancel all or selected print jobs.
  */
 
 static void
 cancel_all_jobs(cupsd_client_t  *con,  /* I - Client connection */
                ipp_attribute_t *uri)   /* I - Job or Printer URI */
 {
+  int          i;                      /* Looping var */
   http_status_t        status;                 /* Policy status */
   cups_ptype_t dtype;                  /* Destination type */
   char         scheme[HTTP_MAX_URI],   /* Scheme portion of URI */
@@ -3783,56 +3877,84 @@ cancel_all_jobs(cupsd_client_t  *con,   /* I - Client connection */
                resource[HTTP_MAX_URI]; /* Resource portion of URI */
   int          port;                   /* Port portion of URI */
   ipp_attribute_t *attr;               /* Attribute in request */
-  const char   *username;              /* Username */
-  cupsd_jobaction_t purge;             /* Purge? */
+  const char   *username = NULL;       /* Username */
+  cupsd_jobaction_t purge = CUPSD_JOB_DEFAULT;
+                                       /* Purge? */
   cupsd_printer_t *printer;            /* Printer */
+  ipp_attribute_t *job_ids;            /* job-ids attribute */
+  cupsd_job_t  *job;                   /* Job */
 
 
   cupsdLogMessage(CUPSD_LOG_DEBUG2, "cancel_all_jobs(%p[%d], %s)", con,
                   con->http.fd, uri->values[0].string.text);
 
  /*
-  * See if we have a printer URI...
+  * Get the jobs to cancel/purge...
   */
 
-  if (strcmp(uri->name, "printer-uri"))
+  switch (con->request->request.op.operation_id)
   {
-    send_ipp_status(con, IPP_BAD_REQUEST,
-                    _("The printer-uri attribute is required"));
-    return;
-  }
+    case IPP_PURGE_JOBS :
+       /*
+       * Get the username (if any) for the jobs we want to cancel (only if
+       * "my-jobs" is specified...
+       */
 
- /*
-  * Get the username (if any) for the jobs we want to cancel (only if
-  * "my-jobs" is specified...
-  */
+        if ((attr = ippFindAttribute(con->request, "my-jobs",
+                                     IPP_TAG_BOOLEAN)) != NULL &&
+            attr->values[0].boolean)
+       {
+         if ((attr = ippFindAttribute(con->request, "requesting-user-name",
+                                      IPP_TAG_NAME)) != NULL)
+           username = attr->values[0].string.text;
+         else
+         {
+           send_ipp_status(con, IPP_BAD_REQUEST,
+                           _("Missing requesting-user-name attribute"));
+           return;
+         }
+       }
 
-  if ((attr = ippFindAttribute(con->request, "my-jobs",
-                               IPP_TAG_BOOLEAN)) != NULL &&
-      attr->values[0].boolean)
-  {
-    if ((attr = ippFindAttribute(con->request, "requesting-user-name",
-                                 IPP_TAG_NAME)) != NULL)
-      username = attr->values[0].string.text;
-    else
-    {
-      send_ipp_status(con, IPP_BAD_REQUEST,
-                      _("Missing requesting-user-name attribute"));
-      return;
-    }
+       /*
+       * Look for the "purge-jobs" attribute...
+       */
+
+       if ((attr = ippFindAttribute(con->request, "purge-jobs",
+                                    IPP_TAG_BOOLEAN)) != NULL)
+         purge = attr->values[0].boolean ? CUPSD_JOB_PURGE : CUPSD_JOB_DEFAULT;
+       else
+         purge = CUPSD_JOB_PURGE;
+       break;
+
+    case IPP_CANCEL_MY_JOBS :
+        if (con->username[0])
+          username = con->username;
+        else if ((attr = ippFindAttribute(con->request, "requesting-user-name",
+                                         IPP_TAG_NAME)) != NULL)
+          username = attr->values[0].string.text;
+        else
+        {
+         send_ipp_status(con, IPP_BAD_REQUEST,
+                         _("Missing requesting-user-name attribute"));
+         return;
+        }
+
+    default :
+        break;
   }
-  else
-    username = NULL;
+
+  job_ids = ippFindAttribute(con->request, "job-ids", IPP_TAG_INTEGER);
 
  /*
-  * Look for the "purge-jobs" attribute...
+  * See if we have a printer URI...
   */
 
-  if ((attr = ippFindAttribute(con->request, "purge-jobs",
-                               IPP_TAG_BOOLEAN)) != NULL)
-    purge = attr->values[0].boolean ? CUPSD_JOB_PURGE : CUPSD_JOB_DEFAULT;
-  else
-    purge = CUPSD_JOB_PURGE;
+  if (strcmp(uri->name, "printer-uri"))
+  {
+    send_ipp_status(con, IPP_BAD_REQUEST,
+                    _("The printer-uri attribute is required"));
+    return;
+  }
 
  /*
   * And if the destination is valid...
@@ -3867,15 +3989,46 @@ cancel_all_jobs(cupsd_client_t  *con,   /* I - Client connection */
       return;
     }
 
-   /*
-    * Cancel all jobs on all printers...
-    */
+    if (job_ids)
+    {
+      for (i = 0; i < job_ids->num_values; i ++)
+      {
+       if (!cupsdFindJob(job_ids->values[i].integer))
+         break;
+      }
 
-    cupsdCancelJobs(NULL, username, purge);
+      if (i < job_ids->num_values)
+      {
+       send_ipp_status(con, IPP_NOT_FOUND, _("job-id %d not found."),
+                       job_ids->values[i].integer);
+       return;
+      }
 
-    cupsdLogMessage(CUPSD_LOG_INFO, "All jobs were %s by \"%s\".",
-                    purge == CUPSD_JOB_PURGE ? "purged" : "canceled",
-                   get_username(con));
+      for (i = 0; i < job_ids->num_values; i ++)
+      {
+       job = cupsdFindJob(job_ids->values[i].integer);
+
+       cupsdSetJobState(job, IPP_JOB_CANCELED, purge,
+                        purge == CUPSD_JOB_PURGE ? "Job purged by user." :
+                                                   "Job canceled by user.");
+      }
+
+      cupsdLogMessage(CUPSD_LOG_INFO, "Selected jobs were %s by \"%s\".",
+                     purge == CUPSD_JOB_PURGE ? "purged" : "canceled",
+                     get_username(con));
+    }
+    else
+    {
+     /*
+      * Cancel all jobs on all printers...
+      */
+
+      cupsdCancelJobs(NULL, username, purge);
+
+      cupsdLogMessage(CUPSD_LOG_INFO, "All jobs were %s by \"%s\".",
+                     purge == CUPSD_JOB_PURGE ? "purged" : "canceled",
+                     get_username(con));
+    }
   }
   else
   {
@@ -3890,16 +4043,48 @@ cancel_all_jobs(cupsd_client_t  *con,   /* I - Client connection */
       return;
     }
 
-   /*
-    * Cancel all of the jobs on the named printer...
-    */
+    if (job_ids)
+    {
+      for (i = 0; i < job_ids->num_values; i ++)
+      {
+       if ((job = cupsdFindJob(job_ids->values[i].integer)) == NULL ||
+           strcasecmp(job->dest, printer->name))
+         break;
+      }
+
+      if (i < job_ids->num_values)
+      {
+       send_ipp_status(con, IPP_NOT_FOUND, _("job-id %d not found."),
+                       job_ids->values[i].integer);
+       return;
+      }
 
-    cupsdCancelJobs(printer->name, username, purge);
+      for (i = 0; i < job_ids->num_values; i ++)
+      {
+       job = cupsdFindJob(job_ids->values[i].integer);
 
-    cupsdLogMessage(CUPSD_LOG_INFO, "All jobs on \"%s\" were %s by \"%s\".",
-                    printer->name,
-                   purge == CUPSD_JOB_PURGE ? "purged" : "canceled",
-                   get_username(con));
+       cupsdSetJobState(job, IPP_JOB_CANCELED, purge,
+                        purge == CUPSD_JOB_PURGE ? "Job purged by user." :
+                                                   "Job canceled by user.");
+      }
+
+      cupsdLogMessage(CUPSD_LOG_INFO, "Selected jobs were %s by \"%s\".",
+                     purge == CUPSD_JOB_PURGE ? "purged" : "canceled",
+                     get_username(con));
+    }
+    else
+    {
+     /*
+      * Cancel all of the jobs on the named printer...
+      */
+
+      cupsdCancelJobs(printer->name, username, purge);
+
+      cupsdLogMessage(CUPSD_LOG_INFO, "All jobs on \"%s\" were %s by \"%s\".",
+                     printer->name,
+                     purge == CUPSD_JOB_PURGE ? "purged" : "canceled",
+                     get_username(con));
+    }
   }
 
   con->response->request.status.status_code = IPP_OK;
@@ -4411,66 +4596,6 @@ check_quotas(cupsd_client_t  *con,       /* I - Client connection */
   * Check quotas...
   */
 
-#ifdef __APPLE__
-  if (AppleQuotas && (q = cupsdFindQuota(p, username)) != NULL)
-  {
-   /*
-    * TODO: Define these special page count values as constants!
-    */
-
-    if (q->page_count == -4) /* special case: unlimited user */
-    {
-      cupsdLogMessage(CUPSD_LOG_INFO,
-                      "User \"%s\" request approved for printer %s (%s): "
-                     "unlimited quota.",
-                     username, p->name, p->info);
-      q->page_count = 0; /* allow user to print */
-      return (1);
-    }
-    else if (q->page_count == -3) /* quota exceeded */
-    {
-      cupsdLogMessage(CUPSD_LOG_INFO,
-                      "User \"%s\" request denied for printer %s (%s): "
-                     "quota limit exceeded.",
-                     username, p->name, p->info);
-      q->page_count = 2; /* force quota exceeded failure */
-      return (-1);
-    }
-    else if (q->page_count == -2) /* quota disabled for user */
-    {
-      cupsdLogMessage(CUPSD_LOG_INFO,
-                      "User \"%s\" request denied for printer %s (%s): "
-                     "printing disabled for user.",
-                     username, p->name, p->info);
-      q->page_count = 2; /* force quota exceeded failure */
-      return (-1);
-    }
-    else if (q->page_count == -1) /* quota access error */
-    {
-      cupsdLogMessage(CUPSD_LOG_INFO,
-                      "User \"%s\" request denied for printer %s (%s): "
-                     "unable to determine quota limit.",
-                     username, p->name, p->info);
-      q->page_count = 2; /* force quota exceeded failure */
-      return (-1);
-    }
-    else if (q->page_count < 0) /* user not found or other error */
-    {
-      cupsdLogMessage(CUPSD_LOG_INFO,
-                      "User \"%s\" request denied for printer %s (%s): "
-                     "user disabled / missing quota.",
-                     username, p->name, p->info);
-      q->page_count = 2; /* force quota exceeded failure */
-      return (-1);
-    }
-    else /* page within user limits */
-    {
-      q->page_count = 0; /* allow user to print */
-      return (1);
-    }
-  }
-  else
-#endif /* __APPLE__ */
   if (p->k_limit || p->page_limit)
   {
     if ((q = cupsdUpdateQuota(p, username, 0, 0)) == NULL)
@@ -4498,6 +4623,127 @@ check_quotas(cupsd_client_t  *con,      /* I - Client connection */
 }
 
 
+/*
+ * 'close_job()' - Close a multi-file job.
+ */
+
+static void
+close_job(cupsd_client_t  *con,                /* I - Client connection */
+          ipp_attribute_t *uri)                /* I - Printer URI */
+{
+  cupsd_job_t          *job;           /* Job */
+  ipp_attribute_t      *attr;          /* Attribute */
+  char                 job_uri[HTTP_MAX_URI],
+                                       /* Job URI */
+                       username[256];  /* User name */
+
+
+  cupsdLogMessage(CUPSD_LOG_DEBUG2, "close_job(%p[%d], %s)", con,
+                  con->http.fd, uri->values[0].string.text);
+
+ /*
+  * See if we have a job URI or a printer URI...
+  */
+
+  if (strcmp(uri->name, "printer-uri"))
+  {
+   /*
+    * job-uri is not supported by Close-Job!
+    */
+
+    send_ipp_status(con, IPP_BAD_REQUEST,
+                   _("Close-Job doesn't support the job-uri attribute."));
+    return;
+  }
+
+ /*
+  * Got a printer URI; see if we also have a job-id attribute...
+  */
+
+  if ((attr = ippFindAttribute(con->request, "job-id",
+                              IPP_TAG_INTEGER)) == NULL)
+  {
+    send_ipp_status(con, IPP_BAD_REQUEST,
+                   _("Got a printer-uri attribute but no job-id"));
+    return;
+  }
+
+  if ((job = cupsdFindJob(attr->values[0].integer)) == NULL)
+  {
+   /*
+    * Nope - return a "not found" error...
+    */
+
+    send_ipp_status(con, IPP_NOT_FOUND, _("Job #%d does not exist"),
+                    attr->values[0].integer);
+    return;
+  }
+
+ /*
+  * See if the job is owned by the requesting user...
+  */
+
+  if (!validate_user(job, con, job->username, username, sizeof(username)))
+  {
+    send_http_error(con, con->username[0] ? HTTP_FORBIDDEN : HTTP_UNAUTHORIZED,
+                    cupsdFindDest(job->dest));
+    return;
+  }
+
+ /*
+  * Add any ending sheet...
+  */
+
+  if (cupsdTimeoutJob(job))
+    return;
+
+  if (job->state_value == IPP_JOB_STOPPED)
+  {
+    job->state->values[0].integer = IPP_JOB_PENDING;
+    job->state_value              = IPP_JOB_PENDING;
+  }
+  else if (job->state_value == IPP_JOB_HELD)
+  {
+    if ((attr = ippFindAttribute(job->attrs, "job-hold-until",
+                                IPP_TAG_KEYWORD)) == NULL)
+      attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME);
+
+    if (!attr || !strcmp(attr->values[0].string.text, "no-hold"))
+    {
+      job->state->values[0].integer = IPP_JOB_PENDING;
+      job->state_value              = IPP_JOB_PENDING;
+    }
+  }
+
+  job->dirty = 1;
+  cupsdMarkDirty(CUPSD_DIRTY_JOBS);
+
+ /*
+  * Fill in the response info...
+  */
+
+  snprintf(job_uri, sizeof(job_uri), "http://%s:%d/jobs/%d", ServerName,
+          LocalPort, job->id);
+  ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL,
+               job_uri);
+
+  ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id);
+
+  ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_ENUM, "job-state",
+                job->state_value);
+
+  add_job_state_reasons(con, job);
+
+  con->response->request.status.status_code = IPP_OK;
+
+ /*
+  * Start the job if necessary...
+  */
+
+  cupsdCheckJobs();
+}
+
+
 /*
  * 'copy_attribute()' - Copy a single attribute.
  */
@@ -4653,9 +4899,8 @@ copy_attribute(
 
         for (i = 0; i < attr->num_values; i ++)
        {
-         toattr->values[i].collection = ippNew();
-         copy_attrs(toattr->values[i].collection, attr->values[i].collection,
-                    NULL, IPP_TAG_ZERO, 0);
+         toattr->values[i].collection = attr->values[i].collection;
+         attr->values[i].collection->use ++;
        }
         break;
 
@@ -4720,11 +4965,14 @@ copy_attrs(ipp_t        *to,            /* I - Destination request */
     {
      /*
       * Don't send collection attributes by default to IPP/1.x clients
-      * since many do not support collections...
+      * since many do not support collections.  Also don't send
+      * media-col-database unless specifically requested by the client.
       */
 
       if (fromattr->value_tag == IPP_TAG_BEGIN_COLLECTION &&
-          !ra && to->request.status.version[0] == 1)
+          !ra &&
+         (to->request.status.version[0] == 1 ||
+          !strcmp(fromattr->name, "media-col-database")))
        continue;
 
       copy_attribute(to, fromattr, quickcopy);
@@ -5089,6 +5337,7 @@ copy_model(cupsd_client_t *con,           /* I - Client connection */
   int          i;                      /* Looping var */
   char         option[PPD_MAX_NAME],   /* Option name */
                choice[PPD_MAX_NAME];   /* Choice name */
+  ppd_size_t   *size;                  /* Default size */
   int          num_defaults;           /* Number of default options */
   cups_option_t        *defaults;              /* Default options */
   char         cups_protocol[PPD_MAX_LINE];
@@ -5261,19 +5510,19 @@ copy_model(cupsd_client_t *con,         /* I - Client connection */
 
     cupsFileClose(dst);
   }
-  else if (ppdPageSize(ppd, DefaultPaperSize))
+  else if ((size = ppdPageSize(ppd, DefaultPaperSize)) != NULL)
   {
    /*
     * Add the default media sizes...
     */
 
-    num_defaults = cupsAddOption("PageSize", DefaultPaperSize,
+    num_defaults = cupsAddOption("PageSize", size->name,
                                  num_defaults, &defaults);
-    num_defaults = cupsAddOption("PageRegion", DefaultPaperSize,
+    num_defaults = cupsAddOption("PageRegion", size->name,
                                  num_defaults, &defaults);
-    num_defaults = cupsAddOption("PaperDimension", DefaultPaperSize,
+    num_defaults = cupsAddOption("PaperDimension", size->name,
                                  num_defaults, &defaults);
-    num_defaults = cupsAddOption("ImageableArea", DefaultPaperSize,
+    num_defaults = cupsAddOption("ImageableArea", size->name,
                                  num_defaults, &defaults);
   }
 
@@ -5415,6 +5664,8 @@ copy_printer_attrs(
 {
   char                 printer_uri[HTTP_MAX_URI];
                                        /* Printer URI */
+  char                 printer_icons[HTTP_MAX_URI];
+                                       /* Printer icons */
   time_t               curtime;        /* Current time */
   int                  i;              /* Looping var */
   ipp_attribute_t      *history;       /* History collection */
@@ -5514,6 +5765,16 @@ copy_printer_attrs(
                    sizeof(errors) / sizeof(errors[0]), NULL, errors);
   }
 
+  if (!ra || cupsArrayFind(ra, "printer-icons"))
+  {
+    httpAssembleURIf(HTTP_URI_CODING_ALL, printer_icons, sizeof(printer_icons),
+                     "http", NULL, con->servername, con->serverport,
+                    "/icons/%s.png", printer->name);
+    ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_URI, "printer-icons",
+                 NULL, printer_icons);
+    cupsdLogMessage(CUPSD_LOG_DEBUG2, "printer-icons=\"%s\"", printer_icons);
+  }
+
   if (!ra || cupsArrayFind(ra, "printer-is-accepting-jobs"))
     ippAddBoolean(con->response, IPP_TAG_PRINTER, "printer-is-accepting-jobs",
                   printer->accepting);
@@ -6407,7 +6668,13 @@ delete_printer(cupsd_client_t  *con,     /* I - Client connection */
            printer->name);
   unlink(filename);
 
-  snprintf(filename, sizeof(filename), "%s/%s.ipp", CacheDir, printer->name);
+  snprintf(filename, sizeof(filename), "%s/%s.ipp4", CacheDir, printer->name);
+  unlink(filename);
+
+  snprintf(filename, sizeof(filename), "%s/%s.png", CacheDir, printer->name);
+  unlink(filename);
+
+  snprintf(filename, sizeof(filename), "%s/%s.pwg3", CacheDir, printer->name);
   unlink(filename);
 
 #ifdef __APPLE__
@@ -6431,7 +6698,9 @@ delete_printer(cupsd_client_t  *con,      /* I - Client connection */
     cupsdLogMessage(CUPSD_LOG_INFO, "Printer \"%s\" deleted by \"%s\".",
                     printer->name, get_username(con));
 
-    cupsdDeletePrinter(printer, 0);
+    if (cupsdDeletePrinter(printer, 0))
+      cupsdMarkDirty(CUPSD_DIRTY_CLASSES);
+
     cupsdMarkDirty(CUPSD_DIRTY_PRINTERS);
   }
 
@@ -6858,10 +7127,12 @@ get_jobs(cupsd_client_t  *con,          /* I - Client connection */
                host[HTTP_MAX_URI],     /* Host portion of URI */
                resource[HTTP_MAX_URI]; /* Resource portion of URI */
   int          port;                   /* Port portion of URI */
-  int          completed;              /* Completed jobs? */
+  int          job_comparison;         /* Job comparison */
+  ipp_jstate_t job_state;              /* job-state value */
   int          first_job_id;           /* First job ID */
   int          limit;                  /* Maximum number of jobs to return */
   int          count;                  /* Number of jobs that match */
+  ipp_attribute_t *job_ids;            /* job-ids attribute */
   cupsd_job_t  *job;                   /* Current job pointer */
   cupsd_printer_t *printer;            /* Printer */
   cups_array_t *list;                  /* Which job list... */
@@ -6942,31 +7213,82 @@ get_jobs(cupsd_client_t  *con,          /* I - Client connection */
     return;
   }
 
+  job_ids = ippFindAttribute(con->request, "job-ids", IPP_TAG_INTEGER);
+
  /*
   * See if the "which-jobs" attribute have been specified...
   */
 
   if ((attr = ippFindAttribute(con->request, "which-jobs",
-                               IPP_TAG_KEYWORD)) != NULL &&
-      !strcmp(attr->values[0].string.text, "completed"))
+                               IPP_TAG_KEYWORD)) != NULL && job_ids)
+  {
+    send_ipp_status(con, IPP_CONFLICT,
+                    _("The %s attribute cannot be provided with job-ids."),
+                    "which-jobs");
+    return;
+  }
+  else if (!attr || !strcmp(attr->values[0].string.text, "not-completed"))
+  {
+    job_comparison = -1;
+    job_state      = IPP_JOB_STOPPED;
+    list           = Jobs;
+  }
+  else if (!strcmp(attr->values[0].string.text, "completed"))
+  {
+    job_comparison = 1;
+    job_state      = IPP_JOB_CANCELED;
+    list           = Jobs;
+  }
+  else if (!strcmp(attr->values[0].string.text, "aborted"))
+  {
+    job_comparison = 0;
+    job_state      = IPP_JOB_ABORTED;
+    list           = Jobs;
+  }
+  else if (!strcmp(attr->values[0].string.text, "all"))
+  {
+    job_comparison = 1;
+    job_state      = IPP_JOB_PENDING;
+    list           = Jobs;
+  }
+  else if (!strcmp(attr->values[0].string.text, "canceled"))
   {
-    completed = 1;
-    list      = Jobs;
+    job_comparison = 0;
+    job_state      = IPP_JOB_CANCELED;
+    list           = Jobs;
   }
-  else if (attr && !strcmp(attr->values[0].string.text, "all"))
+  else if (!strcmp(attr->values[0].string.text, "pending"))
   {
-    completed = 0;
-    list      = Jobs;
+    job_comparison = 0;
+    job_state      = IPP_JOB_PENDING;
+    list           = ActiveJobs;
   }
-  else if (attr && !strcmp(attr->values[0].string.text, "processing"))
+  else if (!strcmp(attr->values[0].string.text, "pending-held"))
   {
-    completed = 0;
-    list      = PrintingJobs;
+    job_comparison = 0;
+    job_state      = IPP_JOB_HELD;
+    list           = ActiveJobs;
+  }
+  else if (!strcmp(attr->values[0].string.text, "processing"))
+  {
+    job_comparison = 0;
+    job_state      = IPP_JOB_PROCESSING;
+    list           = PrintingJobs;
+  }
+  else if (!strcmp(attr->values[0].string.text, "processing-stopped"))
+  {
+    job_comparison = 0;
+    job_state      = IPP_JOB_STOPPED;
+    list           = ActiveJobs;
   }
   else
   {
-    completed = 0;
-    list      = ActiveJobs;
+    send_ipp_status(con, IPP_ATTRIBUTES,
+                    _("The which-jobs value \"%s\" is not supported."),
+                   attr->values[0].string.text);
+    ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD,
+                 "which-jobs", NULL, attr->values[0].string.text);
+    return;
   }
 
  /*
@@ -6975,13 +7297,33 @@ get_jobs(cupsd_client_t  *con,          /* I - Client connection */
 
   if ((attr = ippFindAttribute(con->request, "limit",
                                IPP_TAG_INTEGER)) != NULL)
+  {
+    if (job_ids)
+    {
+      send_ipp_status(con, IPP_CONFLICT,
+                     _("The %s attribute cannot be provided with job-ids."),
+                     "limit");
+      return;
+    }
+
     limit = attr->values[0].integer;
+  }
   else
     limit = 0;
 
   if ((attr = ippFindAttribute(con->request, "first-job-id",
                                IPP_TAG_INTEGER)) != NULL)
+  {
+    if (job_ids)
+    {
+      send_ipp_status(con, IPP_CONFLICT,
+                     _("The %s attribute cannot be provided with job-ids."),
+                     "first-job-id");
+      return;
+    }
+
     first_job_id = attr->values[0].integer;
+  }
   else
     first_job_id = 1;
 
@@ -6990,8 +7332,14 @@ get_jobs(cupsd_client_t  *con,           /* I - Client connection */
   */
 
   if ((attr = ippFindAttribute(con->request, "my-jobs",
-                               IPP_TAG_BOOLEAN)) != NULL &&
-      attr->values[0].boolean)
+                               IPP_TAG_BOOLEAN)) != NULL && job_ids)
+  {
+    send_ipp_status(con, IPP_CONFLICT,
+                    _("The %s attribute cannot be provided with job-ids."),
+                    "my-jobs");
+    return;
+  }
+  else if (attr && attr->values[0].boolean)
     strlcpy(username, get_username(con), sizeof(username));
   else
     username[0] = '\0';
@@ -7013,58 +7361,100 @@ get_jobs(cupsd_client_t  *con,         /* I - Client connection */
   * OK, build a list of jobs for this printer...
   */
 
-  for (count = 0, job = (cupsd_job_t *)cupsArrayFirst(list);
-       (limit <= 0 || count < limit) && job;
-       job = (cupsd_job_t *)cupsArrayNext(list))
+  if (job_ids)
   {
-   /*
-    * Filter out jobs that don't match...
-    */
+    int        i;                              /* Looping var */
 
-    cupsdLogMessage(CUPSD_LOG_DEBUG2,
-                    "get_jobs: job->id=%d, dest=\"%s\", username=\"%s\", "
-                   "state_value=%d, attrs=%p", job->id, job->dest,
-                   job->username, job->state_value, job->attrs);
+    for (i = 0; i < job_ids->num_values; i ++)
+    {
+      if (!cupsdFindJob(job_ids->values[i].integer))
+        break;
+    }
 
-    if (!job->dest || !job->username)
-      cupsdLoadJob(job);
+    if (i < job_ids->num_values)
+    {
+      send_ipp_status(con, IPP_NOT_FOUND, _("job-id %d not found."),
+                      job_ids->values[i].integer);
+      return;
+    }
 
-    if (!job->dest || !job->username)
-      continue;
+    for (i = 0; i < job_ids->num_values; i ++)
+    {
+      job = cupsdFindJob(job_ids->values[i].integer);
 
-    if ((dest && strcmp(job->dest, dest)) &&
-        (!job->printer || !dest || strcmp(job->printer->name, dest)))
-      continue;
-    if ((job->dtype & dmask) != dtype &&
-        (!job->printer || (job->printer->type & dmask) != dtype))
-      continue;
-    if (completed && job->state_value <= IPP_JOB_STOPPED)
-      continue;
+      cupsdLoadJob(job);
 
-    if (job->id < first_job_id)
-      continue;
+      if (!job->attrs)
+      {
+       cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: No attributes for job %d",
+                       job->id);
+       continue;
+      }
 
-    cupsdLoadJob(job);
+      if (i > 0)
+       ippAddSeparator(con->response);
 
-    if (!job->attrs)
-    {
-      cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: No attributes for job %d",
-                      job->id);
-      continue;
+      copy_job_attrs(con, job, ra);
     }
+  }
+  else
+  {
+    for (count = 0, job = (cupsd_job_t *)cupsArrayFirst(list);
+        (limit <= 0 || count < limit) && job;
+        job = (cupsd_job_t *)cupsArrayNext(list))
+    {
+     /*
+      * Filter out jobs that don't match...
+      */
 
-    if (username[0] && strcasecmp(username, job->username))
-      continue;
+      cupsdLogMessage(CUPSD_LOG_DEBUG2,
+                     "get_jobs: job->id=%d, dest=\"%s\", username=\"%s\", "
+                     "state_value=%d, attrs=%p", job->id, job->dest,
+                     job->username, job->state_value, job->attrs);
 
-    if (count > 0)
-      ippAddSeparator(con->response);
+      if (!job->dest || !job->username)
+       cupsdLoadJob(job);
 
-    count ++;
+      if (!job->dest || !job->username)
+       continue;
 
-    copy_job_attrs(con, job, ra);
-  }
+      if ((dest && strcmp(job->dest, dest)) &&
+         (!job->printer || !dest || strcmp(job->printer->name, dest)))
+       continue;
+      if ((job->dtype & dmask) != dtype &&
+         (!job->printer || (job->printer->type & dmask) != dtype))
+       continue;
+
+      if ((job_comparison < 0 && job->state_value > job_state) ||
+          (job_comparison == 0 && job->state_value != job_state) ||
+          (job_comparison > 0 && job->state_value < job_state))
+       continue;
+
+      if (job->id < first_job_id)
+       continue;
+
+      cupsdLoadJob(job);
 
-  cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: count=%d", count);
+      if (!job->attrs)
+      {
+       cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: No attributes for job %d",
+                       job->id);
+       continue;
+      }
+
+      if (username[0] && strcasecmp(username, job->username))
+       continue;
+
+      if (count > 0)
+       ippAddSeparator(con->response);
+
+      count ++;
+
+      copy_job_attrs(con, job, ra);
+    }
+
+    cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: count=%d", count);
+  }
 
   cupsArrayDelete(ra);
 
@@ -8633,7 +9023,7 @@ print_job(cupsd_client_t  *con,           /* I - Client connection */
                type) != 2)
     {
       send_ipp_status(con, IPP_BAD_REQUEST,
-                      _("Could not scan type \"%s\""),
+                      _("Bad document-format \"%s\""),
                      format->values[0].string.text);
       return;
     }
@@ -8649,7 +9039,7 @@ print_job(cupsd_client_t  *con,           /* I - Client connection */
     if (sscanf(default_format, "%15[^/]/%31[^;]", super, type) != 2)
     {
       send_ipp_status(con, IPP_BAD_REQUEST,
-                      _("Could not scan type \"%s\""),
+                      _("Bad document-format \"%s\""),
                      default_format);
       return;
     }
@@ -8714,7 +9104,9 @@ print_job(cupsd_client_t  *con,           /* I - Client connection */
   else if (!filetype)
   {
     send_ipp_status(con, IPP_DOCUMENT_FORMAT,
-                    _("Unsupported format \'%s/%s\'"), super, type);
+                    _("Unsupported document-format \"%s\""),
+                   format ? format->values[0].string.text :
+                            "application/octet-stream");
     cupsdLogMessage(CUPSD_LOG_INFO,
                     "Hint: Do you have the raw file printing rules enabled?");
 
@@ -9034,6 +9426,9 @@ reject_jobs(cupsd_client_t  *con, /* I - Client connection */
 
   cupsdAddPrinterHistory(printer);
 
+  cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, printer, NULL,
+                "No longer accepting jobs.");
+
   if (dtype & CUPS_PRINTER_CLASS)
   {
     cupsdMarkDirty(CUPSD_DIRTY_CLASSES);
@@ -9834,6 +10229,17 @@ send_document(cupsd_client_t  *con,     /* I - Client connection */
 
   if (!con->filename)
   {
+   /*
+    * Check for an empty request with "last-document" set to true, which is
+    * used to close an "open" job by RFC 2911, section 3.3.2.
+    */
+
+    if (job->num_files > 0 &&
+        (attr = ippFindAttribute(con->request, "last-document",
+                                IPP_TAG_BOOLEAN)) != NULL &&
+        attr->values[0].boolean)
+      goto last_document;
+
     send_ipp_status(con, IPP_BAD_REQUEST, _("No file!?"));
     return;
   }
@@ -9990,6 +10396,8 @@ send_document(cupsd_client_t  *con,      /* I - Client connection */
   * Start the job if this is the last document...
   */
 
+  last_document:
+
   if ((attr = ippFindAttribute(con->request, "last-document",
                                IPP_TAG_BOOLEAN)) != NULL &&
       attr->values[0].boolean)
@@ -10186,7 +10594,7 @@ send_ipp_status(cupsd_client_t *con,    /* I - Client connection */
   if (ippFindAttribute(con->response, "attributes-charset",
                        IPP_TAG_ZERO) == NULL)
     ippAddString(con->response, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
-                 "attributes-charset", NULL, DefaultCharset);
+                 "attributes-charset", NULL, "utf-8");
 
   if (ippFindAttribute(con->response, "attributes-natural-language",
                        IPP_TAG_ZERO) == NULL)
@@ -11291,7 +11699,8 @@ validate_job(cupsd_client_t  *con,      /* I - Client connection */
             ipp_attribute_t *uri)      /* I - Printer URI */
 {
   http_status_t                status;         /* Policy status */
-  ipp_attribute_t      *attr;          /* Current attribute */
+  ipp_attribute_t      *attr,          /* Current attribute */
+                       *auth_info;     /* auth-info attribute */
   ipp_attribute_t      *format;        /* Document-format attribute */
   cups_ptype_t         dtype;          /* Destination type (printer/class) */
   char                 super[MIME_MAX_SUPER],
@@ -11310,15 +11719,21 @@ validate_job(cupsd_client_t  *con,    /* I - Client connection */
   */
 
   if ((attr = ippFindAttribute(con->request, "compression",
-                               IPP_TAG_KEYWORD)) != NULL &&
-      !strcmp(attr->values[0].string.text, "none"))
+                               IPP_TAG_KEYWORD)) != NULL)
   {
-    send_ipp_status(con, IPP_ATTRIBUTES,
-                    _("Unsupported compression attribute %s"),
-                    attr->values[0].string.text);
-    ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD,
-                "compression", NULL, attr->values[0].string.text);
-    return;
+    if (strcmp(attr->values[0].string.text, "none")
+#ifdef HAVE_LIBZ
+        && strcmp(attr->values[0].string.text, "gzip")
+#endif /* HAVE_LIBZ */
+      )
+    {
+      send_ipp_status(con, IPP_ATTRIBUTES,
+                      _("Unsupported compression \"%s\""),
+                     attr->values[0].string.text);
+      ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD,
+                  "compression", NULL, attr->values[0].string.text);
+      return;
+    }
   }
 
  /*
@@ -11342,7 +11757,7 @@ validate_job(cupsd_client_t  *con,      /* I - Client connection */
       cupsdLogMessage(CUPSD_LOG_INFO,
                       "Hint: Do you have the raw file printing rules enabled?");
       send_ipp_status(con, IPP_DOCUMENT_FORMAT,
-                      _("Unsupported format \"%s\""),
+                      _("Unsupported document-format \"%s\""),
                      format->values[0].string.text);
       ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_MIMETYPE,
                    "document-format", NULL, format->values[0].string.text);
@@ -11369,11 +11784,32 @@ validate_job(cupsd_client_t  *con,    /* I - Client connection */
   * Check policy...
   */
 
+  auth_info = ippFindAttribute(con->request, "auth-info", IPP_TAG_TEXT);
+
   if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK)
   {
     send_http_error(con, status, printer);
     return;
   }
+  else if (printer->num_auth_info_required == 1 &&
+           !strcmp(printer->auth_info_required[0], "negotiate") &&
+           !con->username[0])
+  {
+    send_http_error(con, HTTP_UNAUTHORIZED, printer);
+    return;
+  }
+#ifdef HAVE_SSL
+  else if (auth_info && !con->http.tls &&
+           !httpAddrLocalhost(con->http.hostaddr))
+  {
+   /*
+    * Require encryption of auth-info over non-local connections...
+    */
+
+    send_http_error(con, HTTP_UPGRADE_REQUIRED, printer);
+    return;
+  }
+#endif /* HAVE_SSL */
 
  /*
   * Everything was ok, so return OK status...