]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - test/ipptool.c
Merge changes from CUPS 1.6svn-r10112.
[thirdparty/cups.git] / test / ipptool.c
index 330c5212de33846345f6242723b56d48a8f6c7d1..c9060905d57b90ecf69426489436754488a21b20 100644 (file)
@@ -3,7 +3,7 @@
  *
  *   ipptool command for CUPS.
  *
- *   Copyright 2007-2010 by Apple Inc.
+ *   Copyright 2007-2011 by Apple Inc.
  *   Copyright 1997-2007 by Easy Software Products.
  *
  *   These coded instructions, statements, and computer programs are the
@@ -12,6 +12,8 @@
  *   which should have been included with this file.  If this file is
  *   file is missing or damaged, see the license at "http://www.cups.org/".
  *
+ *   This file is subject to the Apple OS-Developed Software exception.
+ *
  * Contents:
  *
  *   main()              - Parse options and do tests.
@@ -36,6 +38,8 @@
  *   print_xml_string()  - Print an XML string with escaping.
  *   print_xml_trailer() - Print the XML trailer with success/fail value.
  *   set_variable()      - Set a variable value.
+ *   sigterm_handler()   - Handle SIGINT and SIGTERM.
+ *   timeout_cb()        - Handle HTTP timeouts.
  *   usage()             - Show program usage.
  *   validate_attr()     - Determine whether an attribute is valid.
  *   with_value()        - Test a WITH-VALUE predicate.
 #include <cups/cups-private.h>
 #include <cups/file-private.h>
 #include <regex.h>
-
+#include <sys/stat.h>
+#ifndef WIN32
+#  include <signal.h>
+#endif /* WIN32 */
 #ifndef O_BINARY
 #  define O_BINARY 0
 #endif /* !O_BINARY */
@@ -87,7 +94,9 @@ typedef struct _cups_expect_s         /**** Expected attribute info ****/
                *define_match,          /* Variable to define on match */
                *define_no_match,       /* Variable to define on no-match */
                *define_value;          /* Variable to define with value */
-  int          with_regex,             /* WITH-VALUE is a regular expression */
+  int          repeat_match,           /* Repeat test on match */
+               repeat_no_match,        /* Repeat test on no match */
+               with_regex,             /* WITH-VALUE is a regular expression */
                count;                  /* Expected count if > 0 */
   ipp_tag_t    in_group;               /* IN-GROUP value */
 } _cups_expect_t;
@@ -97,6 +106,8 @@ typedef struct _cups_status_s                /**** Status info ****/
   ipp_status_t status;                 /* Expected status code */
   char         *if_defined,            /* Only if variable is defined */
                *if_not_defined;        /* Only if variable is not defined */
+  int          repeat_match,           /* Repeat the test when it does not match */
+               repeat_no_match;        /* Repeat the test when it matches */
 } _cups_status_t;
 
 typedef struct _cups_var_s             /**** Variable ****/
@@ -107,14 +118,16 @@ typedef struct _cups_var_s                /**** Variable ****/
 
 typedef struct _cups_vars_s            /**** Set of variables ****/
 {
-  const char   *uri,                   /* URI for printer */
-               *filename;              /* Filename */
-  char         scheme[64],             /* Scheme from URI */
+  char         *uri,                   /* URI for printer */
+               *filename,              /* Filename */
+               scheme[64],             /* Scheme from URI */
                userpass[256],          /* Username/password from URI */
                hostname[256],          /* Hostname from URI */
                resource[1024];         /* Resource path from URI */
   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;
 
@@ -127,10 +140,15 @@ _cups_transfer_t Transfer = _CUPS_TRANSFER_AUTO;
                                        /* How to transfer requests */
 _cups_output_t Output = _CUPS_OUTPUT_LIST;
                                        /* Output mode */
-int            IgnoreErrors = 0,       /* Ignore errors? */
+int            Cancel = 0,             /* Cancel test? */
+               IgnoreErrors = 0,       /* Ignore errors? */
                Verbosity = 0,          /* Show all attributes? */
                Version = 11,           /* Default IPP version */
-               XMLHeader = 0;          /* 1 if header is written */
+               XMLHeader = 0,          /* 1 if header is written */
+               TestCount = 0,          /* Number of tests run */
+               PassCount = 0,          /* Number of passing tests */
+               FailCount = 0,          /* Number of failing tests */
+               SkipCount = 0;          /* Number of skipped tests */
 char           *Password = NULL;       /* Password from URI */
 const char * const URIStatusStrings[] =        /* URI status strings */
 {
@@ -156,11 +174,7 @@ const char * const URIStatusStrings[] =    /* URI status strings */
 static int     compare_vars(_cups_var_t *a, _cups_var_t *b);
 static int     do_tests(_cups_vars_t *vars, const char *testfile);
 static void    expand_variables(_cups_vars_t *vars, char *dst, const char *src,
-                                size_t dstsize)
-#ifdef __GNUC__
-__attribute((nonnull(1,2,3)))
-#endif /* __GNUC__ */
-;
+                                size_t dstsize) __attribute__((nonnull(1,2,3)));
 static int      expect_matches(_cups_expect_t *expect, ipp_tag_t value_tag);
 static ipp_t   *get_collection(_cups_vars_t *vars, FILE *fp, int *linenum);
 static char    *get_filename(const char *testfile, char *dst, const char *src,
@@ -170,31 +184,29 @@ static char       *get_token(FILE *fp, char *buf, int buflen,
 static char    *get_variable(_cups_vars_t *vars, const char *name);
 static char    *iso_date(ipp_uchar_t *date);
 static const char *password_cb(const char *prompt);
-static void    print_attr(ipp_attribute_t *attr);
+static void    print_attr(ipp_attribute_t *attr, ipp_tag_t *group);
 static void    print_col(ipp_t *col);
 static void    print_csv(ipp_attribute_t *attr, int num_displayed,
                          char **displayed, size_t *widths);
 static void    print_fatal_error(const char *s, ...)
-#ifdef __GNUC__
-__attribute__ ((__format__ (__printf__, 1, 2)))
-#endif /* __GNUC__ */
-;
+               __attribute__ ((__format__ (__printf__, 1, 2)));
 static void    print_line(ipp_attribute_t *attr, int num_displayed,
                           char **displayed, size_t *widths);
 static void    print_test_error(const char *s, ...)
-#ifdef __GNUC__
-__attribute__ ((__format__ (__printf__, 1, 2)))
-#endif /* __GNUC__ */
-;
+               __attribute__ ((__format__ (__printf__, 1, 2)));
 static void    print_xml_header(void);
 static void    print_xml_string(const char *element, const char *s);
 static void    print_xml_trailer(int success, const char *message);
 static void    set_variable(_cups_vars_t *vars, const char *name,
                             const char *value);
-static void    usage(void);
+#ifndef WIN32
+static void    sigterm_handler(int sig);
+#endif /* WIN32 */
+static int     timeout_cb(http_t *http, void *user_data);
+static void    usage(void) __attribute__((noreturn));
 static int     validate_attr(ipp_attribute_t *attr, int print);
 static int      with_value(char *value, int regex, ipp_attribute_t *attr,
-                          int report);
+                          int report, char *matchbuf, size_t matchlen);
 
 
 /*
@@ -211,9 +223,11 @@ main(int  argc,                            /* I - Number of command-line args */
                        name[1024],     /* Name/value buffer */
                        *value,         /* Pointer to value */
                        filename[1024], /* Real filename */
-                       testname[1024]; /* Real test filename */
-  const char           *testfile;      /* Test file to use */
-  int                  interval,       /* Test interval */
+                       testname[1024], /* Real test filename */
+                       uri[1024];      /* Copy of printer URI */
+  const char           *ext,           /* Extension on filename */
+                       *testfile;      /* Test file to use */
+  int                  interval,       /* Test interval in microseconds */
                        repeat;         /* Repeat count */
   _cups_vars_t         vars;           /* Variables */
   http_uri_status_t    uri_status;     /* URI separation status */
@@ -221,6 +235,14 @@ main(int  argc,                            /* I - Number of command-line args */
                                        /* Global data */
 
 
+#ifndef WIN32
+ /*
+  * Catch SIGINT and SIGTERM...
+  */
+
+  signal(SIGINT, sigterm_handler);
+  signal(SIGTERM, sigterm_handler);
+#endif /* !WIN32 */
 
  /*
   * Initialize the locale and variables...
@@ -229,7 +251,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:
@@ -250,6 +273,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;
@@ -258,8 +291,7 @@ main(int  argc,                             /* I - Number of command-line args */
 #ifdef HAVE_SSL
              vars.encryption = HTTP_ENCRYPT_REQUIRED;
 #else
-             _cupsLangPrintf(stderr,
-                             _("%s: Sorry, no encryption support compiled in\n"),
+             _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."),
                              argv[0]);
 #endif /* HAVE_SSL */
              break;
@@ -276,19 +308,31 @@ main(int  argc,                           /* I - Number of command-line args */
 #ifdef HAVE_SSL
              vars.encryption = HTTP_ENCRYPT_ALWAYS;
 #else
-             _cupsLangPrintf(stderr,
-                             _("%s: Sorry, no encryption support compiled in\n"),
+             _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."),
                              argv[0]);
 #endif /* HAVE_SSL */
              break;
 
+         case 'T' : /* Set timeout */
+             i ++;
+
+             if (i >= argc)
+             {
+               _cupsLangPuts(stderr,
+                             _("ipptool: Missing timeout for \"-T\"."));
+               usage();
+              }
+
+             vars.timeout = _cupsStrScand(argv[i], NULL, localeconv());
+             break;
+
          case 'V' : /* Set IPP version */
              i ++;
 
              if (i >= argc)
              {
                _cupsLangPuts(stderr,
-                             _("ipptool: Missing version for \"-V\".\n"));
+                             _("ipptool: Missing version for \"-V\"."));
                usage();
               }
 
@@ -305,7 +349,7 @@ main(int  argc,                             /* I - Number of command-line args */
              else
              {
                _cupsLangPrintf(stderr,
-                               _("ipptool: Bad version %s for \"-V\".\n"),
+                               _("ipptool: Bad version %s for \"-V\"."),
                                argv[i]);
                usage();
              }
@@ -317,7 +361,7 @@ main(int  argc,                             /* I - Number of command-line args */
               if (interval || repeat)
              {
                _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are "
-                                       "incompatible with -X\".\n"));
+                                       "incompatible with -X\"."));
                usage();
              }
              break;
@@ -332,7 +376,7 @@ main(int  argc,                             /* I - Number of command-line args */
              if (i >= argc)
              {
                _cupsLangPuts(stderr,
-                             _("ipptool: Missing name=value for \"-d\".\n"));
+                             _("ipptool: Missing name=value for \"-d\"."));
                usage();
               }
 
@@ -351,21 +395,81 @@ main(int  argc,                           /* I - Number of command-line args */
              if (i >= argc)
              {
                _cupsLangPuts(stderr,
-                             _("ipptool: Missing filename for \"-f\".\n"));
+                             _("ipptool: Missing filename for \"-f\"."));
                usage();
               }
 
-              if (access(argv[i], 0) && argv[i][0] != '/')
+              if (vars.filename)
+               free(vars.filename);
+
+              if (access(argv[i], 0))
+              {
+               /*
+                * Try filename.gz...
+                */
+
+               snprintf(filename, sizeof(filename), "%s.gz", argv[i]);
+                if (access(filename, 0) && filename[0] != '/')
+               {
+                 snprintf(filename, sizeof(filename), "%s/ipptool/%s",
+                          cg->cups_datadir, argv[i]);
+                 if (access(filename, 0))
+                 {
+                   snprintf(filename, sizeof(filename), "%s/ipptool/%s.gz",
+                            cg->cups_datadir, argv[i]);
+                   if (access(filename, 0))
+                     vars.filename = strdup(argv[i]);
+                 }
+                 else
+                   vars.filename = strdup(filename);
+               }
+               else
+                 vars.filename = strdup(filename);
+             }
+              else
+               vars.filename = strdup(argv[i]);
+
+              if ((ext = strrchr(vars.filename, '.')) != NULL)
               {
-                snprintf(filename, sizeof(filename), "%s/ipptool/%s",
-                         cg->cups_datadir, argv[i]);
-                if (access(argv[i], 0))
-                 vars.filename = argv[i];
+               /*
+                * Guess the MIME media type based on the extension...
+                */
+
+                if (!_cups_strcasecmp(ext, ".gif"))
+                  set_variable(&vars, "filetype", "image/gif");
+                else if (!_cups_strcasecmp(ext, ".htm") ||
+                         !_cups_strcasecmp(ext, ".htm.gz") ||
+                         !_cups_strcasecmp(ext, ".html") ||
+                         !_cups_strcasecmp(ext, ".html.gz"))
+                  set_variable(&vars, "filetype", "text/html");
+                else if (!_cups_strcasecmp(ext, ".jpg"))
+                  set_variable(&vars, "filetype", "image/jpeg");
+                else if (!_cups_strcasecmp(ext, ".pdf"))
+                  set_variable(&vars, "filetype", "application/pdf");
+                else if (!_cups_strcasecmp(ext, ".png"))
+                  set_variable(&vars, "filetype", "image/png");
+                else if (!_cups_strcasecmp(ext, ".ps") ||
+                         !_cups_strcasecmp(ext, ".ps.gz"))
+                  set_variable(&vars, "filetype", "application/postscript");
+                else if (!_cups_strcasecmp(ext, ".ras") ||
+                         !_cups_strcasecmp(ext, ".ras.gz"))
+                  set_variable(&vars, "filetype", "image/pwg-raster");
+                else if (!_cups_strcasecmp(ext, ".txt") ||
+                         !_cups_strcasecmp(ext, ".txt.gz"))
+                  set_variable(&vars, "filetype", "text/plain");
+                else if (!_cups_strcasecmp(ext, ".xps"))
+                  set_variable(&vars, "filetype", "application/openxps");
                 else
-                  vars.filename = filename;
+                 set_variable(&vars, "filetype", "application/octet-stream");
               }
               else
-               vars.filename = argv[i];
+              {
+               /*
+                * Use the "auto-type" MIME media type...
+                */
+
+               set_variable(&vars, "filetype", "application/octet-stream");
+              }
              break;
 
           case 'i' : /* Test every N seconds */
@@ -374,16 +478,25 @@ main(int  argc,                           /* I - Number of command-line args */
              if (i >= argc)
              {
                _cupsLangPuts(stderr,
-                             _("ipptool: Missing seconds for \"-i\".\n"));
+                             _("ipptool: Missing seconds for \"-i\"."));
                usage();
               }
              else
-               interval = atoi(argv[i]);
+             {
+               interval = (int)(_cupsStrScand(argv[i], NULL, localeconv()) *
+                                1000000.0);
+               if (interval <= 0)
+               {
+                 _cupsLangPuts(stderr,
+                               _("ipptool: Invalid seconds for \"-i\"."));
+                 usage();
+               }
+              }
 
               if (Output == _CUPS_OUTPUT_PLIST && interval)
              {
                _cupsLangPuts(stderr, _("ipptool: \"-i\" is incompatible with "
-                                       "\"-X\".\n"));
+                                       "\"-X\"."));
                usage();
              }
              break;
@@ -398,7 +511,7 @@ main(int  argc,                             /* I - Number of command-line args */
              if (i >= argc)
              {
                _cupsLangPuts(stderr,
-                             _("ipptool: Missing count for \"-n\".\n"));
+                             _("ipptool: Missing count for \"-n\"."));
                usage();
               }
              else
@@ -407,7 +520,7 @@ main(int  argc,                             /* I - Number of command-line args */
               if (Output == _CUPS_OUTPUT_PLIST && repeat)
              {
                _cupsLangPuts(stderr, _("ipptool: \"-n\" is incompatible with "
-                                       "\"-X\".\n"));
+                                       "\"-X\"."));
                usage();
              }
              break;
@@ -425,7 +538,7 @@ main(int  argc,                             /* I - Number of command-line args */
              break;
 
          default :
-             _cupsLangPrintf(stderr, _("ipptool: Unknown option \"-%c\".\n"),
+             _cupsLangPrintf(stderr, _("ipptool: Unknown option \"-%c\"."),
                              *opt);
              usage();
              break;
@@ -445,7 +558,7 @@ main(int  argc,                             /* I - Number of command-line args */
 
       if (vars.uri)
       {
-        _cupsLangPuts(stderr, _("ipptool: May only specify a single URI.\n"));
+        _cupsLangPuts(stderr, _("ipptool: May only specify a single URI."));
         usage();
       }
 
@@ -464,7 +577,7 @@ main(int  argc,                             /* I - Number of command-line args */
 
       if (uri_status != HTTP_URI_OK)
       {
-        _cupsLangPrintf(stderr, _("ipptool: Bad URI - %s.\n"),
+        _cupsLangPrintf(stderr, _("ipptool: Bad URI - %s."),
                        URIStatusStrings[uri_status - HTTP_URI_OVERFLOW]);
         return (1);
       }
@@ -478,6 +591,10 @@ main(int  argc,                            /* I - Number of command-line args */
        cupsSetPasswordCB(password_cb);
        set_variable(&vars, "uriuser", vars.userpass);
       }
+
+      httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), vars.scheme, NULL,
+                      vars.hostname, vars.port, vars.resource);
+      vars.uri = uri;
     }
     else
     {
@@ -517,23 +634,33 @@ main(int  argc,                           /* I - Number of command-line args */
 
   if (Output == _CUPS_OUTPUT_PLIST)
     print_xml_trailer(!status, NULL);
-  else if (interval && repeat > 0)
+  else if (interval > 0 && repeat > 0)
   {
     while (repeat > 1)
     {
-      sleep(interval);
+      usleep(interval);
       do_tests(&vars, testfile);
       repeat --;
     }
   }
-  else if (interval)
+  else if (interval > 0)
   {
     for (;;)
     {
-      sleep(interval);
+      usleep(interval);
       do_tests(&vars, testfile);
     }
   }
+  else if (Output == _CUPS_OUTPUT_TEST && TestCount > 1)
+  {
+   /*
+    * Show a summary report if there were multiple tests...
+    */
+
+    printf("\nSummary: %d tests, %d passed, %d failed, %d skipped\n"
+           "Score: %d%%\n", TestCount, PassCount, FailCount, SkipCount,
+           100 * (PassCount + SkipCount) / TestCount);
+  }
 
  /*
   * Exit...
@@ -551,7 +678,7 @@ static int                          /* O - Result of comparison */
 compare_vars(_cups_var_t *a,           /* I - First variable */
              _cups_var_t *b)           /* I - Second variable */
 {
-  return (strcasecmp(a->name, b->name));
+  return (_cups_strcasecmp(a->name, b->name));
 }
 
 
@@ -570,15 +697,21 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
                request_id,             /* Current request ID */
                show_header = 1,        /* Show the test header? */
                ignore_errors,          /* Ignore test failures? */
-               skip_previous = 0;      /* Skip on previous test failure? */
+               skip_previous = 0,      /* Skip on previous test failure? */
+               repeat_test;            /* Repeat a test? */
   http_t       *http = NULL;           /* HTTP connection to server */
   FILE         *fp = NULL;             /* Test file */
   char         resource[512],          /* Resource for request */
                token[1024],            /* Token from file */
                *tokenptr,              /* Pointer into token */
-               temp[1024];             /* Temporary string */
-  ipp_t                *request = NULL;        /* IPP request */
-  ipp_t                *response = NULL;       /* IPP response */
+               temp[1024],             /* Temporary string */
+               buffer[8192];           /* Copy buffer */
+  ipp_t                *request = NULL,        /* IPP request */
+               *response = NULL;       /* IPP response */
+  size_t       length;                 /* Length of IPP request */
+  http_status_t        status;                 /* HTTP status */
+  cups_file_t  *reqfile;               /* File to send */
+  ssize_t      bytes;                  /* Bytes read/written */
   char         attr[128];              /* Attribute name */
   ipp_op_t     op;                     /* Operation */
   ipp_tag_t    group;                  /* Current group */
@@ -601,6 +734,7 @@ do_tests(_cups_vars_t *vars,                /* I - Variables */
   int          num_displayed = 0;      /* Number of displayed attributes */
   char         *displayed[200];        /* Displayed attributes */
   size_t       widths[200];            /* Width of columns */
+  cups_array_t *a;                     /* Duplicate attribute array */
 
 
  /*
@@ -619,15 +753,26 @@ 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, NULL, vars->encryption,
+                         vars->family)) == NULL)
+  {
+    print_fatal_error("Unable to connect to %s on port %d - %s", vars->hostname,
+                      vars->port, cupsLastErrorString());
+    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));
+                      vars->port, cupsLastErrorString());
     pass = 0;
     goto test_exit;
   }
 
+  if (vars->timeout > 0.0)
+    httpSetTimeout(http, vars->timeout, timeout_cb, NULL);
+
  /*
   * Loop on tests...
   */
@@ -638,7 +783,7 @@ do_tests(_cups_vars_t *vars,                /* I - Variables */
   linenum    = 1;
   request_id = (CUPS_RAND() % 1000) * 137 + 1;
 
-  while (get_token(fp, token, sizeof(token), &linenum) != NULL)
+  while (!Cancel && get_token(fp, token, sizeof(token), &linenum) != NULL)
   {
    /*
     * Expect an open brace...
@@ -666,6 +811,29 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
 
       continue;
     }
+    else if (!strcmp(token, "DEFINE-DEFAULT"))
+    {
+     /*
+      * DEFINE-DEFAULT name value
+      */
+
+      if (get_token(fp, attr, sizeof(attr), &linenum) &&
+          get_token(fp, temp, sizeof(temp), &linenum))
+      {
+        expand_variables(vars, token, temp, sizeof(token));
+       if (!get_variable(vars, attr))
+         set_variable(vars, attr, token);
+      }
+      else
+      {
+        print_fatal_error("Missing DEFINE-DEFAULT name and/or value on line "
+                         "%d.", linenum);
+       pass = 0;
+       goto test_exit;
+      }
+
+      continue;
+    }
     else if (!strcmp(token, "IGNORE-ERRORS"))
     {
      /*
@@ -674,9 +842,9 @@ do_tests(_cups_vars_t *vars,                /* I - Variables */
       */
 
       if (get_token(fp, temp, sizeof(temp), &linenum) &&
-          (!strcasecmp(temp, "yes") || !strcasecmp(temp, "no")))
+          (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
       {
-        IgnoreErrors = !strcasecmp(temp, "yes");
+        IgnoreErrors = !_cups_strcasecmp(temp, "yes");
       }
       else
       {
@@ -719,6 +887,76 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
       show_header = 1;
       continue;
     }
+    else if (!strcmp(token, "INCLUDE-IF-DEFINED"))
+    {
+     /*
+      * INCLUDE-IF-DEFINED name "filename"
+      * INCLUDE-IF-DEFINED name <filename>
+      */
+
+      if (get_token(fp, attr, sizeof(attr), &linenum) &&
+          get_token(fp, temp, sizeof(temp), &linenum))
+      {
+       /*
+        * Map the filename to and then run the tests...
+       */
+
+        if (get_variable(vars, attr) &&
+           !do_tests(vars, get_filename(testfile, filename, temp,
+                                        sizeof(filename))))
+       {
+         pass = 0;
+
+         if (!IgnoreErrors)
+           goto test_exit;
+       }
+      }
+      else
+      {
+        print_fatal_error("Missing INCLUDE-IF-DEFINED name or filename on line "
+                         "%d.", linenum);
+       pass = 0;
+       goto test_exit;
+      }
+
+      show_header = 1;
+      continue;
+    }
+    else if (!strcmp(token, "INCLUDE-IF-NOT-DEFINED"))
+    {
+     /*
+      * INCLUDE-IF-NOT-DEFINED name "filename"
+      * INCLUDE-IF-NOT-DEFINED name <filename>
+      */
+
+      if (get_token(fp, attr, sizeof(attr), &linenum) &&
+          get_token(fp, temp, sizeof(temp), &linenum))
+      {
+       /*
+        * Map the filename to and then run the tests...
+       */
+
+        if (!get_variable(vars, attr) &&
+           !do_tests(vars, get_filename(testfile, filename, temp,
+                                        sizeof(filename))))
+       {
+         pass = 0;
+
+         if (!IgnoreErrors)
+           goto test_exit;
+       }
+      }
+      else
+      {
+        print_fatal_error("Missing INCLUDE-IF-NOT-DEFINED name or filename on "
+                         "line %d.", linenum);
+       pass = 0;
+       goto test_exit;
+      }
+
+      show_header = 1;
+      continue;
+    }
     else if (!strcmp(token, "SKIP-IF-DEFINED"))
     {
      /*
@@ -732,7 +970,8 @@ do_tests(_cups_vars_t *vars,                /* I - Variables */
       }
       else
       {
-        print_fatal_error("Missing SKIP-IF-DEFINED value on line %d.", linenum);
+        print_fatal_error("Missing SKIP-IF-DEFINED variable on line %d.",
+                         linenum);
        pass = 0;
        goto test_exit;
       }
@@ -750,7 +989,7 @@ do_tests(_cups_vars_t *vars,                /* I - Variables */
       }
       else
       {
-        print_fatal_error("Missing SKIP-IF-NOT-DEFINED value on line %d.",
+        print_fatal_error("Missing SKIP-IF-NOT-DEFINED variable on line %d.",
                          linenum);
        pass = 0;
        goto test_exit;
@@ -850,6 +1089,7 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
     last_expect   = NULL;
     last_status   = NULL;
     filename[0]   = '\0';
+    skip_previous = 0;
     skip_test     = 0;
     version       = Version;
     transfer      = Transfer;
@@ -864,20 +1104,24 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
 
     while (get_token(fp, token, sizeof(token), &linenum) != NULL)
     {
-      if (strcasecmp(token, "COUNT") &&
-          strcasecmp(token, "DEFINE-MATCH") &&
-          strcasecmp(token, "DEFINE-NO-MATCH") &&
-          strcasecmp(token, "DEFINE-VALUE") &&
-          strcasecmp(token, "IF-DEFINED") &&
-          strcasecmp(token, "IF-NOT-DEFINED") &&
-          strcasecmp(token, "IN-GROUP") &&
-          strcasecmp(token, "OF-TYPE") &&
-          strcasecmp(token, "SAME-COUNT-AS") &&
-          strcasecmp(token, "WITH-VALUE"))
+      if (_cups_strcasecmp(token, "COUNT") &&
+          _cups_strcasecmp(token, "DEFINE-MATCH") &&
+          _cups_strcasecmp(token, "DEFINE-NO-MATCH") &&
+          _cups_strcasecmp(token, "DEFINE-VALUE") &&
+          _cups_strcasecmp(token, "IF-DEFINED") &&
+          _cups_strcasecmp(token, "IF-NOT-DEFINED") &&
+          _cups_strcasecmp(token, "IN-GROUP") &&
+          _cups_strcasecmp(token, "OF-TYPE") &&
+          _cups_strcasecmp(token, "REPEAT-MATCH") &&
+          _cups_strcasecmp(token, "REPEAT-NO-MATCH") &&
+          _cups_strcasecmp(token, "SAME-COUNT-AS") &&
+          _cups_strcasecmp(token, "WITH-VALUE"))
         last_expect = NULL;
 
-      if (strcasecmp(token, "IF-DEFINED") &&
-          strcasecmp(token, "IF-NOT-DEFINED"))
+      if (_cups_strcasecmp(token, "IF-DEFINED") &&
+          _cups_strcasecmp(token, "IF-NOT-DEFINED") &&
+          _cups_strcasecmp(token, "REPEAT-MATCH") &&
+          _cups_strcasecmp(token, "REPEAT-NO-MATCH"))
         last_status = NULL;
 
       if (!strcmp(token, "}"))
@@ -902,7 +1146,7 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
 
          if ((tempcol = realloc(lastcol, sizeof(ipp_attribute_t) +
                                          (lastcol->num_values + 1) *
-                                         sizeof(ipp_value_t))) == NULL)
+                                         sizeof(_ipp_value_t))) == NULL)
          {
            print_fatal_error("Unable to allocate memory on line %d.", linenum);
            pass = 0;
@@ -960,9 +1204,9 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
        */
 
        if (get_token(fp, temp, sizeof(temp), &linenum) &&
-           (!strcasecmp(temp, "yes") || !strcasecmp(temp, "no")))
+           (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
        {
-         ignore_errors = !strcasecmp(temp, "yes");
+         ignore_errors = !_cups_strcasecmp(temp, "yes");
        }
        else
        {
@@ -973,7 +1217,7 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
 
        continue;
       }
-      else if (!strcasecmp(token, "NAME"))
+      else if (!_cups_strcasecmp(token, "NAME"))
       {
        /*
         * Name of test...
@@ -992,7 +1236,7 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
        {
          if (isdigit(temp[0] & 255))
            request_id = atoi(temp);
-         else if (!strcasecmp(temp, "random"))
+         else if (!_cups_strcasecmp(temp, "random"))
            request_id = (CUPS_RAND() % 1000) * 137 + 1;
          else
          {
@@ -1055,9 +1299,9 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
        */
 
        if (get_token(fp, temp, sizeof(temp), &linenum) &&
-           (!strcasecmp(temp, "yes") || !strcasecmp(temp, "no")))
+           (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
        {
-         skip_previous = !strcasecmp(temp, "yes");
+         skip_previous = !_cups_strcasecmp(temp, "yes");
        }
        else
        {
@@ -1099,7 +1343,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "VERSION"))
+      else if (!_cups_strcasecmp(token, "VERSION"))
       {
        if (get_token(fp, temp, sizeof(temp), &linenum))
        {
@@ -1129,7 +1373,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "RESOURCE"))
+      else if (!_cups_strcasecmp(token, "RESOURCE"))
       {
        /*
         * Resource name...
@@ -1142,7 +1386,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "OPERATION"))
+      else if (!_cups_strcasecmp(token, "OPERATION"))
       {
        /*
         * Operation...
@@ -1155,7 +1399,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
 
-       if ((op = ippOpValue(token)) < 0 && (op = strtol(token, NULL, 0)) == 0)
+       if ((op = ippOpValue(token)) == (ipp_op_t)-1 &&
+           (op = strtol(token, NULL, 0)) == 0)
        {
          print_fatal_error("Bad OPERATION code \"%s\" on line %d.", token,
                            linenum);
@@ -1163,7 +1408,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "GROUP"))
+      else if (!_cups_strcasecmp(token, "GROUP"))
       {
        /*
         * Attribute group...
@@ -1188,13 +1433,13 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
         group = value;
       }
-      else if (!strcasecmp(token, "DELAY"))
+      else if (!_cups_strcasecmp(token, "DELAY"))
       {
        /*
         * Delay before operation...
        */
 
-        int delay;
+        double delay;
 
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
@@ -1203,7 +1448,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
 
-       if ((delay = atoi(token)) <= 0)
+       if ((delay = _cupsStrScand(token, NULL, localeconv())) <= 0.0)
        {
          print_fatal_error("Bad DELAY value \"%s\" on line %d.", token,
                            linenum);
@@ -1213,12 +1458,12 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
        else
        {
          if (Output == _CUPS_OUTPUT_TEST)
-           printf("    [%d second delay]\n", delay);
+           printf("    [%g second delay]\n", delay);
 
-         sleep(delay);
+         usleep((int)(1000000.0 * delay));
        }
       }
-      else if (!strcasecmp(token, "ATTR"))
+      else if (!_cups_strcasecmp(token, "ATTR"))
       {
        /*
         * Attribute...
@@ -1254,19 +1499,50 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
        }
 
         expand_variables(vars, token, temp, sizeof(token));
+        attrptr = NULL;
 
         switch (value)
        {
          case IPP_TAG_BOOLEAN :
-             if (!strcasecmp(token, "true"))
-               ippAddBoolean(request, group, attr, 1);
+             if (!_cups_strcasecmp(token, "true"))
+               attrptr = ippAddBoolean(request, group, attr, 1);
               else
-               ippAddBoolean(request, group, attr, atoi(token));
+               attrptr = ippAddBoolean(request, group, attr, atoi(token));
              break;
 
          case IPP_TAG_INTEGER :
          case IPP_TAG_ENUM :
-             ippAddInteger(request, group, value, attr, atoi(token));
+             if (!strchr(token, ','))
+               attrptr = ippAddInteger(request, group, value, attr,
+                                       strtol(token, &tokenptr, 0));
+             else
+             {
+               int     values[100],    /* Values */
+                       num_values = 1; /* Number of values */
+
+               values[0] = strtol(token, &tokenptr, 10);
+               while (tokenptr && *tokenptr &&
+                      num_values < (int)(sizeof(values) / sizeof(values[0])))
+               {
+                 if (*tokenptr == ',')
+                   tokenptr ++;
+                 else if (!isdigit(*tokenptr & 255) && *tokenptr != '-')
+                   break;
+
+                 values[num_values] = strtol(tokenptr, &tokenptr, 0);
+                 num_values ++;
+               }
+
+               attrptr = ippAddIntegers(request, group, value, attr, num_values, values);
+             }
+
+             if (!tokenptr || *tokenptr)
+             {
+               print_fatal_error("Bad %s value \"%s\" on line %d.",
+                                 ippTagString(value), token, linenum);
+               pass = 0;
+               goto test_exit;
+             }
              break;
 
          case IPP_TAG_RESOLUTION :
@@ -1283,8 +1559,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
                }
 
                if (ptr <= token || xres <= 0 || yres <= 0 || !ptr ||
-               (strcasecmp(ptr, "dpi") && strcasecmp(ptr, "dpc") &&
-               strcasecmp(ptr, "other")))
+                   (_cups_strcasecmp(ptr, "dpi") && _cups_strcasecmp(ptr, "dpc") &&
+                    _cups_strcasecmp(ptr, "other")))
                {
                  print_fatal_error("Bad resolution value \"%s\" on line %d.",
                                    token, linenum);
@@ -1292,15 +1568,15 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
                  goto test_exit;
                }
 
-               if (!strcasecmp(ptr, "dpi"))
-                 ippAddResolution(request, group, attr, IPP_RES_PER_INCH,
-                                  xres, yres);
-               else if (!strcasecmp(ptr, "dpc"))
-                 ippAddResolution(request, group, attr, IPP_RES_PER_CM,
-                                  xres, yres);
+               if (!_cups_strcasecmp(ptr, "dpi"))
+                 attrptr = ippAddResolution(request, group, attr, IPP_RES_PER_INCH,
+                                            xres, yres);
+               else if (!_cups_strcasecmp(ptr, "dpc"))
+                 attrptr = ippAddResolution(request, group, attr, IPP_RES_PER_CM,
+                                            xres, yres);
                else
-                 ippAddResolution(request, group, attr, (ipp_res_t)0,
-                                  xres, yres);
+                 attrptr = ippAddResolution(request, group, attr, (ipp_res_t)0,
+                                            xres, yres);
              }
              break;
 
@@ -1325,8 +1601,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
                  goto test_exit;
                }
 
-               ippAddRanges(request, group, attr, num_vals / 2, lowers,
-                            uppers);
+               attrptr = ippAddRanges(request, group, attr, num_vals / 2, lowers,
+                                      uppers);
              }
              break;
 
@@ -1338,7 +1614,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
 
                 if (col)
                 {
-                 lastcol = ippAddCollection(request, group, attr, col);
+                 attrptr = lastcol = ippAddCollection(request, group, attr, col);
                  ippDelete(col);
                }
                else
@@ -1373,7 +1649,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          case IPP_TAG_LANGUAGE :
          case IPP_TAG_MIMETYPE :
              if (!strchr(token, ','))
-               ippAddString(request, group, value, attr, NULL, token);
+               attrptr = ippAddString(request, group, value, attr, NULL, token);
              else
              {
               /*
@@ -1395,13 +1671,21 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
                  num_values ++;
                }
 
-               ippAddStrings(request, group, value, attr, num_values,
-                             NULL, (const char **)values);
+               attrptr = ippAddStrings(request, group, value, attr, num_values,
+                                       NULL, (const char **)values);
              }
              break;
        }
+
+       if (!attrptr)
+       {
+         print_fatal_error("Unable to add attribute on line %d: %s", linenum,
+                           cupsLastErrorString());
+         pass = 0;
+         goto test_exit;
+       }
       }
-      else if (!strcasecmp(token, "FILE"))
+      else if (!_cups_strcasecmp(token, "FILE"))
       {
        /*
         * File...
@@ -1417,7 +1701,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
         expand_variables(vars, token, temp, sizeof(token));
        get_filename(testfile, filename, token, sizeof(filename));
       }
-      else if (!strcasecmp(token, "STATUS"))
+      else if (!_cups_strcasecmp(token, "STATUS"))
       {
        /*
         * Status...
@@ -1437,7 +1721,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
 
-       if ((statuses[num_statuses].status = ippErrorValue(token)) < 0 &&
+       if ((statuses[num_statuses].status = ippErrorValue(token))
+               == (ipp_status_t)-1 &&
            (statuses[num_statuses].status = strtol(token, NULL, 0)) == 0)
        {
          print_fatal_error("Bad STATUS code \"%s\" on line %d.", token,
@@ -1449,10 +1734,12 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
         last_status = statuses + num_statuses;
        num_statuses ++;
 
-       last_status->if_defined   = NULL;
-       last_status->if_not_defined = NULL;
+       last_status->if_defined      = NULL;
+       last_status->if_not_defined  = NULL;
+       last_status->repeat_match    = 0;
+       last_status->repeat_no_match = 0;
       }
-      else if (!strcasecmp(token, "EXPECT"))
+      else if (!_cups_strcasecmp(token, "EXPECT"))
       {
        /*
         * Expected attributes...
@@ -1490,7 +1777,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
         else
          last_expect->name = strdup(token);
       }
-      else if (!strcasecmp(token, "COUNT"))
+      else if (!_cups_strcasecmp(token, "COUNT"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
@@ -1516,7 +1803,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "DEFINE-MATCH"))
+      else if (!_cups_strcasecmp(token, "DEFINE-MATCH"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
@@ -1536,7 +1823,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "DEFINE-NO-MATCH"))
+      else if (!_cups_strcasecmp(token, "DEFINE-NO-MATCH"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
@@ -1556,7 +1843,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "DEFINE-VALUE"))
+      else if (!_cups_strcasecmp(token, "DEFINE-VALUE"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
@@ -1576,7 +1863,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "OF-TYPE"))
+      else if (!_cups_strcasecmp(token, "OF-TYPE"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
@@ -1596,7 +1883,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "IN-GROUP"))
+      else if (!_cups_strcasecmp(token, "IN-GROUP"))
       {
         ipp_tag_t      in_group;       /* IN-GROUP value */
 
@@ -1621,7 +1908,35 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "SAME-COUNT-AS"))
+      else if (!_cups_strcasecmp(token, "REPEAT-MATCH"))
+      {
+        if (last_status)
+          last_status->repeat_match = 1;
+       else if (last_expect)
+         last_expect->repeat_match = 1;
+       else
+       {
+         print_fatal_error("REPEAT-MATCH without a preceding EXPECT or STATUS "
+                           "on line %d.", linenum);
+         pass = 0;
+         goto test_exit;
+       }
+      }
+      else if (!_cups_strcasecmp(token, "REPEAT-NO-MATCH"))
+      {
+       if (last_status)
+         last_status->repeat_no_match = 1;
+       else if (last_expect)
+         last_expect->repeat_no_match = 1;
+       else
+       {
+         print_fatal_error("REPEAT-NO-MATCH without a preceding EXPECT or "
+                           "STATUS on ine %d.", linenum);
+         pass = 0;
+         goto test_exit;
+       }
+      }
+      else if (!_cups_strcasecmp(token, "SAME-COUNT-AS"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
@@ -1640,7 +1955,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "IF-DEFINED"))
+      else if (!_cups_strcasecmp(token, "IF-DEFINED"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
@@ -1661,7 +1976,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "IF-NOT-DEFINED"))
+      else if (!_cups_strcasecmp(token, "IF-NOT-DEFINED"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
@@ -1682,7 +1997,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "WITH-VALUE"))
+      else if (!_cups_strcasecmp(token, "WITH-VALUE"))
       {
        if (!get_token(fp, temp, sizeof(temp), &linenum))
        {
@@ -1730,7 +2045,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "DISPLAY"))
+      else if (!_cups_strcasecmp(token, "DISPLAY"))
       {
        /*
         * Display attributes...
@@ -1766,6 +2081,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
     * Submit the IPP request...
     */
 
+    TestCount ++;
+
     request->request.op.version[0]   = version / 10;
     request->request.op.version[1]   = version % 10;
     request->request.op.operation_id = op;
@@ -1779,27 +2096,36 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
       puts("<key>Operation</key>");
       print_xml_string("string", ippOpString(op));
       puts("<key>RequestAttributes</key>");
-      puts("<dict>");
-      for (attrptr = request->attrs; attrptr; attrptr = attrptr->next)
-       print_attr(attrptr);
-      puts("</dict>");
+      puts("<array>");
+      if (request->attrs)
+      {
+       puts("<dict>");
+       for (attrptr = request->attrs, group = attrptr->group_tag;
+            attrptr;
+            attrptr = attrptr->next)
+         print_attr(attrptr, &group);
+       puts("</dict>");
+      }
+      puts("</array>");
     }
     else if (Output == _CUPS_OUTPUT_TEST)
     {
       if (Verbosity)
       {
-        printf("    %s:\n", ippOpString(op));
+       printf("    %s:\n", ippOpString(op));
 
-        for (attrptr = request->attrs; attrptr; attrptr = attrptr->next)
-          print_attr(attrptr);
+       for (attrptr = request->attrs; attrptr; attrptr = attrptr->next)
+         print_attr(attrptr, NULL);
       }
 
-      printf("    %-69.69s [", name);
+      printf("    %-68.68s [", name);
       fflush(stdout);
     }
 
     if ((skip_previous && !prev_pass) || skip_test)
     {
+      SkipCount ++;
+
       ippDelete(request);
       request = NULL;
 
@@ -1819,230 +2145,387 @@ do_tests(_cups_vars_t *vars,          /* I - Variables */
       goto skip_error;
     }
 
-    if (transfer == _CUPS_TRANSFER_CHUNKED ||
-        (transfer == _CUPS_TRANSFER_AUTO && filename[0]))
+    do
     {
-     /*
-      * Send request using chunking...
-      */
+      status = HTTP_OK;
 
-      http_status_t status = cupsSendRequest(http, request, resource, 0);
+      if (transfer == _CUPS_TRANSFER_CHUNKED ||
+         (transfer == _CUPS_TRANSFER_AUTO && filename[0]))
+      {
+       /*
+       * Send request using chunking - a 0 length means "chunk".
+       */
 
-      if (status == HTTP_CONTINUE && filename[0])
+       length = 0;
+      }
+      else
       {
-        int    fd;                     /* File to send */
-        char   buffer[8192];           /* Copy buffer */
-        ssize_t        bytes;                  /* Bytes read/written */
+       /*
+       * Send request using content length...
+       */
 
-        if ((fd = open(filename, O_RDONLY | O_BINARY)) >= 0)
-        {
-          while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
-            if ((status = cupsWriteRequestData(http, buffer,
-                                               bytes)) != HTTP_CONTINUE)
-              break;
-        }
-        else
+       length = ippLength(request);
+
+       if (filename[0] && (reqfile = cupsFileOpen(filename, "r")) != NULL)
        {
-         snprintf(buffer, sizeof(buffer), "%s: %s", filename, strerror(errno));
-         _cupsSetError(IPP_INTERNAL_ERROR, buffer, 0);
+        /*
+         * Read the file to get the uncompressed file size...
+         */
+
+         while ((bytes = cupsFileRead(reqfile, buffer, sizeof(buffer))) > 0)
+           length += bytes;
 
-          status = HTTP_ERROR;
+         cupsFileClose(reqfile);
        }
       }
 
-      ippDelete(request);
+     /*
+      * Send the request...
+      */
 
-      if (status == HTTP_CONTINUE)
-       response = cupsGetResponse(http, resource);
-      else
-       response = NULL;
-    }
-    else if (filename[0])
-      response = cupsDoFileRequest(http, request, resource, filename);
-    else
-      response = cupsDoRequest(http, request, resource);
+      response    = NULL;
+      repeat_test = 0;
+      prev_pass   = 1;
+
+      if (status != HTTP_ERROR)
+      {
+       while (!response && !Cancel && prev_pass)
+       {
+         status = cupsSendRequest(http, request, resource, length);
 
-    request   = NULL;
-    prev_pass = 1;
+         if (!Cancel && status == HTTP_CONTINUE &&
+             request->state == IPP_DATA && filename[0])
+         {
+           if ((reqfile = cupsFileOpen(filename, "r")) != NULL)
+           {
+             while (!Cancel &&
+                    (bytes = cupsFileRead(reqfile, buffer,
+                                          sizeof(buffer))) > 0)
+               if ((status = cupsWriteRequestData(http, buffer,
+                                                  bytes)) != HTTP_CONTINUE)
+                 break;
 
-    if (!response)
-      prev_pass = pass = 0;
-    else
-    {
-      if (http->version != HTTP_1_1)
-        prev_pass = pass = 0;
+             cupsFileClose(reqfile);
+           }
+           else
+           {
+             snprintf(buffer, sizeof(buffer), "%s: %s", filename,
+                      strerror(errno));
+             _cupsSetError(IPP_INTERNAL_ERROR, buffer, 0);
 
-      if (response->request.status.request_id != request_id)
-        prev_pass = pass = 0;
+             status = HTTP_ERROR;
+           }
+         }
 
-      if (version &&
-          (response->request.status.version[0] != (version / 10) ||
-          response->request.status.version[1] != (version % 10)))
-        prev_pass = pass = 0;
+        /*
+         * Get the server's response...
+         */
 
-      if ((attrptr = ippFindAttribute(response, "job-id",
-                                      IPP_TAG_INTEGER)) != NULL)
-      {
-        snprintf(temp, sizeof(temp), "%d", attrptr->values[0].integer);
-       set_variable(vars, "job-id", temp);
-      }
+         if (!Cancel && status != HTTP_ERROR)
+         {
+           response = cupsGetResponse(http, resource);
+           status   = httpGetStatus(http);
+         }
 
-      if ((attrptr = ippFindAttribute(response, "job-uri",
-                                      IPP_TAG_URI)) != NULL)
-       set_variable(vars, "job-uri", attrptr->values[0].string.text);
+         if (!Cancel && status == HTTP_ERROR &&
+#ifdef WIN32
+             http->error != WSAETIMEDOUT)
+#else
+             http->error != ETIMEDOUT)
+#endif /* WIN32 */
+         {
+           if (httpReconnect(http))
+             prev_pass = 0;
+         }
+         else if (status == HTTP_ERROR)
+         {
+           if (!Cancel)
+             httpReconnect(http);
 
-      if ((attrptr = ippFindAttribute(response, "notify-subscription-id",
-                                      IPP_TAG_INTEGER)) != NULL)
-      {
-        snprintf(temp, sizeof(temp), "%d", attrptr->values[0].integer);
-       set_variable(vars, "notify-subscription-id", temp);
+           prev_pass = 0;
+         }
+       }
       }
 
-      attrptr = response->attrs;
-      if (!attrptr || !attrptr->name ||
-         attrptr->value_tag != IPP_TAG_CHARSET ||
-         attrptr->group_tag != IPP_TAG_OPERATION ||
-         attrptr->num_values != 1 ||
-          strcmp(attrptr->name, "attributes-charset"))
-        prev_pass = pass = 0;
-
-      if (attrptr)
+     /*
+      * Check results of request...
+      */
+
+      if (!response)
+       prev_pass = pass = 0;
+      else
       {
-        attrptr = attrptr->next;
-       if (!attrptr || !attrptr->name ||
-           attrptr->value_tag != IPP_TAG_LANGUAGE ||
-           attrptr->group_tag != IPP_TAG_OPERATION ||
-           attrptr->num_values != 1 ||
-           strcmp(attrptr->name, "attributes-natural-language"))
+       if (http->version != HTTP_1_1)
          prev_pass = pass = 0;
-      }
 
-      if ((attrptr = ippFindAttribute(response, "status-message",
-                                      IPP_TAG_ZERO)) != NULL &&
-          (attrptr->value_tag != IPP_TAG_TEXT ||
-          attrptr->group_tag != IPP_TAG_OPERATION ||
-          attrptr->num_values != 1 ||
-          (attrptr->value_tag == IPP_TAG_TEXT &&
-           strlen(attrptr->values[0].string.text) > 255)))
-       prev_pass = pass = 0;
+       if (response->state != IPP_DATA)
+         prev_pass = pass = 0;
 
-      if ((attrptr = ippFindAttribute(response, "detailed-status-message",
-                                      IPP_TAG_ZERO)) != NULL &&
-          (attrptr->value_tag != IPP_TAG_TEXT ||
-          attrptr->group_tag != IPP_TAG_OPERATION ||
-          attrptr->num_values != 1 ||
-          (attrptr->value_tag == IPP_TAG_TEXT &&
-           strlen(attrptr->values[0].string.text) > 1023)))
-       prev_pass = pass = 0;
+       if (response->request.status.request_id != request_id)
+         prev_pass = pass = 0;
 
-      for (attrptr = response->attrs, group = attrptr->group_tag;
-           attrptr;
-          attrptr = attrptr->next)
-      {
-        if (attrptr->group_tag < group && attrptr->group_tag != IPP_TAG_ZERO)
-       {
+       if (version &&
+           (response->request.status.version[0] != (version / 10) ||
+            response->request.status.version[1] != (version % 10)))
          prev_pass = pass = 0;
-         break;
-       }
 
-        if (!validate_attr(attrptr, 0))
+       if ((attrptr = ippFindAttribute(response, "job-id",
+                                       IPP_TAG_INTEGER)) != NULL)
        {
-         prev_pass = pass = 0;
-         break;
+         snprintf(temp, sizeof(temp), "%d", attrptr->values[0].integer);
+         set_variable(vars, "job-id", temp);
        }
-      }
 
-      for (i = 0; i < num_statuses; i ++)
-      {
-        if (statuses[i].if_defined &&
-           !get_variable(vars, statuses[i].if_defined))
-         continue;
+       if ((attrptr = ippFindAttribute(response, "job-uri",
+                                       IPP_TAG_URI)) != NULL)
+         set_variable(vars, "job-uri", attrptr->values[0].string.text);
 
-        if (statuses[i].if_not_defined &&
-           get_variable(vars, statuses[i].if_not_defined))
-         continue;
+       if ((attrptr = ippFindAttribute(response, "notify-subscription-id",
+                                       IPP_TAG_INTEGER)) != NULL)
+       {
+         snprintf(temp, sizeof(temp), "%d", attrptr->values[0].integer);
+         set_variable(vars, "notify-subscription-id", temp);
+       }
 
-        if (response->request.status.status_code == statuses[i].status)
-         break;
-      }
+       attrptr = response->attrs;
+       if (!attrptr || !attrptr->name ||
+           attrptr->value_tag != IPP_TAG_CHARSET ||
+           attrptr->group_tag != IPP_TAG_OPERATION ||
+           attrptr->num_values != 1 ||
+           strcmp(attrptr->name, "attributes-charset"))
+         prev_pass = pass = 0;
 
-      if (i == num_statuses && num_statuses > 0)
-       prev_pass = pass = 0;
-      else
-      {
-        for (i = num_expects, expect = expects; i > 0; i --, expect ++)
-        {
-          if (expect->if_defined && !get_variable(vars, expect->if_defined))
-            continue;
+       if (attrptr)
+       {
+         attrptr = attrptr->next;
+         if (!attrptr || !attrptr->name ||
+             attrptr->value_tag != IPP_TAG_LANGUAGE ||
+             attrptr->group_tag != IPP_TAG_OPERATION ||
+             attrptr->num_values != 1 ||
+             strcmp(attrptr->name, "attributes-natural-language"))
+           prev_pass = pass = 0;
+       }
+
+       if ((attrptr = ippFindAttribute(response, "status-message",
+                                       IPP_TAG_ZERO)) != NULL &&
+           (attrptr->value_tag != IPP_TAG_TEXT ||
+            attrptr->group_tag != IPP_TAG_OPERATION ||
+            attrptr->num_values != 1 ||
+            (attrptr->value_tag == IPP_TAG_TEXT &&
+             strlen(attrptr->values[0].string.text) > 255)))
+         prev_pass = pass = 0;
+
+       if ((attrptr = ippFindAttribute(response, "detailed-status-message",
+                                       IPP_TAG_ZERO)) != NULL &&
+           (attrptr->value_tag != IPP_TAG_TEXT ||
+            attrptr->group_tag != IPP_TAG_OPERATION ||
+            attrptr->num_values != 1 ||
+            (attrptr->value_tag == IPP_TAG_TEXT &&
+             strlen(attrptr->values[0].string.text) > 1023)))
+         prev_pass = pass = 0;
+
+       a = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+
+       for (attrptr = response->attrs, group = attrptr->group_tag;
+            attrptr;
+            attrptr = attrptr->next)
+       {
+         if (attrptr->group_tag != group)
+         {
+           cupsArrayClear(a);
+
+            switch (attrptr->group_tag)
+            {
+              case IPP_TAG_ZERO :
+                  break;
+
+              case IPP_TAG_OPERATION :
+                  prev_pass = pass = 0;
+                  break;
+
+              case IPP_TAG_UNSUPPORTED_GROUP :
+                  if (group != IPP_TAG_OPERATION)
+                   prev_pass = pass = 0;
+                  break;
+
+              case IPP_TAG_JOB :
+              case IPP_TAG_PRINTER :
+                  if (group != IPP_TAG_OPERATION &&
+                      group != IPP_TAG_UNSUPPORTED_GROUP)
+                   prev_pass = pass = 0;
+                  break;
+
+              case IPP_TAG_SUBSCRIPTION :
+                  if (group > attrptr->group_tag &&
+                      group != IPP_TAG_DOCUMENT)
+                   prev_pass = pass = 0;
+                  break;
+
+              default :
+                  if (group > attrptr->group_tag)
+                   prev_pass = pass = 0;
+                  break;
+            }
+
+            if (!pass)
+             break;
+
+           if (attrptr->group_tag != IPP_TAG_ZERO)
+             group = attrptr->group_tag;
+         }
+
+         if (!validate_attr(attrptr, 0))
+         {
+           prev_pass = pass = 0;
+           break;
+         }
+
+          if (attrptr->name)
+          {
+            if (cupsArrayFind(a, attrptr->name))
+            {
+              prev_pass = pass = 0;
+              break;
+            }
+
+            cupsArrayAdd(a, attrptr->name);
+          }
+       }
+
+        cupsArrayDelete(a);
+
+       for (i = 0; i < num_statuses; i ++)
+       {
+         if (statuses[i].if_defined &&
+             !get_variable(vars, statuses[i].if_defined))
+           continue;
+
+         if (statuses[i].if_not_defined &&
+             get_variable(vars, statuses[i].if_not_defined))
+           continue;
+
+         if (response->request.status.status_code == statuses[i].status)
+         {
+           if (statuses[i].repeat_match)
+             repeat_test = 1;
+
+           break;
+         }
+         else if (statuses[i].repeat_no_match)
+           repeat_test = 1;
+       }
 
-          if (expect->if_not_defined &&
+       if (i == num_statuses && num_statuses > 0)
+         prev_pass = pass = 0;
+
+       for (i = num_expects, expect = expects; i > 0; i --, expect ++)
+       {
+         if (expect->if_defined && !get_variable(vars, expect->if_defined))
+           continue;
+
+         if (expect->if_not_defined &&
              get_variable(vars, expect->if_not_defined))
-            continue;
+           continue;
 
-          found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO);
+         found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO);
 
-          if ((found && expect->not_expect) ||
+         if ((found && expect->not_expect) ||
              (!found && !(expect->not_expect || expect->optional)) ||
              (found && !expect_matches(expect, found->value_tag)) ||
              (found && expect->in_group &&
               found->group_tag != expect->in_group))
-          {
+         {
            if (expect->define_no_match)
              set_variable(vars, expect->define_no_match, "1");
-           else if (!expect->define_match)
+           else if (!expect->define_match && !expect->define_value)
              prev_pass = pass = 0;
 
-           continue;
-          }
+           if (expect->repeat_no_match)
+             repeat_test = 1;
 
-          if (found &&
-             !with_value(expect->with_value, expect->with_regex, found, 0))
-          {
+           continue;
+         }
+
+         if (found)
+           ippAttributeString(found, buffer, sizeof(buffer));
+
+         if (found &&
+             !with_value(expect->with_value, expect->with_regex, found, 0,
+                         buffer, sizeof(buffer)))
+         {
            if (expect->define_no_match)
              set_variable(vars, expect->define_no_match, "1");
-           else if (!expect->define_match)
+           else if (!expect->define_match && !expect->define_value)
              prev_pass = pass = 0;
 
-            continue;
-          }
+           if (expect->repeat_no_match)
+             repeat_test = 1;
 
-          if (found && expect->count > 0 && found->num_values != expect->count)
+           continue;
+         }
+
+         if (found && expect->count > 0 &&
+             found->num_values != expect->count)
          {
            if (expect->define_no_match)
              set_variable(vars, expect->define_no_match, "1");
-           else if (!expect->define_match)
+           else if (!expect->define_match && !expect->define_value)
              prev_pass = pass = 0;
 
-            continue;
+           if (expect->repeat_no_match)
+             repeat_test = 1;
+
+           continue;
          }
 
-          if (found && expect->same_count_as)
-          {
-            attrptr = ippFindAttribute(response, expect->same_count_as,
-                                       IPP_TAG_ZERO);
+         if (found && expect->same_count_as)
+         {
+           attrptr = ippFindAttribute(response, expect->same_count_as,
+                                      IPP_TAG_ZERO);
 
-            if (!attrptr || attrptr->num_values != found->num_values)
-            {
+           if (!attrptr || attrptr->num_values != found->num_values)
+           {
              if (expect->define_no_match)
                set_variable(vars, expect->define_no_match, "1");
-             else if (!expect->define_match)
+             else if (!expect->define_match && !expect->define_value)
                prev_pass = pass = 0;
 
-              continue;
-            }
-          }
+             if (expect->repeat_no_match)
+               repeat_test = 1;
+
+             continue;
+           }
+         }
 
          if (found && expect->define_match)
            set_variable(vars, expect->define_match, "1");
 
          if (found && expect->define_value)
-         {
-           _ippAttrString(found, token, sizeof(token));
-           set_variable(vars, expect->define_value, token);
-         }
-        }
+           set_variable(vars, expect->define_value, buffer);
+
+         if (found && expect->repeat_match)
+           repeat_test = 1;
+       }
       }
+
+     /*
+      * If we are going to repeat this test, sleep 1 second so we don't flood
+      * the printer with requests...
+      */
+
+      if (repeat_test)
+        sleep(1);
     }
+    while (repeat_test);
+
+    ippDelete(request);
+
+    request = NULL;
+
+    if (prev_pass)
+      PassCount ++;
+    else
+      FailCount ++;
 
     if (Output == _CUPS_OUTPUT_PLIST)
     {
@@ -2051,99 +2534,80 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
       puts("<key>StatusCode</key>");
       print_xml_string("string", ippErrorString(cupsLastError()));
       puts("<key>ResponseAttributes</key>");
+      puts("<array>");
       puts("<dict>");
-      for (attrptr = response ? response->attrs : NULL;
+      for (attrptr = response ? response->attrs : NULL,
+               group = attrptr ? attrptr->group_tag : IPP_TAG_ZERO;
           attrptr;
           attrptr = attrptr->next)
-       print_attr(attrptr);
+       print_attr(attrptr, &group);
       puts("</dict>");
+      puts("</array>");
     }
     else if (Output == _CUPS_OUTPUT_TEST)
     {
       puts(prev_pass ? "PASS]" : "FAIL]");
 
-      if (Verbosity && response)
+      if (!prev_pass || (Verbosity && response))
       {
        printf("        RECEIVED: %lu bytes in response\n",
               (unsigned long)ippLength(response));
-       printf("        status-code = %x (%s)\n", cupsLastError(),
-              ippErrorString(cupsLastError()));
+       printf("        status-code = %s (%s)\n", ippErrorString(cupsLastError()),
+              cupsLastErrorString());
 
-       for (attrptr = response->attrs;
-            attrptr != NULL;
-            attrptr = attrptr->next)
-       {
-         print_attr(attrptr);
+        if (response)
+        {
+         for (attrptr = response->attrs;
+              attrptr != NULL;
+              attrptr = attrptr->next)
+           print_attr(attrptr, NULL);
        }
       }
     }
     else if (!prev_pass)
       fprintf(stderr, "%s\n", cupsLastErrorString());
 
-    if (prev_pass && Output != _CUPS_OUTPUT_PLIST && 
-        Output != _CUPS_OUTPUT_QUIET && !Verbosity && num_displayed > 0)
+    if (prev_pass && Output >= _CUPS_OUTPUT_LIST && !Verbosity &&
+        num_displayed > 0)
     {
-      if (Output >= _CUPS_OUTPUT_LIST)
+      size_t   width;                  /* Length of value */
+
+      for (i = 0; i < num_displayed; i ++)
       {
-       size_t  width;                  /* Length of value */
+       widths[i] = strlen(displayed[i]);
 
+       for (attrptr = ippFindAttribute(response, displayed[i], IPP_TAG_ZERO);
+            attrptr;
+            attrptr = ippFindNextAttribute(response, displayed[i],
+                                           IPP_TAG_ZERO))
+       {
+         width = ippAttributeString(attrptr, NULL, 0);
+         if (width > widths[i])
+           widths[i] = width;
+       }
+      }
 
-        for (i = 0; i < num_displayed; i ++)
-        {
-          widths[i] = strlen(displayed[i]);
+      if (Output == _CUPS_OUTPUT_CSV)
+       print_csv(NULL, num_displayed, displayed, widths);
+      else
+       print_line(NULL, num_displayed, displayed, widths);
 
-          for (attrptr = ippFindAttribute(response, displayed[i], IPP_TAG_ZERO);
-               attrptr;
-               attrptr = ippFindNextAttribute(response, displayed[i],
-                                              IPP_TAG_ZERO))
-          {
-            width = _ippAttrString(attrptr, NULL, 0);
-            if (width > widths[i])
-              widths[i] = width;
-          }
-        }
+      attrptr = response->attrs;
 
-        if (Output == _CUPS_OUTPUT_CSV)
-         print_csv(NULL, num_displayed, displayed, widths);
-       else
-         print_line(NULL, num_displayed, displayed, widths);
+      while (attrptr)
+      {
+       while (attrptr && attrptr->group_tag <= IPP_TAG_OPERATION)
+         attrptr = attrptr->next;
 
-        attrptr = response->attrs;
+       if (attrptr)
+       {
+         if (Output == _CUPS_OUTPUT_CSV)
+           print_csv(attrptr, num_displayed, displayed, widths);
+         else
+           print_line(attrptr, num_displayed, displayed, widths);
 
-        while (attrptr)
-        {
-         while (attrptr && attrptr->group_tag <= IPP_TAG_OPERATION)
+         while (attrptr && attrptr->group_tag > IPP_TAG_OPERATION)
            attrptr = attrptr->next;
-
-          if (attrptr)
-          {
-            if (Output == _CUPS_OUTPUT_CSV)
-             print_csv(attrptr, num_displayed, displayed, widths);
-           else
-             print_line(attrptr, num_displayed, displayed, widths);
-
-            while (attrptr && attrptr->group_tag > IPP_TAG_OPERATION)
-              attrptr = attrptr->next;
-          }
-        }
-      }
-      else
-      {
-       for (attrptr = response->attrs;
-            attrptr != NULL;
-            attrptr = attrptr->next)
-       {
-         if (attrptr->name)
-         {
-           for (i = 0; i < num_displayed; i ++)
-           {
-             if (!strcmp(displayed[i], attrptr->name))
-             {
-               print_attr(attrptr);
-               break;
-             }
-           }
-         }
        }
       }
     }
@@ -2165,6 +2629,10 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
                         cupsLastErrorString());
       else
       {
+       if (response->state != IPP_DATA)
+         print_test_error("Missing end-of-attributes-tag in response "
+                          "(RFC 2910 section 3.5.1)");
+
        if (version &&
            (response->request.status.version[0] != (version / 10) ||
             response->request.status.version[1] != (version % 10)))
@@ -2266,18 +2734,75 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
                             (int)strlen(attrptr->values[0].string.text));
         }
 
+       a = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+
        for (attrptr = response->attrs, group = attrptr->group_tag;
             attrptr;
             attrptr = attrptr->next)
        {
-         if (attrptr->group_tag < group && attrptr->group_tag != IPP_TAG_ZERO)
-           print_test_error("Attribute groups out of order (%s < %s)",
-                            ippTagString(attrptr->group_tag),
-                            ippTagString(group));
+         if (attrptr->group_tag != group)
+         {
+           cupsArrayClear(a);
+
+            switch (attrptr->group_tag)
+            {
+              case IPP_TAG_ZERO :
+                  break;
+
+              case IPP_TAG_OPERATION :
+                  prev_pass = pass = 0;
+                  break;
+
+              case IPP_TAG_UNSUPPORTED_GROUP :
+                  if (group != IPP_TAG_OPERATION)
+                   print_test_error("Attribute groups out of order (%s < %s)",
+                                    ippTagString(attrptr->group_tag),
+                                    ippTagString(group));
+                  break;
+
+              case IPP_TAG_JOB :
+              case IPP_TAG_PRINTER :
+                  if (group != IPP_TAG_OPERATION &&
+                      group != IPP_TAG_UNSUPPORTED_GROUP)
+                   print_test_error("Attribute groups out of order (%s < %s)",
+                                    ippTagString(attrptr->group_tag),
+                                    ippTagString(group));
+                  break;
+
+              case IPP_TAG_SUBSCRIPTION :
+                  if (group > attrptr->group_tag &&
+                      group != IPP_TAG_DOCUMENT)
+                   print_test_error("Attribute groups out of order (%s < %s)",
+                                    ippTagString(attrptr->group_tag),
+                                    ippTagString(group));
+                  break;
+
+              default :
+                  if (group > attrptr->group_tag)
+                   print_test_error("Attribute groups out of order (%s < %s)",
+                                    ippTagString(attrptr->group_tag),
+                                    ippTagString(group));
+                  break;
+            }
+
+           if (attrptr->group_tag != IPP_TAG_ZERO)
+             group = attrptr->group_tag;
+         }
 
          validate_attr(attrptr, 1);
+
+          if (attrptr->name)
+          {
+            if (cupsArrayFind(a, attrptr->name))
+              print_test_error("Duplicate \"%s\" attribute in %s group",
+                               attrptr->name, ippTagString(group));
+
+            cupsArrayAdd(a, attrptr->name);
+          }
        }
 
+        cupsArrayDelete(a);
+
        for (i = 0; i < num_statuses; i ++)
        {
          if (statuses[i].if_defined &&
@@ -2294,14 +2819,31 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
        if (i == num_statuses && num_statuses > 0)
        {
-         print_test_error("Bad status-code (%s)",
-                          ippErrorString(cupsLastError()));
-         print_test_error("status-message=\"%s\"", cupsLastErrorString());
+         for (i = 0; i < num_statuses; i ++)
+         {
+           if (statuses[i].if_defined &&
+               !get_variable(vars, statuses[i].if_defined))
+             continue;
+
+           if (statuses[i].if_not_defined &&
+               get_variable(vars, statuses[i].if_not_defined))
+             continue;
+
+           print_test_error("EXPECTED: STATUS %s (got %s)",
+                            ippErrorString(statuses[i].status),
+                            ippErrorString(cupsLastError()));
+         }
+
+         if ((attrptr = ippFindAttribute(response, "status-message",
+                                         IPP_TAG_TEXT)) != NULL)
+           print_test_error("status-message=\"%s\"",
+                            attrptr->values[0].string.text);
         }
 
        for (i = num_expects, expect = expects; i > 0; i --, expect ++)
        {
-         if (expect->define_match || expect->define_no_match)
+         if (expect->define_match || expect->define_no_match ||
+             expect->define_value)
            continue;
 
          if (expect->if_defined && !get_variable(vars, expect->if_defined))
@@ -2329,7 +2871,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
                               expect->name, ippTagString(expect->in_group),
                               ippTagString(found->group_tag));
 
-           if (!with_value(expect->with_value, expect->with_regex, found, 0))
+           if (!with_value(expect->with_value, expect->with_regex, found, 0,
+                           buffer, sizeof(buffer)))
            {
              if (expect->with_regex)
                print_test_error("EXPECTED: %s WITH-VALUE /%s/",
@@ -2338,7 +2881,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
                print_test_error("EXPECTED: %s WITH-VALUE \"%s\"",
                                 expect->name, expect->with_value);
 
-             with_value(expect->with_value, expect->with_regex, found, 1);
+             with_value(expect->with_value, expect->with_regex, found, 1,
+                        buffer, sizeof(buffer));
            }
 
            if (expect->count > 0 && found->num_values != expect->count)
@@ -2369,10 +2913,33 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
        puts("</array>");
     }
 
+    if (num_displayed > 0 && !Verbosity &&
+        (Output == _CUPS_OUTPUT_TEST || Output == _CUPS_OUTPUT_PLIST))
+    {
+      for (attrptr = response->attrs;
+          attrptr != NULL;
+          attrptr = attrptr->next)
+      {
+       if (attrptr->name)
+       {
+         for (i = 0; i < num_displayed; i ++)
+         {
+           if (!strcmp(displayed[i], attrptr->name))
+           {
+             print_attr(attrptr, NULL);
+             break;
+           }
+         }
+       }
+      }
+    }
+
+    skip_error:
+
     if (Output == _CUPS_OUTPUT_PLIST)
       puts("</dict>");
 
-    skip_error:
+    fflush(stdout);
 
     ippDelete(response);
     response = NULL;
@@ -2667,7 +3234,7 @@ get_collection(_cups_vars_t *vars,        /* I  - Variables */
 
        if ((tempcol = realloc(lastcol, sizeof(ipp_attribute_t) +
                                        (lastcol->num_values + 1) *
-                                       sizeof(ipp_value_t))) == NULL)
+                                       sizeof(_ipp_value_t))) == NULL)
        {
          print_fatal_error("Unable to allocate memory on line %d.", *linenum);
          goto col_error;
@@ -2693,7 +3260,7 @@ get_collection(_cups_vars_t *vars,        /* I  - Variables */
       else
        goto col_error;
     }
-    else if (!strcasecmp(token, "MEMBER"))
+    else if (!_cups_strcasecmp(token, "MEMBER"))
     {
      /*
       * Attribute...
@@ -2731,7 +3298,7 @@ get_collection(_cups_vars_t *vars,        /* I  - Variables */
       switch (value)
       {
        case IPP_TAG_BOOLEAN :
-           if (!strcasecmp(token, "true"))
+           if (!_cups_strcasecmp(token, "true"))
              ippAddBoolean(col, IPP_TAG_ZERO, attr, 1);
            else
              ippAddBoolean(col, IPP_TAG_ZERO, attr, atoi(token));
@@ -2749,18 +3316,18 @@ get_collection(_cups_vars_t *vars,      /* I  - Variables */
              char      units[6];       /* Units */
 
              if (sscanf(token, "%dx%d%5s", &xres, &yres, units) != 3 ||
-                 (strcasecmp(units, "dpi") && strcasecmp(units, "dpc") &&
-                  strcasecmp(units, "other")))
+                 (_cups_strcasecmp(units, "dpi") && _cups_strcasecmp(units, "dpc") &&
+                  _cups_strcasecmp(units, "other")))
              {
                print_fatal_error("Bad resolution value \"%s\" on line %d.",
                                  token, *linenum);
                goto col_error;
              }
 
-             if (!strcasecmp(units, "dpi"))
+             if (!_cups_strcasecmp(units, "dpi"))
                ippAddResolution(col, IPP_TAG_ZERO, attr, xres, yres,
                                 IPP_RES_PER_INCH);
-             else if (!strcasecmp(units, "dpc"))
+             else if (!_cups_strcasecmp(units, "dpc"))
                ippAddResolution(col, IPP_TAG_ZERO, attr, xres, yres,
                                 IPP_RES_PER_CM);
              else
@@ -3051,18 +3618,17 @@ get_variable(_cups_vars_t *vars,        /* I - Variables */
 static char *                          /* O - ISO 8601 date/time string */
 iso_date(ipp_uchar_t *date)            /* I - IPP (RFC 1903) date/time value */
 {
-  unsigned     year = (date[0] << 8) + date[1];
-                                       /* Year */
+  time_t       utctime;                /* UTC time since 1970 */
+  struct tm    *utcdate;               /* UTC date/time */
   static char  buffer[255];            /* String buffer */
 
 
-  if (date[9] == 0 && date[10] == 0)
-    snprintf(buffer, sizeof(buffer), "%04u-%02u-%02uT%02u:%02u:%02uZ",
-            year, date[2], date[3], date[4], date[5], date[6]);
-  else
-    snprintf(buffer, sizeof(buffer), "%04u-%02u-%02uT%02u:%02u:%02u%c%02u%02u",
-            year, date[2], date[3], date[4], date[5], date[6],
-            date[8], date[9], date[10]);
+  utctime = ippDateToTime(date);
+  utcdate = gmtime(&utctime);
+
+  snprintf(buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%02dZ",
+          utcdate->tm_year + 1900, utcdate->tm_mon + 1, utcdate->tm_mday,
+          utcdate->tm_hour, utcdate->tm_min, utcdate->tm_sec);
 
   return (buffer);
 }
@@ -3086,7 +3652,8 @@ password_cb(const char *prompt)           /* I - Prompt (unused) */
  */
 
 static void
-print_attr(ipp_attribute_t *attr)      /* I - Attribute to print */
+print_attr(ipp_attribute_t *attr,      /* I  - Attribute to print */
+           ipp_tag_t       *group)     /* IO - Current group */
 {
   int                  i;              /* Looping var */
   ipp_attribute_t      *colattr;       /* Collection attribute */
@@ -3094,155 +3661,164 @@ print_attr(ipp_attribute_t *attr)     /* I - Attribute to print */
 
   if (Output == _CUPS_OUTPUT_PLIST)
   {
-    if (!attr->name)
+    if (!attr->name || (group && *group != attr->group_tag))
     {
-      printf("<key>%s</key>\n<true />\n", ippTagString(attr->group_tag));
-      return;
+      puts("</dict>");
+      puts("<dict>");
+
+      if (group)
+        *group = attr->group_tag;
     }
 
+    if (!attr->name)
+      return;
+
     print_xml_string("key", attr->name);
     if (attr->num_values > 1)
       puts("<array>");
-  }
-  else if (Output == _CUPS_OUTPUT_TEST)
-  {
-    if (!attr->name)
-    {
-      puts("        -- separator --");
-      return;
-    }
 
-    printf("        %s (%s%s) = ", attr->name,
-          attr->num_values > 1 ? "1setOf " : "",
-          ippTagString(attr->value_tag));
-  }
+    switch (attr->value_tag)
+    {
+      case IPP_TAG_INTEGER :
+      case IPP_TAG_ENUM :
+         for (i = 0; i < attr->num_values; i ++)
+           if (Output == _CUPS_OUTPUT_PLIST)
+             printf("<integer>%d</integer>\n", attr->values[i].integer);
+           else
+             printf("%d ", attr->values[i].integer);
+         break;
 
-  switch (attr->value_tag)
-  {
-    case IPP_TAG_INTEGER :
-    case IPP_TAG_ENUM :
-       for (i = 0; i < attr->num_values; i ++)
-         if (Output == _CUPS_OUTPUT_PLIST)
-           printf("<integer>%d</integer>\n", attr->values[i].integer);
-         else
-           printf("%d ", attr->values[i].integer);
-       break;
+      case IPP_TAG_BOOLEAN :
+         for (i = 0; i < attr->num_values; i ++)
+           if (Output == _CUPS_OUTPUT_PLIST)
+             puts(attr->values[i].boolean ? "<true />" : "<false />");
+           else if (attr->values[i].boolean)
+             fputs("true ", stdout);
+           else
+             fputs("false ", stdout);
+         break;
 
-    case IPP_TAG_BOOLEAN :
-       for (i = 0; i < attr->num_values; i ++)
-         if (Output == _CUPS_OUTPUT_PLIST)
-           puts(attr->values[i].boolean ? "<true />" : "<false />");
-         else if (attr->values[i].boolean)
-           fputs("true ", stdout);
-         else
-           fputs("false ", stdout);
-       break;
+      case IPP_TAG_RANGE :
+         for (i = 0; i < attr->num_values; i ++)
+           if (Output == _CUPS_OUTPUT_PLIST)
+             printf("<dict><key>lower</key><integer>%d</integer>"
+                    "<key>upper</key><integer>%d</integer></dict>\n",
+                    attr->values[i].range.lower, attr->values[i].range.upper);
+           else
+             printf("%d-%d ", attr->values[i].range.lower,
+                    attr->values[i].range.upper);
+         break;
 
-    case IPP_TAG_RANGE :
-       for (i = 0; i < attr->num_values; i ++)
-         if (Output == _CUPS_OUTPUT_PLIST)
-           printf("<dict><key>lower</key><integer>%d</integer>"
-                  "<key>upper</key><integer>%d</integer></dict>\n",
-                  attr->values[i].range.lower, attr->values[i].range.upper);
-         else
-           printf("%d-%d ", attr->values[i].range.lower,
-                  attr->values[i].range.upper);
-       break;
+      case IPP_TAG_RESOLUTION :
+         for (i = 0; i < attr->num_values; i ++)
+           if (Output == _CUPS_OUTPUT_PLIST)
+             printf("<dict><key>xres</key><integer>%d</integer>"
+                    "<key>yres</key><integer>%d</integer>"
+                    "<key>units</key><string>%s</string></dict>\n",
+                    attr->values[i].resolution.xres,
+                    attr->values[i].resolution.yres,
+                    attr->values[i].resolution.units == IPP_RES_PER_INCH ?
+                        "dpi" : "dpc");
+           else
+             printf("%dx%d%s ", attr->values[i].resolution.xres,
+                    attr->values[i].resolution.yres,
+                    attr->values[i].resolution.units == IPP_RES_PER_INCH ?
+                        "dpi" : "dpc");
+         break;
 
-    case IPP_TAG_RESOLUTION :
-       for (i = 0; i < attr->num_values; i ++)
-         if (Output == _CUPS_OUTPUT_PLIST)
-           printf("<dict><key>xres</key><integer>%d</integer>"
-                  "<key>yres</key><integer>%d</integer>"
-                  "<key>units</key><string>%s</string></dict>\n",
-                  attr->values[i].resolution.xres,
-                  attr->values[i].resolution.yres,
-                  attr->values[i].resolution.units == IPP_RES_PER_INCH ?
-                      "dpi" : "dpc");
-         else
-           printf("%dx%d%s ", attr->values[i].resolution.xres,
-                  attr->values[i].resolution.yres,
-                  attr->values[i].resolution.units == IPP_RES_PER_INCH ?
-                      "dpi" : "dpc");
-       break;
+      case IPP_TAG_DATE :
+         for (i = 0; i < attr->num_values; i ++)
+           if (Output == _CUPS_OUTPUT_PLIST)
+             printf("<date>%s</date>\n", iso_date(attr->values[i].date));
+           else
+             printf("%s ", iso_date(attr->values[i].date));
+         break;
 
-    case IPP_TAG_DATE :
-       for (i = 0; i < attr->num_values; i ++)
-         if (Output == _CUPS_OUTPUT_PLIST)
-            printf("<date>%s</date>\n", iso_date(attr->values[i].date));
-          else
-           printf("%s ", iso_date(attr->values[i].date));
-        break;
+      case IPP_TAG_STRING :
+      case IPP_TAG_TEXT :
+      case IPP_TAG_NAME :
+      case IPP_TAG_KEYWORD :
+      case IPP_TAG_CHARSET :
+      case IPP_TAG_URI :
+      case IPP_TAG_MIMETYPE :
+      case IPP_TAG_LANGUAGE :
+         for (i = 0; i < attr->num_values; i ++)
+           if (Output == _CUPS_OUTPUT_PLIST)
+             print_xml_string("string", attr->values[i].string.text);
+           else
+             printf("\"%s\" ", attr->values[i].string.text);
+         break;
 
-    case IPP_TAG_STRING :
-    case IPP_TAG_TEXT :
-    case IPP_TAG_NAME :
-    case IPP_TAG_KEYWORD :
-    case IPP_TAG_CHARSET :
-    case IPP_TAG_URI :
-    case IPP_TAG_MIMETYPE :
-    case IPP_TAG_LANGUAGE :
-       for (i = 0; i < attr->num_values; i ++)
-         if (Output == _CUPS_OUTPUT_PLIST)
-           print_xml_string("string", attr->values[i].string.text);
-         else
-           printf("\"%s\" ", attr->values[i].string.text);
-       break;
+      case IPP_TAG_TEXTLANG :
+      case IPP_TAG_NAMELANG :
+         for (i = 0; i < attr->num_values; i ++)
+           if (Output == _CUPS_OUTPUT_PLIST)
+           {
+             fputs("<dict><key>language</key><string>", stdout);
+             print_xml_string(NULL, attr->values[i].string.language);
+             fputs("</string><key>string</key><string>", stdout);
+             print_xml_string(NULL, attr->values[i].string.text);
+             puts("</string></dict>");
+           }
+           else
+             printf("\"%s\"[%s] ", attr->values[i].string.text,
+                    attr->values[i].string.language);
+         break;
 
-    case IPP_TAG_TEXTLANG :
-    case IPP_TAG_NAMELANG :
-       for (i = 0; i < attr->num_values; i ++)
-         if (Output == _CUPS_OUTPUT_PLIST)
+      case IPP_TAG_BEGIN_COLLECTION :
+         for (i = 0; i < attr->num_values; i ++)
          {
-           fputs("<dict><key>language</key><string>", stdout);
-           print_xml_string(NULL, attr->values[i].string.charset);
-           fputs("</string><key>string</key><string>", stdout);
-           print_xml_string(NULL, attr->values[i].string.text);
-           puts("</string></dict>");
+           if (Output == _CUPS_OUTPUT_PLIST)
+           {
+             puts("<dict>");
+             for (colattr = attr->values[i].collection->attrs;
+                  colattr;
+                  colattr = colattr->next)
+               print_attr(colattr, NULL);
+             puts("</dict>");
+           }
+           else
+           {
+             if (i)
+               putchar(' ');
+
+             print_col(attr->values[i].collection);
+           }
          }
-         else
-           printf("\"%s\",%s ", attr->values[i].string.text,
-                  attr->values[i].string.charset);
-       break;
+         break;
 
-    case IPP_TAG_BEGIN_COLLECTION :
-       for (i = 0; i < attr->num_values; i ++)
-       {
+      default :
          if (Output == _CUPS_OUTPUT_PLIST)
-         {
-           puts("<dict>");
-           for (colattr = attr->values[i].collection->attrs;
-                colattr;
-                colattr = colattr->next)
-             print_attr(colattr);
-           puts("</dict>");
-         }
+           printf("<string>&lt;&lt;%s&gt;&gt;</string>\n",
+                  ippTagString(attr->value_tag));
          else
-         {
-           if (i)
-             putchar(' ');
-
-           print_col(attr->values[i].collection);
-          }
-       }
-       break;
-
-    default :
-       if (Output == _CUPS_OUTPUT_PLIST)
-         printf("<string>&lt;&lt;%s&gt;&gt;</string>\n",
-                ippTagString(attr->value_tag));
-       else
-         fputs(ippTagString(attr->value_tag), stdout);
-       break;
-  }
+           fputs(ippTagString(attr->value_tag), stdout);
+         break;
+    }
 
-  if (Output == _CUPS_OUTPUT_PLIST)
-  {
     if (attr->num_values > 1)
       puts("</array>");
   }
   else
-    putchar('\n');
+  {
+    char       buffer[8192];           /* Value buffer */
+
+    if (Output == _CUPS_OUTPUT_TEST)
+    {
+      if (!attr->name)
+      {
+        puts("        -- separator --");
+        return;
+      }
+
+      printf("        %s (%s%s) = ", attr->name,
+            attr->num_values > 1 ? "1setOf " : "",
+            ippTagString(attr->value_tag));
+    }
+
+    ippAttributeString(attr, buffer, sizeof(buffer));
+    puts(buffer);
+  }
 }
 
 
@@ -3312,8 +3888,8 @@ print_col(ipp_t *col)                     /* I - Collection attribute to print */
       case IPP_TAG_TEXTLANG :
       case IPP_TAG_NAMELANG :
          for (i = 0; i < attr->num_values; i ++)
-           printf("\"%s\",%s ", attr->values[i].string.text,
-                  attr->values[i].string.charset);
+           printf("\"%s\"[%s] ", attr->values[i].string.text,
+                  attr->values[i].string.language);
          break;
 
       case IPP_TAG_BEGIN_COLLECTION :
@@ -3383,7 +3959,7 @@ print_csv(
           break;
         else if (!strcmp(current->name, displayed[i]))
         {
-          _ippAttrString(current, buffer, maxlength);
+          ippAttributeString(current, buffer, maxlength);
           break;
         }
       }
@@ -3451,7 +4027,7 @@ print_fatal_error(const char *s,  /* I - Printf-style format string */
     print_xml_trailer(0, buffer);
   }
   else
-    _cupsLangPrintf(stderr, "ipptool: %s\n", buffer);
+    _cupsLangPrintf(stderr, "ipptool: %s", buffer);
 }
 
 
@@ -3504,7 +4080,7 @@ print_line(
           break;
         else if (!strcmp(current->name, displayed[i]))
         {
-          _ippAttrString(current, buffer, maxlength);
+          ippAttributeString(current, buffer, maxlength);
           break;
         }
       }
@@ -3616,6 +4192,69 @@ print_xml_string(const char *element,    /* I - Element name or NULL */
       fputs("&lt;", stdout);
     else if (*s == '>')
       fputs("&gt;", stdout);
+    else if ((*s & 0xe0) == 0xc0)
+    {
+     /*
+      * Validate UTF-8 two-byte sequence...
+      */
+
+      if ((s[1] & 0xc0) != 0x80)
+      {
+        putchar('?');
+        s ++;
+      }
+      else
+      {
+        putchar(*s++);
+        putchar(*s);
+      }
+    }
+    else if ((*s & 0xf0) == 0xe0)
+    {
+     /*
+      * Validate UTF-8 three-byte sequence...
+      */
+
+      if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80)
+      {
+        putchar('?');
+        s += 2;
+      }
+      else
+      {
+        putchar(*s++);
+        putchar(*s++);
+        putchar(*s);
+      }
+    }
+    else if ((*s & 0xf8) == 0xf0)
+    {
+     /*
+      * Validate UTF-8 four-byte sequence...
+      */
+
+      if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
+          (s[3] & 0xc0) != 0x80)
+      {
+        putchar('?');
+        s += 3;
+      }
+      else
+      {
+        putchar(*s++);
+        putchar(*s++);
+        putchar(*s++);
+        putchar(*s);
+      }
+    }
+    else if ((*s & 0x80) || (*s < ' ' && !isspace(*s & 255)))
+    {
+     /*
+      * Invalid control character...
+      */
+
+      putchar('?');
+    }
     else
       putchar(*s);
 
@@ -3666,6 +4305,15 @@ set_variable(_cups_vars_t *vars, /* I - Variables */
                *var;                   /* New variable */
 
 
+  if (!_cups_strcasecmp(name, "filename"))
+  {
+    if (vars->filename)
+      free(vars->filename);
+
+    vars->filename = strdup(value);
+    return;
+  }
+
   key.name = (char *)name;
   if ((var = cupsArrayFind(vars->vars, &key)) != NULL)
   {
@@ -3687,6 +4335,40 @@ set_variable(_cups_vars_t *vars, /* I - Variables */
 }
 
 
+#ifndef WIN32
+/*
+ * 'sigterm_handler()' - Handle SIGINT and SIGTERM.
+ */
+
+static void
+sigterm_handler(int sig)               /* I - Signal number (unused) */
+{
+  (void)sig;
+
+  Cancel = 1;
+
+  signal(SIGINT, SIG_DFL);
+  signal(SIGTERM, SIG_DFL);
+}
+#endif /* !WIN32 */
+
+
+/*
+ * 'timeout_cb()' - Handle HTTP timeouts.
+ */
+
+static int                             /* O - 1 to continue, 0 to cancel */
+timeout_cb(http_t *http,               /* I - Connection to server (unused) */
+           void   *user_data)          /* I - User data (unused) */
+{
+  (void)http;
+  (void)user_data;
+
+ /* Always cancel on timeout */
+  return (0);
+}
+
+
 /*
  * 'usage()' - Show program usage.
  */
@@ -3694,28 +4376,39 @@ set_variable(_cups_vars_t *vars,        /* I - Variables */
 static void
 usage(void)
 {
-  _cupsLangPuts(stderr,
-                _("Usage: ipptool [options] URI filename [ ... "
-                 "filenameN ]\n"
-                 "\n"
-                 "Options:\n"
-                 "\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"
-                 "-S             Test with SSL encryption.\n"
-                 "-V version     Set default IPP version.\n"
-                 "-X             Produce XML plist instead of plain text.\n"
-                 "-d name=value  Define variable.\n"
-                 "-f filename    Set default request filename.\n"
-                 "-i seconds     Repeat the last file with the given time "
-                 "interval.\n"
-                 "-n count       Repeat the last file the given number of "
-                 "times.\n"
-                 "-q             Be quiet - no output except errors.\n"
-                 "-t             Produce a test report.\n"
-                 "-v             Show all attributes sent and received.\n"));
+  _cupsLangPuts(stderr, _("Usage: ipptool [options] URI filename [ ... "
+                         "filenameN ]"));
+  _cupsLangPuts(stderr, _("Options:"));
+  _cupsLangPuts(stderr, _("  -4                      Connect using IPv4."));
+  _cupsLangPuts(stderr, _("  -6                      Connect using IPv6."));
+  _cupsLangPuts(stderr, _("  -C                      Send requests using "
+                          "chunking (default)."));
+  _cupsLangPuts(stderr, _("  -E                      Test with TLS "
+                          "encryption."));
+  _cupsLangPuts(stderr, _("  -I                      Ignore errors."));
+  _cupsLangPuts(stderr, _("  -L                      Send requests using "
+                          "content-length."));
+  _cupsLangPuts(stderr, _("  -S                      Test with SSL "
+                         "encryption."));
+  _cupsLangPuts(stderr, _("  -T                      Set the receive/send "
+                          "timeout in seconds."));
+  _cupsLangPuts(stderr, _("  -V version              Set default IPP "
+                          "version."));
+  _cupsLangPuts(stderr, _("  -X                      Produce XML plist instead "
+                          "of plain text."));
+  _cupsLangPuts(stderr, _("  -d name=value           Set named variable to "
+                          "value."));
+  _cupsLangPuts(stderr, _("  -f filename             Set default request "
+                          "filename."));
+  _cupsLangPuts(stderr, _("  -i seconds              Repeat the last file with "
+                          "the given time interval."));
+  _cupsLangPuts(stderr, _("  -n count                Repeat the last file the "
+                          "given number of times."));
+  _cupsLangPuts(stderr, _("  -q                      Be quiet - no output "
+                          "except errors."));
+  _cupsLangPuts(stderr, _("  -t                      Produce a test report."));
+  _cupsLangPuts(stderr, _("  -v                      Show all attributes sent "
+                          "and received."));
 
   exit(1);
 }
@@ -4449,12 +5142,16 @@ static int                              /* O - 1 on match, 0 on non-match */
 with_value(char            *value,     /* I - Value string */
            int             regex,      /* I - Value is a regular expression */
            ipp_attribute_t *attr,      /* I - Attribute to compare */
-          int             report)      /* I - 1 = report failures */
+          int             report,      /* I - 1 = report failures */
+          char            *matchbuf,   /* I - Buffer to hold matching value */
+          size_t          matchlen)    /* I - Length of match buffer */
 {
   int  i;                              /* Looping var */
   char *valptr;                        /* Pointer into value */
 
 
+  *matchbuf = '\0';
+
  /*
   * NULL matches everything.
   */
@@ -4478,8 +5175,6 @@ with_value(char            *value,        /* I - Value string */
 
 
           valptr = value;
-         if (!strncmp(valptr, "no-value,", 9))
-           valptr += 9;
 
          while (isspace(*valptr & 255) || isdigit(*valptr & 255) ||
                 *valptr == '-' || *valptr == ',' || *valptr == '<' ||
@@ -4505,15 +5200,24 @@ with_value(char            *value,      /* I - Value string */
            {
              case '=' :
                  if (attr->values[i].integer == intvalue)
+                 {
+                   snprintf(matchbuf, matchlen, "%d", attr->values[i].integer);
                    return (1);
+                 }
                  break;
              case '<' :
                  if (attr->values[i].integer < intvalue)
+                 {
+                   snprintf(matchbuf, matchlen, "%d", attr->values[i].integer);
                    return (1);
+                 }
                  break;
              case '>' :
                  if (attr->values[i].integer > intvalue)
+                 {
+                   snprintf(matchbuf, matchlen, "%d", attr->values[i].integer);
                    return (1);
+                 }
                  break;
            }
          }
@@ -4526,11 +5230,87 @@ with_value(char            *value,      /* I - Value string */
        }
        break;
 
+    case IPP_TAG_RANGE :
+        for (i = 0; i < attr->num_values; i ++)
+        {
+         char  op,                     /* Comparison operator */
+               *nextptr;               /* Next pointer */
+         int   intvalue;               /* Integer value */
+
+
+          valptr = value;
+
+         while (isspace(*valptr & 255) || isdigit(*valptr & 255) ||
+                *valptr == '-' || *valptr == ',' || *valptr == '<' ||
+                *valptr == '=' || *valptr == '>')
+         {
+           op = '=';
+           while (*valptr && !isdigit(*valptr & 255) && *valptr != '-')
+           {
+             if (*valptr == '<' || *valptr == '>' || *valptr == '=')
+               op = *valptr;
+             valptr ++;
+           }
+
+            if (!*valptr)
+             break;
+
+           intvalue = strtol(valptr, &nextptr, 0);
+           if (nextptr == valptr)
+             break;
+           valptr = nextptr;
+
+           switch (op)
+           {
+             case '=' :
+                 if (attr->values[i].range.lower == intvalue ||
+                     attr->values[i].range.upper == intvalue)
+                 {
+                   snprintf(matchbuf, matchlen, "%d-%d",
+                            attr->values[i].range.lower,
+                            attr->values[i].range.upper);
+                   return (1);
+                 }
+                 break;
+             case '<' :
+                 if (attr->values[i].range.upper < intvalue)
+                 {
+                   snprintf(matchbuf, matchlen, "%d-%d",
+                            attr->values[i].range.lower,
+                            attr->values[i].range.upper);
+                   return (1);
+                 }
+                 break;
+             case '>' :
+                 if (attr->values[i].range.upper > intvalue)
+                 {
+                   snprintf(matchbuf, matchlen, "%d-%d",
+                            attr->values[i].range.lower,
+                            attr->values[i].range.upper);
+                   return (1);
+                 }
+                 break;
+           }
+         }
+        }
+
+       if (report)
+       {
+         for (i = 0; i < attr->num_values; i ++)
+           print_test_error("GOT: %s=%d-%d", attr->name,
+                            attr->values[i].range.lower,
+                            attr->values[i].range.upper);
+       }
+       break;
+
     case IPP_TAG_BOOLEAN :
        for (i = 0; i < attr->num_values; i ++)
        {
           if (!strcmp(value, "true") == attr->values[i].boolean)
+          {
+            strlcpy(matchbuf, value, matchlen);
            return (1);
+         }
        }
 
        if (report)
@@ -4542,7 +5322,8 @@ with_value(char            *value,        /* I - Value string */
        break;
 
     case IPP_TAG_NOVALUE :
-        return (!strcmp(value, "no-value") || !strncmp(value, "no-value,", 9));
+    case IPP_TAG_UNKNOWN :
+       return (1);
 
     case IPP_TAG_CHARSET :
     case IPP_TAG_KEYWORD :
@@ -4591,6 +5372,9 @@ with_value(char            *value,        /* I - Value string */
 
          regfree(&re);
 
+          if (i == attr->num_values)
+            strlcpy(matchbuf, attr->values[0].string.text, matchlen);
+
           return (i == attr->num_values);
        }
        else
@@ -4603,7 +5387,10 @@ with_value(char            *value,       /* I - Value string */
          for (i = 0; i < attr->num_values; i ++)
          {
            if (!strcmp(value, attr->values[i].string.text))
+           {
+             strlcpy(matchbuf, attr->values[i].string.text, matchlen);
              return (1);
+           }
          }
 
          if (report)