]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - test/ipptool.c
License change: Apache License, Version 2.0.
[thirdparty/cups.git] / test / ipptool.c
index 969f15435d387bf69640bb04b53f7c4eeb502ade..20a2c9c8df6f5a0941d85fadde80cb7369524275 100644 (file)
@@ -1,47 +1,10 @@
 /*
- * "$Id$"
+ * ipptool command for CUPS.
  *
- *   ipptool command for CUPS.
+ * Copyright 2007-2017 by Apple Inc.
+ * Copyright 1997-2007 by Easy Software Products.
  *
- *   Copyright 2007-2011 by Apple Inc.
- *   Copyright 1997-2007 by Easy Software Products.
- *
- *   These coded instructions, statements, and computer programs are the
- *   property of Apple Inc. and are protected by Federal copyright
- *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
- *   which should have been included with this file.  If this file is
- *   file is missing or damaged, see the license at "http://www.cups.org/".
- *
- *   This file is subject to the Apple OS-Developed Software exception.
- *
- * Contents:
- *
- *   main()              - Parse options and do tests.
- *   compare_vars()      - Compare two variables.
- *   do_tests()          - Do tests as specified in the test file.
- *   expand_variables()  - Expand variables in a string.
- *   expect_matches()    - Return true if the tag matches the specification.
- *   get_collection()    - Get a collection value from the current test file.
- *   get_filename()      - Get a filename based on the current test file.
- *   get_token()         - Get a token from a file.
- *   get_variable()      - Get the value of a variable.
- *   iso_date()          - Return an ISO 8601 date/time string for the given IPP
- *                         dateTime value.
- *   password_cb()       - Password callback for authenticated tests.
- *   print_attr()        - Print an attribute on the screen.
- *   print_col()         - Print a collection attribute on the screen.
- *   print_csv()         - Print a line of CSV text.
- *   print_fatal_error() - Print a fatal error message.
- *   print_line()        - Print a line of formatted or CSV text.
- *   print_test_error()  - Print a test error message.
- *   print_xml_header()  - Print a standard XML plist header.
- *   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.
- *   timeout_cb()        - Handle HTTP timeouts.
- *   usage()             - Show program usage.
- *   validate_attr()     - Determine whether an attribute is valid.
- *   with_value()        - Test a WITH-VALUE predicate.
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
  */
 
 /*
 #include <cups/cups-private.h>
 #include <cups/file-private.h>
 #include <regex.h>
-
+#include <sys/stat.h>
+#ifdef WIN32
+#  include <windows.h>
+#  ifndef R_OK
+#    define R_OK 0
+#  endif /* !R_OK */
+#else
+#  include <signal.h>
+#  include <termios.h>
+#endif /* WIN32 */
 #ifndef O_BINARY
 #  define O_BINARY 0
 #endif /* !O_BINARY */
@@ -73,24 +45,40 @@ typedef enum _cups_output_e         /**** Output mode ****/
   _CUPS_OUTPUT_QUIET,                  /* No output */
   _CUPS_OUTPUT_TEST,                   /* Traditional CUPS test output */
   _CUPS_OUTPUT_PLIST,                  /* XML plist test output */
+  _CUPS_OUTPUT_IPPSERVER,              /* ippserver attribute file output */
   _CUPS_OUTPUT_LIST,                   /* Tabular list output */
   _CUPS_OUTPUT_CSV                     /* Comma-separated values output */
 } _cups_output_t;
 
+typedef enum _cups_with_e              /**** WITH flags ****/
+{
+  _CUPS_WITH_LITERAL = 0,              /* Match string is a literal value */
+  _CUPS_WITH_ALL = 1,                  /* Must match all values */
+  _CUPS_WITH_REGEX = 2,                        /* Match string is a regular expression */
+  _CUPS_WITH_HOSTNAME = 4,             /* Match string is a URI hostname */
+  _CUPS_WITH_RESOURCE = 8,             /* Match string is a URI resource */
+  _CUPS_WITH_SCHEME = 16               /* Match string is a URI scheme */
+} _cups_with_t;
+
 typedef struct _cups_expect_s          /**** Expected attribute info ****/
 {
   int          optional,               /* Optional attribute? */
-               not_expect;             /* Don't expect attribute? */
+               not_expect,             /* Don't expect attribute? */
+               expect_all;             /* Expect all attributes to match/not match */
   char         *name,                  /* Attribute name */
                *of_type,               /* Type name */
                *same_count_as,         /* Parallel attribute name */
                *if_defined,            /* Only required if variable defined */
                *if_not_defined,        /* Only required if variable is not defined */
                *with_value,            /* Attribute must include this value */
+               *with_value_from,       /* Attribute must have one of the values in this attribute */
                *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_limit,           /* Maximum number of times to repeat */
+               repeat_match,           /* Repeat test on match */
+               repeat_no_match,        /* Repeat test on no match */
+               with_flags,             /* WITH flags  */
                count;                  /* Expected count if > 0 */
   ipp_tag_t    in_group;               /* IN-GROUP value */
 } _cups_expect_t;
@@ -99,7 +87,13 @@ 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 */
+               *if_not_defined,        /* Only if variable is not defined */
+               *define_match,          /* Variable to define on match */
+               *define_no_match,       /* Variable to define on no-match */
+               *define_value;          /* Variable to define with value */
+  int          repeat_limit,           /* Maximum number of times to repeat */
+               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 ****/
@@ -110,9 +104,9 @@ 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 */
@@ -128,79 +122,64 @@ typedef struct _cups_vars_s               /**** Set of variables ****/
  * Globals...
  */
 
-_cups_transfer_t Transfer = _CUPS_TRANSFER_AUTO;
+static _cups_transfer_t Transfer = _CUPS_TRANSFER_AUTO;
                                        /* How to transfer requests */
-_cups_output_t Output = _CUPS_OUTPUT_LIST;
+static _cups_output_t  Output = _CUPS_OUTPUT_LIST;
                                        /* Output mode */
-int            IgnoreErrors = 0,       /* Ignore errors? */
-               Verbosity = 0,          /* Show all attributes? */
+static int     Cancel = 0,             /* Cancel test? */
+               IgnoreErrors = 0,       /* Ignore errors? */
+               StopAfterIncludeError = 0,
+                                       /* Stop after include errors? */
+               ValidateHeaders = 0,    /* Validate HTTP headers in response? */
+                Verbosity = 0,          /* Show all attributes? */
                Version = 11,           /* Default IPP version */
-               XMLHeader = 0;          /* 1 if header is written */
-char           *Password = NULL;       /* Password from URI */
-const char * const URIStatusStrings[] =        /* URI status strings */
-{
-  "URI too large",
-  "Bad arguments to function",
-  "Bad resource in URI",
-  "Bad port number in URI",
-  "Bad hostname/address in URI",
-  "Bad username in URI",
-  "Bad scheme in URI",
-  "Bad/empty URI",
-  "OK",
-  "Missing scheme in URI",
-  "Unknown scheme in URI",
-  "Missing resource in URI"
-};
+               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 */
+static char    *Username = NULL,       /* Username from URI */
+               *Password = NULL;       /* Password from URI */
+static int     PasswordTries = 0;      /* Number of tries with password */
 
 
 /*
  * Local functions...
  */
 
+static void    add_stringf(cups_array_t *a, const char *s, ...) __attribute__ ((__format__ (__printf__, 2, 3)));
+static int      compare_uris(const char *a, const char *b);
 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__ */
-;
+static int     do_tests(cups_file_t *outfile, _cups_vars_t *vars, const char *testfile);
+static void    expand_variables(_cups_vars_t *vars, char *dst, const char *src, 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,
-                             size_t dstsize);
-static char    *get_token(FILE *fp, char *buf, int buflen,
-                          int *linenum);
+static ipp_t   *get_collection(cups_file_t *outfile, _cups_vars_t *vars, cups_file_t *fp, int *linenum);
+static char    *get_filename(const char *testfile, char *dst, const char *src, size_t dstsize);
+static const char *get_string(ipp_attribute_t *attr, int element, int flags, char *buffer, size_t bufsize);
+static char    *get_token(cups_file_t *fp, char *buf, int buflen, int *linenum);
 static char    *get_variable(_cups_vars_t *vars, const char *name);
-static char    *iso_date(ipp_uchar_t *date);
+static char    *iso_date(const ipp_uchar_t *date);
 static const char *password_cb(const char *prompt);
-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__ */
-;
-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__ */
-;
-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    pause_message(const char *message);
+static void    print_attr(cups_file_t *outfile, int format, ipp_attribute_t *attr, ipp_tag_t *group);
+static void    print_csv(cups_file_t *outfile, ipp_attribute_t *attr, int num_displayed, char **displayed, size_t *widths);
+static void    print_fatal_error(cups_file_t *outfile, const char *s, ...) __attribute__ ((__format__ (__printf__, 2, 3)));
+static void    print_ippserver_attr(cups_file_t *outfile, ipp_attribute_t *attr, int indent);
+static void    print_ippserver_string(cups_file_t *outfile, const char *s, size_t len);
+static void    print_line(cups_file_t *outfile, ipp_attribute_t *attr, int num_displayed, char **displayed, size_t *widths);
+static void    print_xml_header(cups_file_t *outfile);
+static void    print_xml_string(cups_file_t *outfile, const char *element, const char *s);
+static void    print_xml_trailer(cups_file_t *outfile, int success, const char *message);
+static void    set_variable(cups_file_t *outfile, _cups_vars_t *vars, const char *name, const char *value);
+#ifndef WIN32
+static void    sigterm_handler(int sig);
+#endif /* WIN32 */
 static int     timeout_cb(http_t *http, void *user_data);
-static void    usage(void);
-static int     validate_attr(ipp_attribute_t *attr, int print);
-static int      with_value(char *value, int regex, ipp_attribute_t *attr,
-                          int report);
+static void    usage(void) __attribute__((noreturn));
+static int     validate_attr(cups_file_t *outfile, cups_array_t *errors, ipp_attribute_t *attr);
+static const char *with_flags_string(int flags);
+static int      with_value(cups_file_t *outfile, cups_array_t *errors, char *value, int flags, ipp_attribute_t *attr, char *matchbuf, size_t matchlen);
+static int      with_value_from(cups_array_t *errors, ipp_attribute_t *fromattr, ipp_attribute_t *attr, char *matchbuf, size_t matchlen);
 
 
 /*
@@ -213,12 +192,16 @@ main(int  argc,                           /* I - Number of command-line args */
 {
   int                  i;              /* Looping var */
   int                  status;         /* Status of tests... */
+  cups_file_t          *outfile = cupsFileStdout();
+                                        /* Output file */
   char                 *opt,           /* Current option */
                        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 */
+                       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 */
@@ -227,6 +210,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...
@@ -251,7 +242,41 @@ main(int  argc,                            /* I - Number of command-line args */
 
   for (i = 1; i < argc; i ++)
   {
-    if (argv[i][0] == '-')
+    if (!strcmp(argv[i], "--help"))
+    {
+      usage();
+    }
+    else if (!strcmp(argv[i], "--ippserver"))
+    {
+      i ++;
+
+      if (i >= argc)
+      {
+       _cupsLangPuts(stderr, _("ipptool: Missing filename for \"--ippserver\"."));
+       usage();
+      }
+
+      if (outfile != cupsFileStdout())
+       usage();
+
+      if ((outfile = cupsFileOpen(argv[i], "w")) == NULL)
+      {
+       _cupsLangPrintf(stderr, _("%s: Unable to open \"%s\": %s"), "ipptool", argv[i], strerror(errno));
+       exit(1);
+      }
+
+      Output = _CUPS_OUTPUT_IPPSERVER;
+    }
+    else if (!strcmp(argv[i], "--stop-after-include-error"))
+    {
+      StopAfterIncludeError = 1;
+    }
+    else if (!strcmp(argv[i], "--version"))
+    {
+      puts(CUPS_SVERSION);
+      return (0);
+    }
+    else if (argv[i][0] == '-')
     {
       for (opt = argv[i] + 1; *opt; opt ++)
       {
@@ -288,6 +313,33 @@ main(int  argc,                            /* I - Number of command-line args */
               Transfer = _CUPS_TRANSFER_LENGTH;
               break;
 
+          case 'P' : /* Output to plist file */
+             i ++;
+
+             if (i >= argc)
+             {
+               _cupsLangPrintf(stderr, _("%s: Missing filename for \"-P\"."), "ipptool");
+               usage();
+              }
+
+              if (outfile != cupsFileStdout())
+                usage();
+
+              if ((outfile = cupsFileOpen(argv[i], "w")) == NULL)
+              {
+                _cupsLangPrintf(stderr, _("%s: Unable to open \"%s\": %s"), "ipptool", argv[i], strerror(errno));
+                exit(1);
+              }
+
+             Output = _CUPS_OUTPUT_PLIST;
+
+              if (interval || repeat)
+             {
+               _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"-P\" and \"-X\"."));
+               usage();
+             }
+              break;
+
          case 'S' : /* Encrypt with SSL */
 #ifdef HAVE_SSL
              vars.encryption = HTTP_ENCRYPT_ALWAYS;
@@ -302,8 +354,9 @@ main(int  argc,                             /* I - Number of command-line args */
 
              if (i >= argc)
              {
-               _cupsLangPuts(stderr,
-                             _("ipptool: Missing timeout for \"-T\"."));
+               _cupsLangPrintf(stderr,
+                               _("%s: Missing timeout for \"-T\"."),
+                               "ipptool");
                usage();
               }
 
@@ -315,8 +368,9 @@ main(int  argc,                             /* I - Number of command-line args */
 
              if (i >= argc)
              {
-               _cupsLangPuts(stderr,
-                             _("ipptool: Missing version for \"-V\"."));
+               _cupsLangPrintf(stderr,
+                               _("%s: Missing version for \"-V\"."),
+                               "ipptool");
                usage();
               }
 
@@ -333,8 +387,8 @@ main(int  argc,                             /* I - Number of command-line args */
              else
              {
                _cupsLangPrintf(stderr,
-                               _("ipptool: Bad version %s for \"-V\"."),
-                               argv[i]);
+                               _("%s: Bad version %s for \"-V\"."),
+                               "ipptool", argv[i]);
                usage();
              }
              break;
@@ -344,8 +398,7 @@ main(int  argc,                             /* I - Number of command-line args */
 
               if (interval || repeat)
              {
-               _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are "
-                                       "incompatible with -X\"."));
+               _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"-P\" and \"-X\"."));
                usage();
              }
              break;
@@ -370,7 +423,7 @@ main(int  argc,                             /* I - Number of command-line args */
              else
                value = name + strlen(name);
 
-             set_variable(&vars, name, value);
+             set_variable(outfile, &vars, name, value);
              break;
 
           case 'f' : /* Set the default test filename */
@@ -383,19 +436,104 @@ main(int  argc,                          /* I - Number of command-line args */
                usage();
               }
 
-              if (access(argv[i], 0) && argv[i][0] != '/')
+              if (vars.filename)
+              {
+               free(vars.filename);
+               vars.filename = NULL;
+             }
+
+              if (access(argv[i], 0))
+              {
+               /*
+                * Try filename.gz...
+                */
+
+               snprintf(filename, sizeof(filename), "%s.gz", argv[i]);
+                if (access(filename, 0) && filename[0] != '/'
+#ifdef WIN32
+                    && (!isalpha(filename[0] & 255) || filename[1] != ':')
+#endif /* WIN32 */
+                    )
+               {
+                 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(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(outfile, &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(outfile, &vars, "filetype", "text/html");
+                else if (!_cups_strcasecmp(ext, ".jpg") ||
+                         !_cups_strcasecmp(ext, ".jpeg"))
+                  set_variable(outfile, &vars, "filetype", "image/jpeg");
+                else if (!_cups_strcasecmp(ext, ".pcl") ||
+                         !_cups_strcasecmp(ext, ".pcl.gz"))
+                  set_variable(outfile, &vars, "filetype", "application/vnd.hp-PCL");
+                else if (!_cups_strcasecmp(ext, ".pdf"))
+                  set_variable(outfile, &vars, "filetype", "application/pdf");
+                else if (!_cups_strcasecmp(ext, ".png"))
+                  set_variable(outfile, &vars, "filetype", "image/png");
+                else if (!_cups_strcasecmp(ext, ".ps") ||
+                         !_cups_strcasecmp(ext, ".ps.gz"))
+                  set_variable(outfile, &vars, "filetype", "application/postscript");
+                else if (!_cups_strcasecmp(ext, ".pwg") ||
+                         !_cups_strcasecmp(ext, ".pwg.gz") ||
+                         !_cups_strcasecmp(ext, ".ras") ||
+                         !_cups_strcasecmp(ext, ".ras.gz"))
+                  set_variable(outfile, &vars, "filetype", "image/pwg-raster");
+                else if (!_cups_strcasecmp(ext, ".tif") ||
+                         !_cups_strcasecmp(ext, ".tiff"))
+                  set_variable(outfile, &vars, "filetype", "image/tiff");
+                else if (!_cups_strcasecmp(ext, ".txt") ||
+                         !_cups_strcasecmp(ext, ".txt.gz"))
+                  set_variable(outfile, &vars, "filetype", "text/plain");
+                else if (!_cups_strcasecmp(ext, ".urf") ||
+                         !_cups_strcasecmp(ext, ".urf.gz"))
+                  set_variable(outfile, &vars, "filetype", "image/urf");
+                else if (!_cups_strcasecmp(ext, ".xps"))
+                  set_variable(outfile, &vars, "filetype", "application/openxps");
                 else
-                  vars.filename = filename;
+                 set_variable(outfile, &vars, "filetype", "application/octet-stream");
               }
               else
-               vars.filename = argv[i];
+              {
+               /*
+                * Use the "auto-type" MIME media type...
+                */
+
+               set_variable(outfile, &vars, "filetype", "application/octet-stream");
+              }
              break;
 
+          case 'h' : /* Validate response headers */
+              ValidateHeaders = 1;
+              break;
+
           case 'i' : /* Test every N seconds */
              i ++;
 
@@ -417,10 +555,9 @@ main(int  argc,                            /* I - Number of command-line args */
                }
               }
 
-              if (Output == _CUPS_OUTPUT_PLIST && interval)
+              if ((Output == _CUPS_OUTPUT_PLIST || Output == _CUPS_OUTPUT_IPPSERVER) && interval)
              {
-               _cupsLangPuts(stderr, _("ipptool: \"-i\" is incompatible with "
-                                       "\"-X\"."));
+               _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"--ippserver\", \"-P\", and \"-X\"."));
                usage();
              }
              break;
@@ -441,10 +578,9 @@ main(int  argc,                            /* I - Number of command-line args */
              else
                repeat = atoi(argv[i]);
 
-              if (Output == _CUPS_OUTPUT_PLIST && repeat)
+              if ((Output == _CUPS_OUTPUT_PLIST || Output == _CUPS_OUTPUT_IPPSERVER) && repeat)
              {
-               _cupsLangPuts(stderr, _("ipptool: \"-n\" is incompatible with "
-                                       "\"-X\"."));
+               _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"--ippserver\", \"-P\", and \"-X\"."));
                usage();
              }
              break;
@@ -465,7 +601,6 @@ main(int  argc,                             /* I - Number of command-line args */
              _cupsLangPrintf(stderr, _("ipptool: Unknown option \"-%c\"."),
                              *opt);
              usage();
-             break;
        }
       }
     }
@@ -501,8 +636,7 @@ main(int  argc,                             /* I - Number of command-line args */
 
       if (uri_status != HTTP_URI_OK)
       {
-        _cupsLangPrintf(stderr, _("ipptool: Bad URI - %s."),
-                       URIStatusStrings[uri_status - HTTP_URI_OVERFLOW]);
+        _cupsLangPrintf(stderr, _("ipptool: Bad URI - %s."), httpURIStatusString(uri_status));
         return (1);
       }
 
@@ -511,10 +645,14 @@ main(int  argc,                           /* I - Number of command-line args */
         if ((Password = strchr(vars.userpass, ':')) != NULL)
          *Password++ = '\0';
 
-        cupsSetUser(vars.userpass);
+        Username = vars.userpass;
        cupsSetPasswordCB(password_cb);
-       set_variable(&vars, "uriuser", vars.userpass);
+       set_variable(outfile, &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
     {
@@ -525,10 +663,15 @@ main(int  argc,                           /* I - Number of command-line args */
       if (!vars.uri)
       {
         _cupsLangPuts(stderr, _("ipptool: URI required before test file."));
+        _cupsLangPuts(stderr, argv[i]);
        usage();
       }
 
-      if (access(argv[i], 0) && argv[i][0] != '/')
+      if (access(argv[i], 0) && argv[i][0] != '/'
+#ifdef WIN32
+          && (!isalpha(argv[i][0] & 255) || argv[i][1] != ':')
+#endif /* WIN32 */
+          )
       {
         snprintf(testname, sizeof(testname), "%s/ipptool/%s", cg->cups_datadir,
                  argv[i]);
@@ -540,7 +683,7 @@ main(int  argc,                             /* I - Number of command-line args */
       else
         testfile = argv[i];
 
-      if (!do_tests(&vars, testfile))
+      if (!do_tests(outfile, &vars, testfile))
         status = 1;
     }
   }
@@ -553,13 +696,13 @@ main(int  argc,                           /* I - Number of command-line args */
   */
 
   if (Output == _CUPS_OUTPUT_PLIST)
-    print_xml_trailer(!status, NULL);
+    print_xml_trailer(outfile, !status, NULL);
   else if (interval > 0 && repeat > 0)
   {
     while (repeat > 1)
     {
-      usleep(interval);
-      do_tests(&vars, testfile);
+      usleep((useconds_t)interval);
+      do_tests(outfile, &vars, testfile);
       repeat --;
     }
   }
@@ -567,12 +710,23 @@ main(int  argc,                           /* I - Number of command-line args */
   {
     for (;;)
     {
-      usleep(interval);
-      do_tests(&vars, testfile);
+      usleep((useconds_t)interval);
+      do_tests(outfile, &vars, testfile);
     }
   }
 
- /*
+  if ((Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile)) && TestCount > 1)
+  {
+   /*
+    * Show a summary report if there were multiple tests...
+    */
+
+    cupsFilePrintf(cupsFileStdout(), "\nSummary: %d tests, %d passed, %d failed, %d skipped\nScore: %d%%\n", TestCount, PassCount, FailCount, SkipCount, 100 * (PassCount + SkipCount) / TestCount);
+  }
+
+  cupsFileClose(outfile);
+
+/*
   * Exit...
   */
 
@@ -580,6 +734,107 @@ main(int  argc,                           /* I - Number of command-line args */
 }
 
 
+/*
+ * 'add_stringf()' - Add a formatted string to an array.
+ */
+
+static void
+add_stringf(cups_array_t *a,           /* I - Array */
+            const char   *s,           /* I - Printf-style format string */
+            ...)                       /* I - Additional args as needed */
+{
+  char         buffer[10240];          /* Format buffer */
+  va_list      ap;                     /* Argument pointer */
+
+
+ /*
+  * Don't bother is the array is NULL...
+  */
+
+  if (!a)
+    return;
+
+ /*
+  * Format the message...
+  */
+
+  va_start(ap, s);
+  vsnprintf(buffer, sizeof(buffer), s, ap);
+  va_end(ap);
+
+ /*
+  * Add it to the array...
+  */
+
+  cupsArrayAdd(a, buffer);
+}
+
+
+/*
+ * 'compare_uris()' - Compare two URIs...
+ */
+
+static int                              /* O - Result of comparison */
+compare_uris(const char *a,             /* I - First URI */
+             const char *b)             /* I - Second URI */
+{
+  char  ascheme[32],                    /* Components of first URI */
+        auserpass[256],
+        ahost[256],
+        aresource[256];
+  int   aport;
+  char  bscheme[32],                    /* Components of second URI */
+        buserpass[256],
+        bhost[256],
+        bresource[256];
+  int   bport;
+  char  *ptr;                           /* Pointer into string */
+  int   result;                         /* Result of comparison */
+
+
+ /*
+  * Separate the URIs into their components...
+  */
+
+  if (httpSeparateURI(HTTP_URI_CODING_ALL, a, ascheme, sizeof(ascheme), auserpass, sizeof(auserpass), ahost, sizeof(ahost), &aport, aresource, sizeof(aresource)) < HTTP_URI_STATUS_OK)
+    return (-1);
+
+  if (httpSeparateURI(HTTP_URI_CODING_ALL, b, bscheme, sizeof(bscheme), buserpass, sizeof(buserpass), bhost, sizeof(bhost), &bport, bresource, sizeof(bresource)) < HTTP_URI_STATUS_OK)
+    return (-1);
+
+ /*
+  * Strip trailing dots from the host components, if present...
+  */
+
+  if ((ptr = ahost + strlen(ahost) - 1) > ahost && *ptr == '.')
+    *ptr = '\0';
+
+  if ((ptr = bhost + strlen(bhost) - 1) > bhost && *ptr == '.')
+    *ptr = '\0';
+
+ /*
+  * Compare each component...
+  */
+
+  if ((result = _cups_strcasecmp(ascheme, bscheme)) != 0)
+    return (result);
+
+  if ((result = strcmp(auserpass, buserpass)) != 0)
+    return (result);
+
+  if ((result = _cups_strcasecmp(ahost, bhost)) != 0)
+    return (result);
+
+  if (aport != bport)
+    return (aport - bport);
+
+  if (!_cups_strcasecmp(ascheme, "mailto") || !_cups_strcasecmp(ascheme, "urn"))
+    return (_cups_strcasecmp(aresource, bresource));
+  else
+    return (strcmp(aresource, bresource));
+}
+
+
 /*
  * 'compare_vars()' - Compare two variables.
  */
@@ -588,7 +843,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));
 }
 
 
@@ -597,7 +852,8 @@ compare_vars(_cups_var_t *a,                /* I - First variable */
  */
 
 static int                             /* 1 = success, 0 = failure */
-do_tests(_cups_vars_t *vars,           /* I - Variables */
+do_tests(cups_file_t  *outfile,                /* I - Output file */
+         _cups_vars_t *vars,           /* I - Variables */
          const char   *testfile)       /* I - Test file to use */
 {
   int          i,                      /* Looping var */
@@ -607,15 +863,25 @@ 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_count,           /* Repeat count */
+               repeat_test;            /* Repeat a test? */
+  useconds_t   delay,                  /* Initial delay */
+               repeat_interval;        /* Repeat interval (delay) */
   http_t       *http = NULL;           /* HTTP connection to server */
-  FILE         *fp = NULL;             /* Test file */
+  cups_file_t  *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[131072],         /* Copy buffer */
+               compression[16];        /* COMPRESSION value */
+  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 */
@@ -623,7 +889,9 @@ do_tests(_cups_vars_t *vars,                /* I - Variables */
   ipp_attribute_t *attrptr,            /* Attribute pointer */
                *found,                 /* Found attribute */
                *lastcol = NULL;        /* Last collection attribute */
-  char         name[1024];             /* Name of test */
+  char         name[1024],             /* Name of test */
+               file_id[1024],          /* File identifier */
+               test_id[1024];          /* Test identifier */
   char         filename[1024];         /* Filename */
   _cups_transfer_t transfer;           /* To chunk or not to chunk */
   int          version,                /* IPP version number to use */
@@ -631,22 +899,26 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
   int          num_statuses = 0;       /* Number of valid status codes */
   _cups_status_t statuses[100],                /* Valid status codes */
                *last_status;           /* Last STATUS (for predicates) */
-  int          num_expects = 0;        /* Number of expected attributes */
+  int           status_ok,              /* Did we get a matching status? */
+               num_expects = 0;  /* Number of expected attributes */
   _cups_expect_t expects[200],         /* Expected attributes */
                *expect,                /* Current expected attribute */
                *last_expect;           /* Last EXPECT (for predicates) */
   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 */
+               *errors = NULL;         /* Errors array */
+  const char   *error;                 /* Current error */
 
 
  /*
   * Open the test file...
   */
 
-  if ((fp = fopen(testfile, "r")) == NULL)
+  if ((fp = cupsFileOpen(testfile, "r")) == NULL)
   {
-    print_fatal_error("Unable to open test file %s - %s", testfile,
+    print_fatal_error(outfile, "Unable to open test file %s - %s", testfile,
                       strerror(errno));
     pass = 0;
     goto test_exit;
@@ -656,37 +928,39 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
   * Connect to the server...
   */
 
-  if ((http = _httpCreate(vars->hostname, vars->port, vars->encryption,
-                         vars->family)) == NULL)
+  if ((http = httpConnect2(vars->hostname, vars->port, NULL, vars->family,
+                           vars->encryption, 1, 30000, NULL)) == NULL)
   {
-    print_fatal_error("Unable to connect to %s on port %d - %s", vars->hostname,
-                      vars->port, strerror(errno));
+    print_fatal_error(outfile, "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));
-    pass = 0;
-    goto test_exit;
-  }
+#ifdef HAVE_LIBZ
+  httpSetDefaultField(http, HTTP_FIELD_ACCEPT_ENCODING,
+                      "deflate, gzip, identity");
+#else
+  httpSetDefaultField(http, HTTP_FIELD_ACCEPT_ENCODING, "identity");
+#endif /* HAVE_LIBZ */
 
   if (vars->timeout > 0.0)
-    _httpSetTimeout(http, vars->timeout, timeout_cb, NULL);
+    httpSetTimeout(http, vars->timeout, timeout_cb, NULL);
 
  /*
   * Loop on tests...
   */
 
-  CUPS_SRAND(time(NULL));
+  CUPS_SRAND((unsigned)time(NULL));
 
+  errors     = cupsArrayNew3(NULL, NULL, NULL, 0, (cups_acopy_func_t)strdup,
+                             (cups_afree_func_t)free);
+  file_id[0] = '\0';
   pass       = 1;
-  linenum    = 1;
+  linenum    = 0;
   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...
@@ -702,11 +976,11 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
           get_token(fp, temp, sizeof(temp), &linenum))
       {
         expand_variables(vars, token, temp, sizeof(token));
-       set_variable(vars, attr, token);
+       set_variable(outfile, vars, attr, token);
       }
       else
       {
-        print_fatal_error("Missing DEFINE name and/or value on line %d.",
+        print_fatal_error(outfile, "Missing DEFINE name and/or value on line %d.",
                          linenum);
        pass = 0;
        goto test_exit;
@@ -714,6 +988,48 @@ 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(outfile, vars, attr, token);
+      }
+      else
+      {
+        print_fatal_error(outfile, "Missing DEFINE-DEFAULT name and/or value on line "
+                         "%d.", linenum);
+       pass = 0;
+       goto test_exit;
+      }
+
+      continue;
+    }
+    else if (!strcmp(token, "FILE-ID"))
+    {
+     /*
+      * FILE-ID "string"
+      */
+
+      if (get_token(fp, temp, sizeof(temp), &linenum))
+      {
+        expand_variables(vars, file_id, temp, sizeof(file_id));
+      }
+      else
+      {
+        print_fatal_error(outfile, "Missing FILE-ID value on line %d.", linenum);
+       pass = 0;
+       goto test_exit;
+      }
+
+      continue;
+    }
     else if (!strcmp(token, "IGNORE-ERRORS"))
     {
      /*
@@ -722,13 +1038,13 @@ 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
       {
-        print_fatal_error("Missing IGNORE-ERRORS value on line %d.", linenum);
+        print_fatal_error(outfile, "Missing IGNORE-ERRORS value on line %d.", linenum);
        pass = 0;
        goto test_exit;
       }
@@ -748,18 +1064,85 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
         * Map the filename to and then run the tests...
        */
 
-        if (!do_tests(vars, get_filename(testfile, filename, temp,
-                                        sizeof(filename))))
+        if (!do_tests(outfile, vars, get_filename(testfile, filename, temp, sizeof(filename))))
+       {
+         pass = 0;
+
+         if (StopAfterIncludeError)
+           goto test_exit;
+       }
+      }
+      else
+      {
+        print_fatal_error(outfile, "Missing INCLUDE filename on line %d.", linenum);
+       pass = 0;
+       goto test_exit;
+      }
+
+      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(outfile, vars, get_filename(testfile, filename, temp, sizeof(filename))))
+       {
+         pass = 0;
+
+         if (StopAfterIncludeError)
+           goto test_exit;
+       }
+      }
+      else
+      {
+        print_fatal_error(outfile, "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(outfile, vars, get_filename(testfile, filename, temp, sizeof(filename))))
        {
          pass = 0;
 
-         if (!IgnoreErrors)
+         if (StopAfterIncludeError)
            goto test_exit;
        }
       }
       else
       {
-        print_fatal_error("Missing INCLUDE filename on line %d.", linenum);
+        print_fatal_error(outfile, "Missing INCLUDE-IF-NOT-DEFINED name or filename on "
+                         "line %d.", linenum);
        pass = 0;
        goto test_exit;
       }
@@ -780,7 +1163,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(outfile, "Missing SKIP-IF-DEFINED variable on line %d.",
+                         linenum);
        pass = 0;
        goto test_exit;
       }
@@ -798,12 +1182,34 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
       }
       else
       {
-        print_fatal_error("Missing SKIP-IF-NOT-DEFINED value on line %d.",
+        print_fatal_error(outfile, "Missing SKIP-IF-NOT-DEFINED variable on line %d.",
                          linenum);
        pass = 0;
        goto test_exit;
       }
     }
+    else if (!strcmp(token, "STOP-AFTER-INCLUDE-ERROR"))
+    {
+     /*
+      * STOP-AFTER-INCLUDE-ERROR yes
+      * STOP-AFTER-INCLUDE-ERROR no
+      */
+
+      if (get_token(fp, temp, sizeof(temp), &linenum) &&
+          (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
+      {
+        StopAfterIncludeError = !_cups_strcasecmp(temp, "yes");
+      }
+      else
+      {
+        print_fatal_error(outfile, "Missing STOP-AFTER-INCLUDE-ERROR value on line %d.",
+                          linenum);
+       pass = 0;
+       goto test_exit;
+      }
+
+      continue;
+    }
     else if (!strcmp(token, "TRANSFER"))
     {
      /*
@@ -822,7 +1228,7 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
          Transfer = _CUPS_TRANSFER_LENGTH;
        else
        {
-         print_fatal_error("Bad TRANSFER value \"%s\" on line %d.", temp,
+         print_fatal_error(outfile, "Bad TRANSFER value \"%s\" on line %d.", temp,
                            linenum);
          pass = 0;
          goto test_exit;
@@ -830,7 +1236,7 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
       }
       else
       {
-        print_fatal_error("Missing TRANSFER value on line %d.", linenum);
+        print_fatal_error(outfile, "Missing TRANSFER value on line %d.", linenum);
        pass = 0;
        goto test_exit;
       }
@@ -853,14 +1259,14 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
          Version = 22;
        else
        {
-         print_fatal_error("Bad VERSION \"%s\" on line %d.", temp, linenum);
+         print_fatal_error(outfile, "Bad VERSION \"%s\" on line %d.", temp, linenum);
          pass = 0;
          goto test_exit;
        }
       }
       else
       {
-        print_fatal_error("Missing VERSION number on line %d.", linenum);
+        print_fatal_error(outfile, "Missing VERSION number on line %d.", linenum);
        pass = 0;
        goto test_exit;
       }
@@ -869,7 +1275,7 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
     }
     else if (strcmp(token, "{"))
     {
-      print_fatal_error("Unexpected token %s seen on line %d.", token, linenum);
+      print_fatal_error(outfile, "Unexpected token %s seen on line %d.", token, linenum);
       pass = 0;
       goto test_exit;
     }
@@ -881,9 +1287,9 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
     if (show_header)
     {
       if (Output == _CUPS_OUTPUT_PLIST)
-       print_xml_header();
-      else if (Output == _CUPS_OUTPUT_TEST)
-       printf("\"%s\":\n", testfile);
+       print_xml_header(outfile);
+      if (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != cupsFileStdout()))
+       cupsFilePrintf(cupsFileStdout(), "\"%s\":\n", testfile);
 
       show_header = 0;
     }
@@ -891,16 +1297,22 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
     strlcpy(resource, vars->resource, sizeof(resource));
 
     request_id ++;
-    request       = ippNew();
-    op            = (ipp_op_t)0;
-    group         = IPP_TAG_ZERO;
-    ignore_errors = IgnoreErrors;
-    last_expect   = NULL;
-    last_status   = NULL;
-    filename[0]   = '\0';
-    skip_test     = 0;
-    version       = Version;
-    transfer      = Transfer;
+    request         = ippNew();
+    op              = (ipp_op_t)0;
+    group           = IPP_TAG_ZERO;
+    ignore_errors   = IgnoreErrors;
+    last_expect     = NULL;
+    last_status     = NULL;
+    filename[0]     = '\0';
+    skip_previous   = 0;
+    skip_test       = 0;
+    test_id[0]      = '\0';
+    version         = Version;
+    transfer        = Transfer;
+    compression[0]  = '\0';
+    delay           = 0;
+    repeat_count    = 0;
+    repeat_interval = 5000000;
 
     strlcpy(name, testfile, sizeof(name));
     if (strrchr(name, '.') != NULL)
@@ -912,20 +1324,36 @@ 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-LIMIT") &&
+          _cups_strcasecmp(token, "REPEAT-MATCH") &&
+          _cups_strcasecmp(token, "REPEAT-NO-MATCH") &&
+          _cups_strcasecmp(token, "SAME-COUNT-AS") &&
+          _cups_strcasecmp(token, "WITH-ALL-VALUES") &&
+          _cups_strcasecmp(token, "WITH-ALL-HOSTNAMES") &&
+          _cups_strcasecmp(token, "WITH-ALL-RESOURCES") &&
+          _cups_strcasecmp(token, "WITH-ALL-SCHEMES") &&
+          _cups_strcasecmp(token, "WITH-HOSTNAME") &&
+          _cups_strcasecmp(token, "WITH-RESOURCE") &&
+          _cups_strcasecmp(token, "WITH-SCHEME") &&
+          _cups_strcasecmp(token, "WITH-VALUE") &&
+          _cups_strcasecmp(token, "WITH-VALUE-FROM"))
         last_expect = NULL;
 
-      if (strcasecmp(token, "IF-DEFINED") &&
-          strcasecmp(token, "IF-NOT-DEFINED"))
+      if (_cups_strcasecmp(token, "DEFINE-MATCH") &&
+          _cups_strcasecmp(token, "DEFINE-NO-MATCH") &&
+         _cups_strcasecmp(token, "IF-DEFINED") &&
+          _cups_strcasecmp(token, "IF-NOT-DEFINED") &&
+          _cups_strcasecmp(token, "REPEAT-LIMIT") &&
+          _cups_strcasecmp(token, "REPEAT-MATCH") &&
+          _cups_strcasecmp(token, "REPEAT-NO-MATCH"))
         last_status = NULL;
 
       if (!strcmp(token, "}"))
@@ -936,46 +1364,49 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
        * Another collection value
        */
 
-       ipp_t   *col = get_collection(vars, fp, &linenum);
+       ipp_t   *col = get_collection(outfile, vars, fp, &linenum);
                                        /* Collection value */
 
        if (col)
        {
-         ipp_attribute_t *tempcol;     /* Pointer to new buffer */
-
-
-        /*
-         * Reallocate memory...
-         */
+          ippSetCollection(request, &lastcol, ippGetCount(lastcol), col);
+        }
+       else
+       {
+         pass = 0;
+         goto test_exit;
+       }
+      }
+      else if (!strcmp(token, "COMPRESSION"))
+      {
+       /*
+       * COMPRESSION none
+       * COMPRESSION deflate
+       * COMPRESSION gzip
+       */
 
-         if ((tempcol = realloc(lastcol, sizeof(ipp_attribute_t) +
-                                         (lastcol->num_values + 1) *
-                                         sizeof(ipp_value_t))) == NULL)
-         {
-           print_fatal_error("Unable to allocate memory on line %d.", linenum);
+       if (get_token(fp, temp, sizeof(temp), &linenum))
+       {
+         expand_variables(vars, compression, temp, sizeof(compression));
+#ifdef HAVE_LIBZ
+         if (strcmp(compression, "none") && strcmp(compression, "deflate") &&
+             strcmp(compression, "gzip"))
+#else
+         if (strcmp(compression, "none"))
+#endif /* HAVE_LIBZ */
+          {
+           print_fatal_error(outfile, "Unsupported COMPRESSION value '%s' on line %d.",
+                             compression, linenum);
            pass = 0;
            goto test_exit;
-         }
-
-         if (tempcol != lastcol)
-         {
-          /*
-           * Reset pointers in the list...
-           */
-
-           if (request->prev)
-             request->prev->next = tempcol;
-           else
-             request->attrs = tempcol;
-
-           lastcol = request->current = request->last = tempcol;
-         }
+          }
 
-         lastcol->values[lastcol->num_values].collection = col;
-         lastcol->num_values ++;
+          if (!strcmp(compression, "none"))
+            compression[0] = '\0';
        }
        else
        {
+         print_fatal_error(outfile, "Missing COMPRESSION value on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
@@ -990,11 +1421,11 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
            get_token(fp, temp, sizeof(temp), &linenum))
        {
          expand_variables(vars, token, temp, sizeof(token));
-         set_variable(vars, attr, token);
+         set_variable(outfile, vars, attr, token);
        }
        else
        {
-         print_fatal_error("Missing DEFINE name and/or value on line %d.",
+         print_fatal_error(outfile, "Missing DEFINE name and/or value on line %d.",
                            linenum);
          pass = 0;
          goto test_exit;
@@ -1008,26 +1439,36 @@ 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
        {
-         print_fatal_error("Missing IGNORE-ERRORS value on line %d.", linenum);
+         print_fatal_error(outfile, "Missing IGNORE-ERRORS value on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
 
        continue;
       }
-      else if (!strcasecmp(token, "NAME"))
+      else if (!_cups_strcasecmp(token, "NAME"))
       {
        /*
         * Name of test...
        */
 
-       get_token(fp, name, sizeof(name), &linenum);
+       get_token(fp, temp, sizeof(temp), &linenum);
+       expand_variables(vars, name, temp, sizeof(name));
+      }
+      else if (!_cups_strcasecmp(token, "PAUSE"))
+      {
+       /*
+        * Pause with a message...
+       */
+
+       get_token(fp, token, sizeof(token), &linenum);
+       pause_message(token);
       }
       else if (!strcmp(token, "REQUEST-ID"))
       {
@@ -1040,11 +1481,11 @@ 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
          {
-           print_fatal_error("Bad REQUEST-ID value \"%s\" on line %d.", temp,
+           print_fatal_error(outfile, "Bad REQUEST-ID value \"%s\" on line %d.", temp,
                              linenum);
            pass = 0;
            goto test_exit;
@@ -1052,7 +1493,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
        }
        else
        {
-         print_fatal_error("Missing REQUEST-ID value on line %d.", linenum);
+         print_fatal_error(outfile, "Missing REQUEST-ID value on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
@@ -1070,12 +1511,34 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
        }
        else
        {
-         print_fatal_error("Missing SKIP-IF-DEFINED value on line %d.",
+         print_fatal_error(outfile, "Missing SKIP-IF-DEFINED value on line %d.",
                            linenum);
          pass = 0;
          goto test_exit;
        }
       }
+      else if (!strcmp(token, "SKIP-IF-MISSING"))
+      {
+       /*
+       * SKIP-IF-MISSING filename
+       */
+
+       if (get_token(fp, temp, sizeof(temp), &linenum))
+       {
+         expand_variables(vars, token, temp, sizeof(token));
+         get_filename(testfile, filename, token, sizeof(filename));
+
+         if (access(filename, R_OK))
+           skip_test = 1;
+       }
+       else
+       {
+         print_fatal_error(outfile, "Missing SKIP-IF-MISSING filename on line %d.",
+                           linenum);
+         pass = 0;
+         goto test_exit;
+       }
+      }
       else if (!strcmp(token, "SKIP-IF-NOT-DEFINED"))
       {
        /*
@@ -1089,7 +1552,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(outfile, "Missing SKIP-IF-NOT-DEFINED value on line %d.",
                            linenum);
          pass = 0;
          goto test_exit;
@@ -1103,13 +1566,32 @@ 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 = !_cups_strcasecmp(temp, "yes");
+       }
+       else
+       {
+         print_fatal_error(outfile, "Missing SKIP-PREVIOUS-ERROR value on line %d.", linenum);
+         pass = 0;
+         goto test_exit;
+       }
+
+       continue;
+      }
+      else if (!strcmp(token, "TEST-ID"))
+      {
+       /*
+       * TEST-ID "string"
+       */
+
+       if (get_token(fp, temp, sizeof(temp), &linenum))
        {
-         skip_previous = !strcasecmp(temp, "yes");
+         expand_variables(vars, test_id, temp, sizeof(test_id));
        }
        else
        {
-         print_fatal_error("Missing SKIP-PREVIOUS-ERROR value on line %d.", linenum);
+         print_fatal_error(outfile, "Missing TEST-ID value on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
@@ -1134,7 +1616,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
            transfer = _CUPS_TRANSFER_LENGTH;
          else
          {
-           print_fatal_error("Bad TRANSFER value \"%s\" on line %d.", temp,
+           print_fatal_error(outfile, "Bad TRANSFER value \"%s\" on line %d.", temp,
                              linenum);
            pass = 0;
            goto test_exit;
@@ -1142,12 +1624,12 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
        }
        else
        {
-         print_fatal_error("Missing TRANSFER value on line %d.", linenum);
+         print_fatal_error(outfile, "Missing TRANSFER value on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "VERSION"))
+      else if (!_cups_strcasecmp(token, "VERSION"))
       {
        if (get_token(fp, temp, sizeof(temp), &linenum))
        {
@@ -1165,19 +1647,19 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
            version = 22;
          else
          {
-           print_fatal_error("Bad VERSION \"%s\" on line %d.", temp, linenum);
+           print_fatal_error(outfile, "Bad VERSION \"%s\" on line %d.", temp, linenum);
            pass = 0;
            goto test_exit;
          }
        }
        else
        {
-         print_fatal_error("Missing VERSION number on line %d.", linenum);
+         print_fatal_error(outfile, "Missing VERSION number on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "RESOURCE"))
+      else if (!_cups_strcasecmp(token, "RESOURCE"))
       {
        /*
         * Resource name...
@@ -1185,33 +1667,36 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
        if (!get_token(fp, resource, sizeof(resource), &linenum))
        {
-         print_fatal_error("Missing RESOURCE path on line %d.", linenum);
+         print_fatal_error(outfile, "Missing RESOURCE path on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "OPERATION"))
+      else if (!_cups_strcasecmp(token, "OPERATION"))
       {
        /*
         * Operation...
        */
 
-       if (!get_token(fp, token, sizeof(token), &linenum))
+       if (!get_token(fp, temp, sizeof(temp), &linenum))
        {
-         print_fatal_error("Missing OPERATION code on line %d.", linenum);
+         print_fatal_error(outfile, "Missing OPERATION code on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
 
-       if ((op = ippOpValue(token)) < 0 && (op = strtol(token, NULL, 0)) == 0)
+       expand_variables(vars, token, temp, sizeof(token));
+
+       if ((op = ippOpValue(token)) == (ipp_op_t)-1 &&
+           (op = (ipp_op_t)strtol(token, NULL, 0)) == 0)
        {
-         print_fatal_error("Bad OPERATION code \"%s\" on line %d.", token,
+         print_fatal_error(outfile, "Bad OPERATION code \"%s\" on line %d.", token,
                            linenum);
          pass = 0;
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "GROUP"))
+      else if (!_cups_strcasecmp(token, "GROUP"))
       {
        /*
         * Attribute group...
@@ -1219,14 +1704,14 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
-         print_fatal_error("Missing GROUP tag on line %d.", linenum);
+         print_fatal_error(outfile, "Missing GROUP tag on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
 
-       if ((value = ippTagValue(token)) < 0)
+       if ((value = ippTagValue(token)) == IPP_TAG_ZERO || value >= IPP_TAG_UNSUPPORTED_VALUE)
        {
-         print_fatal_error("Bad GROUP tag \"%s\" on line %d.", token, linenum);
+         print_fatal_error(outfile, "Bad GROUP tag \"%s\" on line %d.", token, linenum);
          pass = 0;
          goto test_exit;
        }
@@ -1236,37 +1721,49 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
         group = value;
       }
-      else if (!strcasecmp(token, "DELAY"))
+      else if (!_cups_strcasecmp(token, "DELAY"))
       {
        /*
         * Delay before operation...
        */
 
-        double delay;
+        double dval;                    /* Delay value */
 
-       if (!get_token(fp, token, sizeof(token), &linenum))
+       if (!get_token(fp, temp, sizeof(temp), &linenum))
        {
-         print_fatal_error("Missing DELAY value on line %d.", linenum);
+         print_fatal_error(outfile, "Missing DELAY value on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
 
-       if ((delay = _cupsStrScand(token, NULL, localeconv())) <= 0.0)
+       expand_variables(vars, token, temp, sizeof(token));
+
+       if ((dval = _cupsStrScand(token, &tokenptr, localeconv())) < 0.0 || (*tokenptr && *tokenptr != ','))
        {
-         print_fatal_error("Bad DELAY value \"%s\" on line %d.", token,
+         print_fatal_error(outfile, "Bad DELAY value \"%s\" on line %d.", token,
                            linenum);
          pass = 0;
          goto test_exit;
        }
-       else
-       {
-         if (Output == _CUPS_OUTPUT_TEST)
-           printf("    [%g second delay]\n", delay);
 
-         usleep((int)(1000000.0 * delay));
-       }
+        delay = (useconds_t)(1000000.0 * dval);
+
+        if (*tokenptr == ',')
+        {
+          if ((dval = _cupsStrScand(tokenptr + 1, &tokenptr, localeconv())) <= 0.0 || *tokenptr)
+          {
+            print_fatal_error(outfile, "Bad DELAY value \"%s\" on line %d.", token,
+                              linenum);
+            pass = 0;
+            goto test_exit;
+          }
+
+          repeat_interval = (useconds_t)(1000000.0 * dval);
+        }
+        else
+          repeat_interval = delay;
       }
-      else if (!strcasecmp(token, "ATTR"))
+      else if (!_cups_strcasecmp(token, "ATTR"))
       {
        /*
         * Attribute...
@@ -1274,14 +1771,14 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
-         print_fatal_error("Missing ATTR value tag on line %d.", linenum);
+         print_fatal_error(outfile, "Missing ATTR value tag on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
 
-       if ((value = ippTagValue(token)) == IPP_TAG_ZERO)
+       if ((value = ippTagValue(token)) < IPP_TAG_UNSUPPORTED_VALUE)
        {
-         print_fatal_error("Bad ATTR value tag \"%s\" on line %d.", token,
+         print_fatal_error(outfile, "Bad ATTR value tag \"%s\" on line %d.", token,
                            linenum);
          pass = 0;
          goto test_exit;
@@ -1289,59 +1786,134 @@ do_tests(_cups_vars_t *vars,           /* I - Variables */
 
        if (!get_token(fp, attr, sizeof(attr), &linenum))
        {
-         print_fatal_error("Missing ATTR name on line %d.", linenum);
+         print_fatal_error(outfile, "Missing ATTR name on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
 
-       if (!get_token(fp, temp, sizeof(temp), &linenum))
+        if (value < IPP_TAG_INTEGER)
+        {
+         /*
+          * Add out-of-band value - no value string needed...
+          */
+
+          token[0] = '\0';
+        }
+        else if (!get_token(fp, temp, sizeof(temp), &linenum))
        {
-         print_fatal_error("Missing ATTR value on line %d.", linenum);
+         print_fatal_error(outfile, "Missing ATTR value on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
+       else
+       {
+          expand_variables(vars, token, temp, sizeof(token));
+        }
 
-        expand_variables(vars, token, temp, sizeof(token));
+        attrptr = NULL;
 
         switch (value)
        {
+          default :
+              if (value < IPP_TAG_INTEGER)
+              {
+               /*
+                * Add out-of-band value...
+                */
+
+                attrptr = ippAddOutOfBand(request, group, value, attr);
+              }
+              else
+              {
+                print_fatal_error(outfile, "Unsupported ATTR value tag %s for \"%s\" on line %d.", ippTagString(value), attr, linenum);
+                pass = 0;
+                goto test_exit;
+              }
+              break;
+
          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, (char)atoi(token));
              break;
 
          case IPP_TAG_INTEGER :
          case IPP_TAG_ENUM :
              if (!strchr(token, ','))
-               ippAddInteger(request, group, value, attr,
-                             strtol(token, &tokenptr, 0));
+             {
+               if (isdigit(token[0] & 255) || token[0] == '-')
+                 attrptr = ippAddInteger(request, group, value, attr, (int)strtol(token, &tokenptr, 0));
+               else
+               {
+                  tokenptr = token;
+                  if ((i = ippEnumValue(attr, tokenptr)) >= 0)
+                  {
+                    attrptr  = ippAddInteger(request, group, value, attr, i);
+                    tokenptr += strlen(tokenptr);
+                  }
+               }
+             }
              else
              {
                int     values[100],    /* Values */
                        num_values = 1; /* Number of values */
 
-               values[0] = strtol(token, &tokenptr, 10);
+               if (!isdigit(token[0] & 255) && token[0] != '-' && value == IPP_TAG_ENUM)
+               {
+                 char *ptr;            /* Pointer to next terminator */
+
+                 if ((ptr = strchr(token, ',')) != NULL)
+                   *ptr++ = '\0';
+                 else
+                   ptr = token + strlen(token);
+
+                 if ((i = ippEnumValue(attr, token)) < 0)
+                   tokenptr = NULL;
+                 else
+                   tokenptr = ptr;
+               }
+               else
+                 i = (int)strtol(token, &tokenptr, 0);
+
+               values[0] = i;
+
                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 ++;
+                 if (!isdigit(*tokenptr & 255) && *tokenptr != '-')
+                 {
+                   char *ptr;          /* Pointer to next terminator */
+
+                   if (value != IPP_TAG_ENUM)
+                     break;
+
+                    if ((ptr = strchr(tokenptr, ',')) != NULL)
+                      *ptr++ = '\0';
+                    else
+                      ptr = tokenptr + strlen(tokenptr);
+
+                    if ((i = ippEnumValue(attr, tokenptr)) < 0)
+                      break;
+
+                    tokenptr = ptr;
+                 }
+                 else
+                   i = (int)strtol(tokenptr, &tokenptr, 0);
+
+                 values[num_values ++] = i;
                }
 
-               ippAddIntegers(request, group, value, attr, num_values, values);
+               attrptr = ippAddIntegers(request, group, value, attr, num_values, values);
              }
 
-             if (!tokenptr || *tokenptr)
+             if ((!token[0] || !tokenptr || *tokenptr) && !skip_test)
              {
-               print_fatal_error("Bad %s value \"%s\" on line %d.",
-                                 ippTagString(value), token, linenum);
+               print_fatal_error(outfile, "Bad %s value \'%s\' for \"%s\" on line %d.",
+                                 ippTagString(value), token, attr, linenum);
                pass = 0;
                goto test_exit;
              }
@@ -1353,32 +1925,34 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
                        yres;           /* Y resolution */
                char    *ptr;           /* Pointer into value */
 
-               xres = yres = strtol(token, (char **)&ptr, 10);
+               xres = yres = (int)strtol(token, (char **)&ptr, 10);
                if (ptr > token && xres > 0)
                {
                  if (*ptr == 'x')
-                 yres = strtol(ptr + 1, (char **)&ptr, 10);
+                   yres = (int)strtol(ptr + 1, (char **)&ptr, 10);
                }
 
                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, "dpcm") &&
+                    _cups_strcasecmp(ptr, "other")))
                {
-                 print_fatal_error("Bad resolution value \"%s\" on line %d.",
-                                   token, linenum);
+                 if (skip_test)
+                   break;
+
+                 print_fatal_error(outfile, "Bad resolution value \'%s\' for \"%s\" on line %d.", token, attr, linenum);
                  pass = 0;
                  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") ||
+                        !_cups_strcasecmp(ptr, "dpcm"))
+                 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;
 
@@ -1397,26 +1971,28 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
                 if ((num_vals & 1) || num_vals == 0)
                {
-                 print_fatal_error("Bad rangeOfInteger value \"%s\" on line "
-                                   "%d.", token, linenum);
+                 if (skip_test)
+                   break;
+
+                 print_fatal_error(outfile, "Bad rangeOfInteger value \'%s\' for \"%s\" on line %d.", token, attr, linenum);
                  pass = 0;
                  goto test_exit;
                }
 
-               ippAddRanges(request, group, attr, num_vals / 2, lowers,
-                            uppers);
+               attrptr = ippAddRanges(request, group, attr, num_vals / 2, lowers,
+                                      uppers);
              }
              break;
 
           case IPP_TAG_BEGIN_COLLECTION :
              if (!strcmp(token, "{"))
              {
-               ipp_t   *col = get_collection(vars, fp, &linenum);
+               ipp_t   *col = get_collection(outfile, vars, fp, &linenum);
                                        /* Collection value */
 
                 if (col)
                 {
-                 lastcol = ippAddCollection(request, group, attr, col);
+                 attrptr = lastcol = ippAddCollection(request, group, attr, col);
                  ippDelete(col);
                }
                else
@@ -1425,20 +2001,51 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
                  goto test_exit;
                }
               }
+              else if (skip_test)
+               break;
              else
              {
-               print_fatal_error("Bad ATTR collection value on line %d.",
-                                 linenum);
+               print_fatal_error(outfile, "Bad ATTR collection value for \"%s\" on line %d.", attr, linenum);
                pass = 0;
                goto test_exit;
              }
+
+             do
+             {
+               ipp_t   *col;                           /* Collection value */
+               off_t   savepos = cupsFileTell(fp);     /* Save position of file */
+               int     savelinenum = linenum;          /* Save line number */
+
+               if (!get_token(fp, token, sizeof(token), &linenum))
+                 break;
+
+               if (strcmp(token, ","))
+               {
+                 cupsFileSeek(fp, savepos);
+                 linenum = savelinenum;
+                 break;
+               }
+
+               if (!get_token(fp, token, sizeof(token), &linenum) || strcmp(token, "{"))
+               {
+                 print_fatal_error(outfile, "Unexpected \"%s\" on line %d.", token, linenum);
+                 pass = 0;
+                 goto test_exit;
+                 break;
+               }
+
+               if ((col = get_collection(outfile, vars, fp, &linenum)) == NULL)
+                 break;
+
+               ippSetCollection(request, &attrptr, ippGetCount(attrptr), col);
+               lastcol = attrptr;
+             }
+             while (!strcmp(token, "{"));
              break;
 
-         default :
-             print_fatal_error("Unsupported ATTR value tag %s on line %d.",
-                               ippTagString(value), linenum);
-             pass = 0;
-             goto test_exit;
+          case IPP_TAG_STRING :
+              attrptr = ippAddOctetString(request, group, attr, token, (int)strlen(token));
+             break;
 
          case IPP_TAG_TEXTLANG :
          case IPP_TAG_NAMELANG :
@@ -1451,7 +2058,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
              {
               /*
@@ -1468,18 +2075,30 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
                 for (ptr = strchr(token, ','); ptr; ptr = strchr(ptr, ','))
                {
-                 *ptr++ = '\0';
-                 values[num_values] = ptr;
-                 num_values ++;
+                 if (ptr > token && ptr[-1] == '\\')
+                   _cups_strcpy(ptr - 1, ptr);
+                 else
+                 {
+                   *ptr++ = '\0';
+                   values[num_values] = ptr;
+                   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 && !skip_test)
+       {
+         print_fatal_error(outfile, "Unable to add attribute \"%s\" on line %d.", attr, linenum);
+         pass = 0;
+         goto test_exit;
+       }
       }
-      else if (!strcasecmp(token, "FILE"))
+      else if (!_cups_strcasecmp(token, "FILE"))
       {
        /*
         * File...
@@ -1487,15 +2106,24 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
        if (!get_token(fp, temp, sizeof(temp), &linenum))
        {
-         print_fatal_error("Missing FILE filename on line %d.", linenum);
+         print_fatal_error(outfile, "Missing FILE filename on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
 
         expand_variables(vars, token, temp, sizeof(token));
        get_filename(testfile, filename, token, sizeof(filename));
+
+        if (access(filename, R_OK))
+        {
+         print_fatal_error(outfile, "Filename \"%s\" on line %d cannot be read.",
+                           temp, linenum);
+         print_fatal_error(outfile, "Filename mapped to \"%s\".", filename);
+         pass = 0;
+         goto test_exit;
+        }
       }
-      else if (!strcasecmp(token, "STATUS"))
+      else if (!_cups_strcasecmp(token, "STATUS"))
       {
        /*
         * Status...
@@ -1503,22 +2131,23 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
         if (num_statuses >= (int)(sizeof(statuses) / sizeof(statuses[0])))
        {
-         print_fatal_error("Too many STATUS's on line %d.", linenum);
+         print_fatal_error(outfile, "Too many STATUS's on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
 
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
-         print_fatal_error("Missing STATUS code on line %d.", linenum);
+         print_fatal_error(outfile, "Missing STATUS code on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
 
-       if ((statuses[num_statuses].status = ippErrorValue(token)) < 0 &&
-           (statuses[num_statuses].status = strtol(token, NULL, 0)) == 0)
+       if ((statuses[num_statuses].status = ippErrorValue(token))
+               == (ipp_status_t)-1 &&
+           (statuses[num_statuses].status = (ipp_status_t)strtol(token, NULL, 0)) == 0)
        {
-         print_fatal_error("Bad STATUS code \"%s\" on line %d.", token,
+         print_fatal_error(outfile, "Bad STATUS code \"%s\" on line %d.", token,
                            linenum);
          pass = 0;
          goto test_exit;
@@ -1527,25 +2156,32 @@ 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->define_match    = NULL;
+        last_status->define_no_match = NULL;
+       last_status->if_defined      = NULL;
+       last_status->if_not_defined  = NULL;
+       last_status->repeat_limit    = 1000;
+       last_status->repeat_match    = 0;
+       last_status->repeat_no_match = 0;
       }
-      else if (!strcasecmp(token, "EXPECT"))
+      else if (!_cups_strcasecmp(token, "EXPECT") || !_cups_strcasecmp(token, "EXPECT-ALL"))
       {
        /*
         * Expected attributes...
        */
 
+       int expect_all = !_cups_strcasecmp(token, "EXPECT-ALL");
+
         if (num_expects >= (int)(sizeof(expects) / sizeof(expects[0])))
         {
-         print_fatal_error("Too many EXPECT's on line %d.", linenum);
+         print_fatal_error(outfile, "Too many EXPECT's on line %d.", linenum);
          pass = 0;
          goto test_exit;
         }
 
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
-         print_fatal_error("Missing EXPECT name on line %d.", linenum);
+         print_fatal_error(outfile, "Missing EXPECT name on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
@@ -1554,6 +2190,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
        num_expects ++;
 
        memset(last_expect, 0, sizeof(_cups_expect_t));
+       last_expect->repeat_limit = 1000;
+       last_expect->expect_all   = expect_all;
 
         if (token[0] == '!')
         {
@@ -1568,18 +2206,18 @@ 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))
        {
-         print_fatal_error("Missing COUNT number on line %d.", linenum);
+         print_fatal_error(outfile, "Missing COUNT number on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
 
         if ((i = atoi(token)) <= 0)
        {
-         print_fatal_error("Bad COUNT \"%s\" on line %d.", token, linenum);
+         print_fatal_error(outfile, "Bad COUNT \"%s\" on line %d.", token, linenum);
          pass = 0;
          goto test_exit;
        }
@@ -1588,17 +2226,17 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
          last_expect->count = i;
        else
        {
-         print_fatal_error("COUNT without a preceding EXPECT on line %d.",
+         print_fatal_error(outfile, "COUNT without a preceding EXPECT on line %d.",
                            linenum);
          pass = 0;
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "DEFINE-MATCH"))
+      else if (!_cups_strcasecmp(token, "DEFINE-MATCH"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
-         print_fatal_error("Missing DEFINE-MATCH variable on line %d.",
+         print_fatal_error(outfile, "Missing DEFINE-MATCH variable on line %d.",
                            linenum);
          pass = 0;
          goto test_exit;
@@ -1606,19 +2244,21 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
        if (last_expect)
          last_expect->define_match = strdup(token);
+       else if (last_status)
+         last_status->define_match = strdup(token);
        else
        {
-         print_fatal_error("DEFINE-MATCH without a preceding EXPECT on line "
-                           "%d.", linenum);
+         print_fatal_error(outfile, "DEFINE-MATCH without a preceding EXPECT or STATUS "
+                           "on line %d.", linenum);
          pass = 0;
          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))
        {
-         print_fatal_error("Missing DEFINE-NO-MATCH variable on line %d.",
+         print_fatal_error(outfile, "Missing DEFINE-NO-MATCH variable on line %d.",
                            linenum);
          pass = 0;
          goto test_exit;
@@ -1626,19 +2266,21 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
        if (last_expect)
          last_expect->define_no_match = strdup(token);
+       else if (last_status)
+         last_status->define_no_match = strdup(token);
        else
        {
-         print_fatal_error("DEFINE-NO-MATCH without a preceding EXPECT on "
-                           "line %d.", linenum);
+         print_fatal_error(outfile, "DEFINE-NO-MATCH without a preceding EXPECT or "
+                           "STATUS on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "DEFINE-VALUE"))
+      else if (!_cups_strcasecmp(token, "DEFINE-VALUE"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
-         print_fatal_error("Missing DEFINE-VALUE variable on line %d.",
+         print_fatal_error(outfile, "Missing DEFINE-VALUE variable on line %d.",
                            linenum);
          pass = 0;
          goto test_exit;
@@ -1648,17 +2290,17 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
          last_expect->define_value = strdup(token);
        else
        {
-         print_fatal_error("DEFINE-VALUE without a preceding EXPECT on line "
-                           "%d.", linenum);
+         print_fatal_error(outfile, "DEFINE-VALUE without a preceding EXPECT on "
+                           "line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "OF-TYPE"))
+      else if (!_cups_strcasecmp(token, "OF-TYPE"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
-         print_fatal_error("Missing OF-TYPE value tag(s) on line %d.",
+         print_fatal_error(outfile, "Missing OF-TYPE value tag(s) on line %d.",
                            linenum);
          pass = 0;
          goto test_exit;
@@ -1668,42 +2310,96 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
          last_expect->of_type = strdup(token);
        else
        {
-         print_fatal_error("OF-TYPE without a preceding EXPECT on line %d.",
+         print_fatal_error(outfile, "OF-TYPE without a preceding EXPECT on line %d.",
                            linenum);
          pass = 0;
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "IN-GROUP"))
+      else if (!_cups_strcasecmp(token, "IN-GROUP"))
       {
         ipp_tag_t      in_group;       /* IN-GROUP value */
 
 
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
-         print_fatal_error("Missing IN-GROUP group tag on line %d.", linenum);
+         print_fatal_error(outfile, "Missing IN-GROUP group tag on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
 
-        if ((in_group = ippTagValue(token)) == (ipp_tag_t)-1)
+        if ((in_group = ippTagValue(token)) == IPP_TAG_ZERO || in_group >= IPP_TAG_UNSUPPORTED_VALUE)
        {
+          print_fatal_error(outfile, "Bad IN-GROUP group tag \"%s\" on line %d.", token, linenum);
+          pass = 0;
+          goto test_exit;
        }
        else if (last_expect)
          last_expect->in_group = in_group;
        else
        {
-         print_fatal_error("IN-GROUP without a preceding EXPECT on line %d.",
-                           linenum);
+         print_fatal_error(outfile, "IN-GROUP without a preceding EXPECT on line %d.", linenum);
+         pass = 0;
+         goto test_exit;
+       }
+      }
+      else if (!_cups_strcasecmp(token, "REPEAT-LIMIT"))
+      {
+       if (!get_token(fp, token, sizeof(token), &linenum))
+       {
+         print_fatal_error(outfile, "Missing REPEAT-LIMIT value on line %d.", linenum);
+         pass = 0;
+         goto test_exit;
+       }
+       else if (atoi(token) <= 0)
+       {
+         print_fatal_error(outfile, "Bad REPEAT-LIMIT value on line %d.", linenum);
+         pass = 0;
+         goto test_exit;
+       }
+
+        if (last_status)
+          last_status->repeat_limit = atoi(token);
+       else if (last_expect)
+         last_expect->repeat_limit = atoi(token);
+       else
+       {
+         print_fatal_error(outfile, "REPEAT-LIMIT without a preceding EXPECT or STATUS on line %d.", linenum);
+         pass = 0;
+         goto test_exit;
+       }
+      }
+      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(outfile, "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(outfile, "REPEAT-NO-MATCH without a preceding EXPECT or STATUS on ine %d.", linenum);
          pass = 0;
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "SAME-COUNT-AS"))
+      else if (!_cups_strcasecmp(token, "SAME-COUNT-AS"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
-         print_fatal_error("Missing SAME-COUNT-AS name on line %d.", linenum);
+         print_fatal_error(outfile, "Missing SAME-COUNT-AS name on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
@@ -1712,17 +2408,17 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
          last_expect->same_count_as = strdup(token);
        else
        {
-         print_fatal_error("SAME-COUNT-AS without a preceding EXPECT on line "
+         print_fatal_error(outfile, "SAME-COUNT-AS without a preceding EXPECT on line "
                            "%d.", linenum);
          pass = 0;
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "IF-DEFINED"))
+      else if (!_cups_strcasecmp(token, "IF-DEFINED"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
-         print_fatal_error("Missing IF-DEFINED name on line %d.", linenum);
+         print_fatal_error(outfile, "Missing IF-DEFINED name on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
@@ -1733,17 +2429,16 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
          last_status->if_defined = strdup(token);
        else
        {
-         print_fatal_error("IF-DEFINED without a preceding EXPECT or STATUS "
-                           "on line %d.", linenum);
+         print_fatal_error(outfile, "IF-DEFINED without a preceding EXPECT or STATUS on line %d.", linenum);
          pass = 0;
          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))
        {
-         print_fatal_error("Missing IF-NOT-DEFINED name on line %d.", linenum);
+         print_fatal_error(outfile, "Missing IF-NOT-DEFINED name on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
@@ -1754,17 +2449,39 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
          last_status->if_not_defined = strdup(token);
        else
        {
-         print_fatal_error("IF-NOT-DEFINED without a preceding EXPECT or STATUS "
-                           "on line %d.", linenum);
+         print_fatal_error(outfile, "IF-NOT-DEFINED without a preceding EXPECT or STATUS on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "WITH-VALUE"))
+      else if (!_cups_strcasecmp(token, "WITH-ALL-VALUES") ||
+               !_cups_strcasecmp(token, "WITH-ALL-HOSTNAMES") ||
+               !_cups_strcasecmp(token, "WITH-ALL-RESOURCES") ||
+               !_cups_strcasecmp(token, "WITH-ALL-SCHEMES") ||
+               !_cups_strcasecmp(token, "WITH-HOSTNAME") ||
+               !_cups_strcasecmp(token, "WITH-RESOURCE") ||
+               !_cups_strcasecmp(token, "WITH-SCHEME") ||
+               !_cups_strcasecmp(token, "WITH-VALUE"))
       {
+       if (last_expect)
+       {
+         if (!_cups_strcasecmp(token, "WITH-ALL-HOSTNAMES") ||
+             !_cups_strcasecmp(token, "WITH-HOSTNAME"))
+           last_expect->with_flags = _CUPS_WITH_HOSTNAME;
+         else if (!_cups_strcasecmp(token, "WITH-ALL-RESOURCES") ||
+             !_cups_strcasecmp(token, "WITH-RESOURCE"))
+           last_expect->with_flags = _CUPS_WITH_RESOURCE;
+         else if (!_cups_strcasecmp(token, "WITH-ALL-SCHEMES") ||
+             !_cups_strcasecmp(token, "WITH-SCHEME"))
+           last_expect->with_flags = _CUPS_WITH_SCHEME;
+
+         if (!_cups_strncasecmp(token, "WITH-ALL-", 9))
+           last_expect->with_flags |= _CUPS_WITH_ALL;
+        }
+
        if (!get_token(fp, temp, sizeof(temp), &linenum))
        {
-         print_fatal_error("Missing WITH-VALUE value on line %d.", linenum);
+         print_fatal_error(outfile, "Missing %s value on line %d.", token, linenum);
          pass = 0;
          goto test_exit;
        }
@@ -1785,11 +2502,11 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
            * WITH-VALUE is a POSIX extended regular expression.
            */
 
-           last_expect->with_value = calloc(1, tokenptr - token);
-           last_expect->with_regex = 1;
+           last_expect->with_value = calloc(1, (size_t)(tokenptr - token));
+           last_expect->with_flags |= _CUPS_WITH_REGEX;
 
            if (last_expect->with_value)
-             memcpy(last_expect->with_value, token + 1, tokenptr - token - 1);
+             memcpy(last_expect->with_value, token + 1, (size_t)(tokenptr - token - 1));
          }
          else
          {
@@ -1797,18 +2514,59 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
            * WITH-VALUE is a literal value...
            */
 
+           char *ptr;                  /* Pointer into value */
+
+            for (ptr = token; *ptr; ptr ++)
+            {
+             if (*ptr == '\\' && ptr[1])
+             {
+              /*
+               * Remove \ from \foo...
+               */
+
+               _cups_strcpy(ptr, ptr + 1);
+             }
+           }
+
            last_expect->with_value = strdup(token);
+           last_expect->with_flags |= _CUPS_WITH_LITERAL;
          }
        }
        else
        {
-         print_fatal_error("WITH-VALUE without a preceding EXPECT on line %d.",
-                           linenum);
+         print_fatal_error(outfile, "%s without a preceding EXPECT on line %d.", token, linenum);
+         pass = 0;
+         goto test_exit;
+       }
+      }
+      else if (!_cups_strcasecmp(token, "WITH-VALUE-FROM"))
+      {
+       if (!get_token(fp, temp, sizeof(temp), &linenum))
+       {
+         print_fatal_error(outfile, "Missing %s value on line %d.", token, linenum);
+         pass = 0;
+         goto test_exit;
+       }
+
+        if (last_expect)
+       {
+        /*
+         * Expand any variables in the value and then save it.
+         */
+
+         expand_variables(vars, token, temp, sizeof(token));
+
+         last_expect->with_value_from = strdup(token);
+         last_expect->with_flags      = _CUPS_WITH_LITERAL;
+       }
+       else
+       {
+         print_fatal_error(outfile, "%s without a preceding EXPECT on line %d.", token, linenum);
          pass = 0;
          goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "DISPLAY"))
+      else if (!_cups_strcasecmp(token, "DISPLAY"))
       {
        /*
         * Display attributes...
@@ -1816,14 +2574,14 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
         if (num_displayed >= (int)(sizeof(displayed) / sizeof(displayed[0])))
        {
-         print_fatal_error("Too many DISPLAY's on line %d", linenum);
+         print_fatal_error(outfile, "Too many DISPLAY's on line %d", linenum);
          pass = 0;
          goto test_exit;
        }
 
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
-         print_fatal_error("Missing DISPLAY name on line %d.", linenum);
+         print_fatal_error(outfile, "Missing DISPLAY name on line %d.", linenum);
          pass = 0;
          goto test_exit;
        }
@@ -1833,8 +2591,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
       }
       else
       {
-       print_fatal_error("Unexpected token %s seen on line %d.", token,
-                         linenum);
+       print_fatal_error(outfile, "Unexpected token %s seen on line %d.", token, linenum);
        pass = 0;
        goto test_exit;
       }
@@ -1844,431 +2601,316 @@ do_tests(_cups_vars_t *vars,          /* I - Variables */
     * Submit the IPP request...
     */
 
-    request->request.op.version[0]   = version / 10;
-    request->request.op.version[1]   = version % 10;
-    request->request.op.operation_id = op;
-    request->request.op.request_id   = request_id;
+    TestCount ++;
+
+    ippSetVersion(request, version / 10, version % 10);
+    ippSetOperation(request, op);
+    ippSetRequestId(request, request_id);
 
     if (Output == _CUPS_OUTPUT_PLIST)
     {
-      puts("<dict>");
-      puts("<key>Name</key>");
-      print_xml_string("string", name);
-      puts("<key>Operation</key>");
-      print_xml_string("string", ippOpString(op));
-      puts("<key>RequestAttributes</key>");
-      puts("<array>");
-      puts("<dict>");
-      for (attrptr = request->attrs, group = attrptr->group_tag;
-           attrptr;
-          attrptr = attrptr->next)
-       print_attr(attrptr, &group);
-      puts("</dict>");
-      puts("</array>");
+      cupsFilePuts(outfile, "<dict>\n");
+      cupsFilePuts(outfile, "<key>Name</key>\n");
+      print_xml_string(outfile, "string", name);
+      if (file_id[0])
+      {
+       cupsFilePuts(outfile, "<key>FileId</key>\n");
+       print_xml_string(outfile, "string", file_id);
+      }
+      if (test_id[0])
+      {
+        cupsFilePuts(outfile, "<key>TestId</key>\n");
+        print_xml_string(outfile, "string", test_id);
+      }
+      cupsFilePuts(outfile, "<key>Version</key>\n");
+      cupsFilePrintf(outfile, "<string>%d.%d</string>\n", version / 10, version % 10);
+      cupsFilePuts(outfile, "<key>Operation</key>\n");
+      print_xml_string(outfile, "string", ippOpString(op));
+      cupsFilePuts(outfile, "<key>RequestId</key>\n");
+      cupsFilePrintf(outfile, "<integer>%d</integer>\n", request_id);
+      cupsFilePuts(outfile, "<key>RequestAttributes</key>\n");
+      cupsFilePuts(outfile, "<array>\n");
+      if (request->attrs)
+      {
+       cupsFilePuts(outfile, "<dict>\n");
+       for (attrptr = request->attrs,
+                group = attrptr ? attrptr->group_tag : IPP_TAG_ZERO;
+            attrptr;
+            attrptr = attrptr->next)
+         print_attr(outfile, Output, attrptr, &group);
+       cupsFilePuts(outfile, "</dict>\n");
+      }
+      cupsFilePuts(outfile, "</array>\n");
     }
-    else if (Output == _CUPS_OUTPUT_TEST)
+
+    if (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != cupsFileStdout()))
     {
       if (Verbosity)
       {
-        printf("    %s:\n", ippOpString(op));
+       cupsFilePrintf(cupsFileStdout(), "    %s:\n", ippOpString(op));
 
-        for (attrptr = request->attrs; attrptr; attrptr = attrptr->next)
-          print_attr(attrptr, NULL);
+       for (attrptr = request->attrs; attrptr; attrptr = attrptr->next)
+         print_attr(cupsFileStdout(), _CUPS_OUTPUT_TEST, attrptr, NULL);
       }
 
-      printf("    %-69.69s [", name);
-      fflush(stdout);
+      cupsFilePrintf(cupsFileStdout(), "    %-68.68s [", name);
     }
 
     if ((skip_previous && !prev_pass) || skip_test)
     {
+      SkipCount ++;
+
       ippDelete(request);
       request = NULL;
 
       if (Output == _CUPS_OUTPUT_PLIST)
       {
-       puts("<key>Successful</key>");
-       puts("<true />");
-       puts("<key>StatusCode</key>");
-       print_xml_string("string", "skip");
-       puts("<key>ResponseAttributes</key>");
-       puts("<dict>");
-       puts("</dict>");
+       cupsFilePuts(outfile, "<key>Successful</key>\n");
+       cupsFilePuts(outfile, "<true />\n");
+       cupsFilePuts(outfile, "<key>Skipped</key>\n");
+       cupsFilePuts(outfile, "<true />\n");
+       cupsFilePuts(outfile, "<key>StatusCode</key>\n");
+       print_xml_string(outfile, "string", "skip");
+       cupsFilePuts(outfile, "<key>ResponseAttributes</key>\n");
+       cupsFilePuts(outfile, "<dict />\n");
       }
-      else if (Output == _CUPS_OUTPUT_TEST)
-       puts("SKIP]");
+
+      if (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != cupsFileStdout()))
+       cupsFilePuts(cupsFileStdout(), "SKIP]\n");
 
       goto skip_error;
     }
 
-    if (transfer == _CUPS_TRANSFER_CHUNKED ||
-        (transfer == _CUPS_TRANSFER_AUTO && filename[0]))
+    PasswordTries   = 0;
+
+    do
     {
-     /*
-      * Send request using chunking...
-      */
+      if (delay > 0)
+        usleep(delay);
 
-      http_status_t status = cupsSendRequest(http, request, resource, 0);
+      delay = repeat_interval;
+      repeat_count ++;
 
-      if (status == HTTP_CONTINUE && filename[0])
+      status = HTTP_STATUS_OK;
+
+      if (transfer == _CUPS_TRANSFER_CHUNKED ||
+         (transfer == _CUPS_TRANSFER_AUTO && filename[0]))
       {
-        int    fd;                     /* File to send */
-        char   buffer[8192];           /* Copy buffer */
-        ssize_t        bytes;                  /* Bytes read/written */
+       /*
+       * Send request using chunking - a 0 length means "chunk".
+       */
 
-        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 = 0;
+      }
+      else
+      {
+       /*
+       * Send request using content length...
+       */
+
+       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...
+         */
 
-          status = HTTP_ERROR;
+         while ((bytes = cupsFileRead(reqfile, buffer, sizeof(buffer))) > 0)
+           length += (size_t)bytes;
+
+         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;
 
-    request   = NULL;
-    prev_pass = 1;
+      if (status != HTTP_STATUS_ERROR)
+      {
+       while (!response && !Cancel && prev_pass)
+       {
+         status = cupsSendRequest(http, request, resource, length);
 
-    if (!response)
-      prev_pass = pass = 0;
-    else
-    {
-      if (http->version != HTTP_1_1)
-        prev_pass = pass = 0;
+#ifdef HAVE_LIBZ
+         if (compression[0])
+           httpSetField(http, HTTP_FIELD_CONTENT_ENCODING, compression);
+#endif /* HAVE_LIBZ */
 
-      if (response->request.status.request_id != request_id)
-        prev_pass = pass = 0;
+         if (!Cancel && status == HTTP_STATUS_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, (size_t)bytes)) != HTTP_STATUS_CONTINUE)
+                 break;
 
-      if (version &&
-          (response->request.status.version[0] != (version / 10) ||
-          response->request.status.version[1] != (version % 10)))
-        prev_pass = pass = 0;
+             cupsFileClose(reqfile);
+           }
+           else
+           {
+             snprintf(buffer, sizeof(buffer), "%s: %s", filename,
+                      strerror(errno));
+             _cupsSetError(IPP_INTERNAL_ERROR, buffer, 0);
 
-      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);
-      }
+             status = HTTP_STATUS_ERROR;
+           }
+         }
 
-      if ((attrptr = ippFindAttribute(response, "job-uri",
-                                      IPP_TAG_URI)) != NULL)
-       set_variable(vars, "job-uri", attrptr->values[0].string.text);
+        /*
+         * Get the server's response...
+         */
 
-      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 (!Cancel && status != HTTP_STATUS_ERROR)
+         {
+           response = cupsGetResponse(http, resource);
+           status   = httpGetStatus(http);
+         }
 
-      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)
-      {
-        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 (!Cancel && status == HTTP_STATUS_ERROR && http->error != EINVAL &&
+#ifdef WIN32
+             http->error != WSAETIMEDOUT)
+#else
+             http->error != ETIMEDOUT)
+#endif /* WIN32 */
+         {
+           if (httpReconnect2(http, 30000, NULL))
+             prev_pass = 0;
+         }
+         else if (status == HTTP_STATUS_ERROR || status == HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED)
+         {
+           prev_pass = 0;
+           break;
+         }
+         else if (status != HTTP_STATUS_OK)
+         {
+           httpFlush(http);
 
-      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;
-
-      for (attrptr = response->attrs, group = attrptr->group_tag;
-           attrptr;
-          attrptr = attrptr->next)
-      {
-        if (attrptr->group_tag < group && attrptr->group_tag != IPP_TAG_ZERO)
-       {
-         prev_pass = pass = 0;
-         break;
-       }
+           if (status == HTTP_STATUS_UNAUTHORIZED)
+             continue;
 
-        if (!validate_attr(attrptr, 0))
-       {
-         prev_pass = pass = 0;
-         break;
+           break;
+         }
        }
       }
 
-      for (i = 0; i < num_statuses; i ++)
+      if (!Cancel && status == HTTP_STATUS_ERROR && http->error != EINVAL &&
+#ifdef WIN32
+         http->error != WSAETIMEDOUT)
+#else
+         http->error != ETIMEDOUT)
+#endif /* WIN32 */
       {
-        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)
-         break;
+       if (httpReconnect2(http, 30000, NULL))
+         prev_pass = 0;
       }
-
-      if (i == num_statuses && num_statuses > 0)
-       prev_pass = pass = 0;
-      else
+      else if (status == HTTP_STATUS_ERROR)
       {
-        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;
-
-          found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO);
-
-          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)
-             prev_pass = pass = 0;
-
-           continue;
-          }
-
-          if (found &&
-             !with_value(expect->with_value, expect->with_regex, found, 0))
-          {
-           if (expect->define_no_match)
-             set_variable(vars, expect->define_no_match, "1");
-           else if (!expect->define_match)
-             prev_pass = pass = 0;
-
-            continue;
-          }
+        if (!Cancel)
+          httpReconnect2(http, 30000, NULL);
 
-          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)
-             prev_pass = pass = 0;
+       prev_pass = 0;
+      }
+      else if (status != HTTP_STATUS_OK)
+      {
+        httpFlush(http);
+        prev_pass = 0;
+      }
 
-            continue;
-         }
+     /*
+      * Check results of request...
+      */
 
-          if (found && expect->same_count_as)
-          {
-            attrptr = ippFindAttribute(response, expect->same_count_as,
-                                       IPP_TAG_ZERO);
+      cupsArrayClear(errors);
 
-            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)
-               prev_pass = pass = 0;
+      if (http->version != HTTP_1_1)
+       add_stringf(errors, "Bad HTTP version (%d.%d)", http->version / 100,
+                   http->version % 100);
 
-              continue;
-            }
-          }
+      if (ValidateHeaders)
+      {
+        const char *header;               /* HTTP header value */
 
-         if (found && expect->define_match)
-           set_variable(vars, expect->define_match, "1");
+        if ((header = httpGetField(http, HTTP_FIELD_CONTENT_TYPE)) == NULL || _cups_strcasecmp(header, "application/ipp"))
+          add_stringf(errors, "Bad HTTP Content-Type in response (%s)", header && *header ? header : "<missing>");
 
-         if (found && expect->define_value)
-         {
-           _ippAttrString(found, token, sizeof(token));
-           set_variable(vars, expect->define_value, token);
-         }
-        }
+        if ((header = httpGetField(http, HTTP_FIELD_DATE)) != NULL && *header && httpGetDateTime(header) == 0)
+          add_stringf(errors, "Bad HTTP Date in response (%s)", header);
       }
-    }
 
-    if (Output == _CUPS_OUTPUT_PLIST)
-    {
-      puts("<key>Successful</key>");
-      puts(prev_pass ? "<true />" : "<false />");
-      puts("<key>StatusCode</key>");
-      print_xml_string("string", ippErrorString(cupsLastError()));
-      puts("<key>ResponseAttributes</key>");
-      puts("<array>");
-      puts("<dict>");
-      for (attrptr = response ? response->attrs : NULL,
-               group = attrptr ? attrptr->group_tag : IPP_TAG_ZERO;
-          attrptr;
-          attrptr = attrptr->next)
-       print_attr(attrptr, &group);
-      puts("</dict>");
-      puts("</array>");
-    }
-    else if (Output == _CUPS_OUTPUT_TEST)
-    {
-      puts(prev_pass ? "PASS]" : "FAIL]");
-
-      if (Verbosity && response)
+      if (!response)
       {
-       printf("        RECEIVED: %lu bytes in response\n",
-              (unsigned long)ippLength(response));
-       printf("        status-code = %x (%s)\n", cupsLastError(),
-              ippErrorString(cupsLastError()));
+       /*
+        * No response, log error...
+        */
 
-       for (attrptr = response->attrs;
-            attrptr != NULL;
-            attrptr = attrptr->next)
-       {
-         print_attr(attrptr, NULL);
-       }
+       add_stringf(errors, "IPP request failed with status %s (%s)",
+                   ippErrorString(cupsLastError()),
+                   cupsLastErrorString());
       }
-    }
-    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 (Output >= _CUPS_OUTPUT_LIST)
+      else
       {
-       size_t  width;                  /* Length of value */
-
-
-        for (i = 0; i < num_displayed; i ++)
-        {
-          widths[i] = strlen(displayed[i]);
-
-          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;
-          }
-        }
-
-        if (Output == _CUPS_OUTPUT_CSV)
-         print_csv(NULL, num_displayed, displayed, widths);
-       else
-         print_line(NULL, num_displayed, displayed, widths);
-
-        attrptr = response->attrs;
+       /*
+        * Collect common attribute values...
+        */
 
-        while (attrptr)
-        {
-         while (attrptr && attrptr->group_tag <= IPP_TAG_OPERATION)
-           attrptr = attrptr->next;
+       if ((attrptr = ippFindAttribute(response, "job-id",
+                                       IPP_TAG_INTEGER)) != NULL)
+       {
+         snprintf(temp, sizeof(temp), "%d", attrptr->values[0].integer);
+         set_variable(outfile, vars, "job-id", temp);
+       }
 
-          if (attrptr)
-          {
-            if (Output == _CUPS_OUTPUT_CSV)
-             print_csv(attrptr, num_displayed, displayed, widths);
-           else
-             print_line(attrptr, num_displayed, displayed, widths);
+       if ((attrptr = ippFindAttribute(response, "job-uri",
+                                       IPP_TAG_URI)) != NULL)
+         set_variable(outfile, vars, "job-uri", attrptr->values[0].string.text);
 
-            while (attrptr && attrptr->group_tag > IPP_TAG_OPERATION)
-              attrptr = attrptr->next;
-          }
-        }
-      }
-      else
-      {
-       for (attrptr = response->attrs;
-            attrptr != NULL;
-            attrptr = attrptr->next)
+       if ((attrptr = ippFindAttribute(response, "notify-subscription-id",
+                                       IPP_TAG_INTEGER)) != NULL)
        {
-         if (attrptr->name)
-         {
-           for (i = 0; i < num_displayed; i ++)
-           {
-             if (!strcmp(displayed[i], attrptr->name))
-             {
-               print_attr(attrptr, NULL);
-               break;
-             }
-           }
-         }
+         snprintf(temp, sizeof(temp), "%d", attrptr->values[0].integer);
+         set_variable(outfile, vars, "notify-subscription-id", temp);
        }
-      }
-    }
-    else if (!prev_pass)
-    {
-      if (Output == _CUPS_OUTPUT_PLIST)
-      {
-       puts("<key>Errors</key>");
-       puts("<array>");
-      }
 
-      if (http->version != HTTP_1_1)
-       print_test_error("Bad HTTP version (%d.%d)", http->version / 100,
-                        http->version % 100);
+       /*
+        * Check response, validating groups and attributes and logging errors
+        * as needed...
+        */
+
+       if (response->state != IPP_DATA)
+         add_stringf(errors,
+                     "Missing end-of-attributes-tag in response "
+                     "(RFC 2910 section 3.5.1)");
 
-      if (!response)
-       print_test_error("IPP request failed with status %s (%s)",
-                        ippErrorString(cupsLastError()),
-                        cupsLastErrorString());
-      else
-      {
        if (version &&
            (response->request.status.version[0] != (version / 10) ||
             response->request.status.version[1] != (version % 10)))
-          print_test_error("Bad version %d.%d in response - expected %d.%d "
-                          "(RFC 2911 section 3.1.8).",
-                          response->request.status.version[0],
-                          response->request.status.version[1],
-                          version / 10, version % 10);
+          add_stringf(errors,
+                      "Bad version %d.%d in response - expected %d.%d "
+                     "(RFC 2911 section 3.1.8).",
+                     response->request.status.version[0],
+                     response->request.status.version[1],
+                     version / 10, version % 10);
 
        if (response->request.status.request_id != request_id)
-         print_test_error("Bad request ID %d in response - expected %d "
-                          "(RFC 2911 section 3.1.1)",
-                          response->request.status.request_id, request_id);
+         add_stringf(errors,
+                     "Bad request ID %d in response - expected %d "
+                     "(RFC 2911 section 3.1.1)",
+                     response->request.status.request_id, request_id);
 
        attrptr = response->attrs;
        if (!attrptr)
-         print_test_error("Missing first attribute \"attributes-charset "
-                          "(charset)\" in group operation-attributes-tag "
-                          "(RFC 2911 section 3.1.4).");
+         add_stringf(errors,
+                     "Missing first attribute \"attributes-charset "
+                     "(charset)\" in group operation-attributes-tag "
+                     "(RFC 2911 section 3.1.4).");
        else
        {
          if (!attrptr->name ||
@@ -2276,94 +2918,166 @@ do_tests(_cups_vars_t *vars,           /* I - Variables */
              attrptr->group_tag != IPP_TAG_OPERATION ||
              attrptr->num_values != 1 ||
              strcmp(attrptr->name, "attributes-charset"))
-           print_test_error("Bad first attribute \"%s (%s%s)\" in group %s, "
-                            "expected \"attributes-charset (charset)\" in "
-                            "group operation-attributes-tag (RFC 2911 section "
-                            "3.1.4).",
-                            attrptr->name ? attrptr->name : "(null)",
-                            attrptr->num_values > 1 ? "1setOf " : "",
-                            ippTagString(attrptr->value_tag),
-                            ippTagString(attrptr->group_tag));
+           add_stringf(errors,
+                       "Bad first attribute \"%s (%s%s)\" in group %s, "
+                       "expected \"attributes-charset (charset)\" in "
+                       "group operation-attributes-tag (RFC 2911 section "
+                       "3.1.4).",
+                       attrptr->name ? attrptr->name : "(null)",
+                       attrptr->num_values > 1 ? "1setOf " : "",
+                       ippTagString(attrptr->value_tag),
+                       ippTagString(attrptr->group_tag));
 
          attrptr = attrptr->next;
          if (!attrptr)
-           print_test_error("Missing second attribute \"attributes-natural-"
-                            "language (naturalLanguage)\" in group "
-                            "operation-attributes-tag (RFC 2911 section "
-                            "3.1.4).");
+           add_stringf(errors,
+                       "Missing second attribute \"attributes-natural-"
+                       "language (naturalLanguage)\" in group "
+                       "operation-attributes-tag (RFC 2911 section "
+                       "3.1.4).");
          else if (!attrptr->name ||
                   attrptr->value_tag != IPP_TAG_LANGUAGE ||
                   attrptr->group_tag != IPP_TAG_OPERATION ||
                   attrptr->num_values != 1 ||
                   strcmp(attrptr->name, "attributes-natural-language"))
-           print_test_error("Bad first attribute \"%s (%s%s)\" in group %s, "
-                            "expected \"attributes-natural-language "
-                            "(naturalLanguage)\" in group "
-                            "operation-attributes-tag (RFC 2911 section "
-                            "3.1.4).",
-                            attrptr->name ? attrptr->name : "(null)",
-                            attrptr->num_values > 1 ? "1setOf " : "",
-                            ippTagString(attrptr->value_tag),
-                            ippTagString(attrptr->group_tag));
+           add_stringf(errors,
+                       "Bad first attribute \"%s (%s%s)\" in group %s, "
+                       "expected \"attributes-natural-language "
+                       "(naturalLanguage)\" in group "
+                       "operation-attributes-tag (RFC 2911 section "
+                       "3.1.4).",
+                       attrptr->name ? attrptr->name : "(null)",
+                       attrptr->num_values > 1 ? "1setOf " : "",
+                       ippTagString(attrptr->value_tag),
+                       ippTagString(attrptr->group_tag));
         }
 
        if ((attrptr = ippFindAttribute(response, "status-message",
                                         IPP_TAG_ZERO)) != NULL)
        {
          if (attrptr->value_tag != IPP_TAG_TEXT)
-           print_test_error("status-message (text(255)) has wrong value tag "
-                            "%s (RFC 2911 section 3.1.6.2).",
-                            ippTagString(attrptr->value_tag));
+           add_stringf(errors,
+                       "status-message (text(255)) has wrong value tag "
+                       "%s (RFC 2911 section 3.1.6.2).",
+                       ippTagString(attrptr->value_tag));
          if (attrptr->group_tag != IPP_TAG_OPERATION)
-           print_test_error("status-message (text(255)) has wrong group tag "
-                            "%s (RFC 2911 section 3.1.6.2).",
-                            ippTagString(attrptr->group_tag));
+           add_stringf(errors,
+                       "status-message (text(255)) has wrong group tag "
+                       "%s (RFC 2911 section 3.1.6.2).",
+                       ippTagString(attrptr->group_tag));
          if (attrptr->num_values != 1)
-           print_test_error("status-message (text(255)) has %d values "
-                            "(RFC 2911 section 3.1.6.2).",
-                            attrptr->num_values);
+           add_stringf(errors,
+                       "status-message (text(255)) has %d values "
+                       "(RFC 2911 section 3.1.6.2).",
+                       attrptr->num_values);
          if (attrptr->value_tag == IPP_TAG_TEXT &&
              strlen(attrptr->values[0].string.text) > 255)
-           print_test_error("status-message (text(255)) has bad length %d"
-                            " (RFC 2911 section 3.1.6.2).",
-                            (int)strlen(attrptr->values[0].string.text));
+           add_stringf(errors,
+                       "status-message (text(255)) has bad length %d"
+                       " (RFC 2911 section 3.1.6.2).",
+                       (int)strlen(attrptr->values[0].string.text));
         }
 
        if ((attrptr = ippFindAttribute(response, "detailed-status-message",
                                         IPP_TAG_ZERO)) != NULL)
        {
          if (attrptr->value_tag != IPP_TAG_TEXT)
-           print_test_error("detailed-status-message (text(MAX)) has wrong "
-                            "value tag %s (RFC 2911 section 3.1.6.3).",
-                            ippTagString(attrptr->value_tag));
+           add_stringf(errors,
+                       "detailed-status-message (text(MAX)) has wrong "
+                       "value tag %s (RFC 2911 section 3.1.6.3).",
+                       ippTagString(attrptr->value_tag));
          if (attrptr->group_tag != IPP_TAG_OPERATION)
-           print_test_error("detailed-status-message (text(MAX)) has wrong "
-                            "group tag %s (RFC 2911 section 3.1.6.3).",
-                            ippTagString(attrptr->group_tag));
+           add_stringf(errors,
+                       "detailed-status-message (text(MAX)) has wrong "
+                       "group tag %s (RFC 2911 section 3.1.6.3).",
+                       ippTagString(attrptr->group_tag));
          if (attrptr->num_values != 1)
-           print_test_error("detailed-status-message (text(MAX)) has %d values"
-                            " (RFC 2911 section 3.1.6.3).",
-                            attrptr->num_values);
+           add_stringf(errors,
+                       "detailed-status-message (text(MAX)) has %d values"
+                       " (RFC 2911 section 3.1.6.3).",
+                       attrptr->num_values);
          if (attrptr->value_tag == IPP_TAG_TEXT &&
              strlen(attrptr->values[0].string.text) > 1023)
-           print_test_error("detailed-status-message (text(MAX)) has bad "
-                            "length %d (RFC 2911 section 3.1.6.3).",
-                            (int)strlen(attrptr->values[0].string.text));
+           add_stringf(errors,
+                       "detailed-status-message (text(MAX)) has bad "
+                       "length %d (RFC 2911 section 3.1.6.3).",
+                       (int)strlen(attrptr->values[0].string.text));
         }
 
-       for (attrptr = response->attrs, group = attrptr->group_tag;
+       a = cupsArrayNew((cups_array_func_t)strcmp, NULL);
+
+       for (attrptr = response->attrs,
+                group = attrptr ? attrptr->group_tag : IPP_TAG_ZERO;
             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)
+         {
+           int out_of_order = 0;       /* Are attribute groups out-of-order? */
+           cupsArrayClear(a);
+
+            switch (attrptr->group_tag)
+            {
+              case IPP_TAG_ZERO :
+                  break;
+
+              case IPP_TAG_OPERATION :
+                  out_of_order = 1;
+                  break;
+
+              case IPP_TAG_UNSUPPORTED_GROUP :
+                  if (group != IPP_TAG_OPERATION)
+                   out_of_order = 1;
+                  break;
+
+              case IPP_TAG_JOB :
+              case IPP_TAG_PRINTER :
+                  if (group != IPP_TAG_OPERATION &&
+                      group != IPP_TAG_UNSUPPORTED_GROUP)
+                   out_of_order = 1;
+                  break;
+
+              case IPP_TAG_SUBSCRIPTION :
+                  if (group > attrptr->group_tag &&
+                      group != IPP_TAG_DOCUMENT)
+                   out_of_order = 1;
+                  break;
+
+              default :
+                  if (group > attrptr->group_tag)
+                   out_of_order = 1;
+                  break;
+            }
+
+            if (out_of_order)
+             add_stringf(errors, "Attribute groups out of order (%s < %s)",
+                         ippTagString(attrptr->group_tag),
+                         ippTagString(group));
+
+           if (attrptr->group_tag != IPP_TAG_ZERO)
+             group = attrptr->group_tag;
+         }
 
-         validate_attr(attrptr, 1);
+         validate_attr(outfile, errors, attrptr);
+
+          if (attrptr->name)
+          {
+            if (cupsArrayFind(a, attrptr->name))
+              add_stringf(errors, "Duplicate \"%s\" attribute in %s group",
+                         attrptr->name, ippTagString(group));
+
+            cupsArrayAdd(a, attrptr->name);
+          }
        }
 
-       for (i = 0; i < num_statuses; i ++)
+        cupsArrayDelete(a);
+
+       /*
+        * Now check the test-defined expected status-code and attribute
+        * values...
+        */
+
+       for (i = 0, status_ok = 0; i < num_statuses; i ++)
        {
          if (statuses[i].if_defined &&
              !get_variable(vars, statuses[i].if_defined))
@@ -2373,11 +3087,30 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
              get_variable(vars, statuses[i].if_not_defined))
            continue;
 
-         if (response->request.status.status_code == statuses[i].status)
-           break;
-        }
+         if (ippGetStatusCode(response) == statuses[i].status)
+         {
+            status_ok = 1;
+
+            if (statuses[i].repeat_match && repeat_count < statuses[i].repeat_limit)
+              repeat_test = 1;
 
-       if (i == num_statuses && num_statuses > 0)
+            if (statuses[i].define_match)
+              set_variable(outfile, vars, statuses[i].define_match, "1");
+         }
+         else
+         {
+           if (statuses[i].repeat_no_match && repeat_count < statuses[i].repeat_limit)
+              repeat_test = 1;
+
+            if (statuses[i].define_no_match)
+            {
+              set_variable(outfile, vars, statuses[i].define_no_match, "1");
+              status_ok = 1;
+            }
+          }
+       }
+
+       if (!status_ok && num_statuses > 0)
        {
          for (i = 0; i < num_statuses; i ++)
          {
@@ -2389,92 +3122,419 @@ do_tests(_cups_vars_t *vars,           /* I - Variables */
                get_variable(vars, statuses[i].if_not_defined))
              continue;
 
-           print_test_error("EXPECTED: STATUS %s (got %s)",
-                            ippErrorString(statuses[i].status),
-                            ippErrorString(cupsLastError()));
-         }
+            if (!statuses[i].repeat_match || repeat_count >= statuses[i].repeat_limit)
+             add_stringf(errors, "EXPECTED: STATUS %s (got %s)",
+                         ippErrorString(statuses[i].status),
+                         ippErrorString(cupsLastError()));
+         }
+
+         if ((attrptr = ippFindAttribute(response, "status-message",
+                                         IPP_TAG_TEXT)) != NULL)
+           add_stringf(errors, "status-message=\"%s\"",
+                       attrptr->values[0].string.text);
+        }
+
+       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;
+
+          found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO);
+
+          do
+          {
+           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(outfile, vars, expect->define_no_match, "1");
+             else if (!expect->define_match && !expect->define_value)
+             {
+               if (found && expect->not_expect)
+                 add_stringf(errors, "NOT EXPECTED: %s", expect->name);
+               else if (!found && !(expect->not_expect || expect->optional))
+                 add_stringf(errors, "EXPECTED: %s", expect->name);
+               else if (found)
+               {
+                 if (!expect_matches(expect, found->value_tag))
+                   add_stringf(errors, "EXPECTED: %s OF-TYPE %s (got %s)",
+                               expect->name, expect->of_type,
+                               ippTagString(found->value_tag));
+
+                 if (expect->in_group && found->group_tag != expect->in_group)
+                   add_stringf(errors, "EXPECTED: %s IN-GROUP %s (got %s).",
+                               expect->name, ippTagString(expect->in_group),
+                               ippTagString(found->group_tag));
+               }
+             }
+
+             if (expect->repeat_no_match && repeat_count < expect->repeat_limit)
+                repeat_test = 1;
+
+             break;
+           }
+
+           if (found)
+             ippAttributeString(found, buffer, sizeof(buffer));
+
+            if (found && expect->with_value_from && !with_value_from(NULL, ippFindAttribute(response, expect->with_value_from, IPP_TAG_ZERO), found, buffer, sizeof(buffer)))
+           {
+             if (expect->define_no_match)
+               set_variable(outfile, vars, expect->define_no_match, "1");
+             else if (!expect->define_match && !expect->define_value && ((!expect->repeat_match && !expect->repeat_no_match) || repeat_count >= expect->repeat_limit))
+             {
+               add_stringf(errors, "EXPECTED: %s WITH-VALUES-FROM %s", expect->name, expect->with_value_from);
+
+               with_value_from(errors, ippFindAttribute(response, expect->with_value_from, IPP_TAG_ZERO), found, buffer, sizeof(buffer));
+             }
+
+             if (expect->repeat_no_match && repeat_count < expect->repeat_limit)
+               repeat_test = 1;
+
+             break;
+           }
+           else if (found && !with_value(outfile, NULL, expect->with_value, expect->with_flags, found, buffer, sizeof(buffer)))
+           {
+             if (expect->define_no_match)
+               set_variable(outfile, vars, expect->define_no_match, "1");
+             else if (!expect->define_match && !expect->define_value &&
+                      !expect->repeat_match && (!expect->repeat_no_match || repeat_count >= expect->repeat_limit))
+             {
+               if (expect->with_flags & _CUPS_WITH_REGEX)
+                 add_stringf(errors, "EXPECTED: %s %s /%s/", expect->name, with_flags_string(expect->with_flags), expect->with_value);
+               else
+                 add_stringf(errors, "EXPECTED: %s %s \"%s\"", expect->name, with_flags_string(expect->with_flags), expect->with_value);
+
+               with_value(outfile, errors, expect->with_value, expect->with_flags, found, buffer, sizeof(buffer));
+             }
+
+             if (expect->repeat_no_match &&
+                 repeat_count < expect->repeat_limit)
+               repeat_test = 1;
+
+             break;
+           }
+
+           if (found && expect->count > 0 &&
+               found->num_values != expect->count)
+           {
+             if (expect->define_no_match)
+               set_variable(outfile, vars, expect->define_no_match, "1");
+             else if (!expect->define_match && !expect->define_value)
+             {
+               add_stringf(errors, "EXPECTED: %s COUNT %d (got %d)", expect->name,
+                           expect->count, found->num_values);
+             }
+
+             if (expect->repeat_no_match &&
+                 repeat_count < expect->repeat_limit)
+               repeat_test = 1;
+
+             break;
+           }
+
+           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 (expect->define_no_match)
+                 set_variable(outfile, vars, expect->define_no_match, "1");
+               else if (!expect->define_match && !expect->define_value)
+               {
+                 if (!attrptr)
+                   add_stringf(errors,
+                               "EXPECTED: %s (%d values) SAME-COUNT-AS %s "
+                               "(not returned)", expect->name,
+                               found->num_values, expect->same_count_as);
+                 else if (attrptr->num_values != found->num_values)
+                   add_stringf(errors,
+                               "EXPECTED: %s (%d values) SAME-COUNT-AS %s "
+                               "(%d values)", expect->name, found->num_values,
+                               expect->same_count_as, attrptr->num_values);
+               }
+
+               if (expect->repeat_no_match &&
+                   repeat_count < expect->repeat_limit)
+                 repeat_test = 1;
+
+               break;
+             }
+           }
+
+           if (found && expect->define_match)
+             set_variable(outfile, vars, expect->define_match, "1");
+
+           if (found && expect->define_value)
+           {
+             if (!expect->with_value)
+             {
+               int last = ippGetCount(found) - 1;
+                                         /* Last element in attribute */
+
+               switch (ippGetValueTag(found))
+               {
+                 case IPP_TAG_ENUM :
+                 case IPP_TAG_INTEGER :
+                     snprintf(buffer, sizeof(buffer), "%d", ippGetInteger(found, last));
+                     break;
+
+                 case IPP_TAG_BOOLEAN :
+                     if (ippGetBoolean(found, last))
+                       strlcpy(buffer, "true", sizeof(buffer));
+                     else
+                       strlcpy(buffer, "false", sizeof(buffer));
+                     break;
+
+                 case IPP_TAG_RESOLUTION :
+                     {
+                       int     xres,   /* Horizontal resolution */
+                                 yres; /* Vertical resolution */
+                       ipp_res_t       units;  /* Resolution units */
+
+                       xres = ippGetResolution(found, last, &yres, &units);
+
+                       if (xres == yres)
+                         snprintf(buffer, sizeof(buffer), "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+                       else
+                         snprintf(buffer, sizeof(buffer), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+                     }
+                     break;
+
+                 case IPP_TAG_CHARSET :
+                 case IPP_TAG_KEYWORD :
+                 case IPP_TAG_LANGUAGE :
+                 case IPP_TAG_MIMETYPE :
+                 case IPP_TAG_NAME :
+                 case IPP_TAG_NAMELANG :
+                 case IPP_TAG_TEXT :
+                 case IPP_TAG_TEXTLANG :
+                 case IPP_TAG_URI :
+                 case IPP_TAG_URISCHEME :
+                     strlcpy(buffer, ippGetString(found, last, NULL), sizeof(buffer));
+                     break;
+
+                 default :
+                     ippAttributeString(found, buffer, sizeof(buffer));
+                     break;
+               }
+             }
+
+             set_variable(outfile, vars, expect->define_value, buffer);
+           }
+
+           if (found && expect->repeat_match &&
+               repeat_count < expect->repeat_limit)
+             repeat_test = 1;
+          }
+          while (expect->expect_all && (found = ippFindNextAttribute(response, expect->name, IPP_TAG_ZERO)) != NULL);
+       }
+      }
+
+     /*
+      * If we are going to repeat this test, display intermediate results...
+      */
+
+      if (repeat_test)
+      {
+       if (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != cupsFileStdout()))
+        {
+          cupsFilePrintf(cupsFileStdout(), "%04d]\n", repeat_count);
+\
+         if (num_displayed > 0)
+         {
+           for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(response))
+           {
+             const char *attrname = ippGetName(attrptr);
+             if (attrname)
+             {
+               for (i = 0; i < num_displayed; i ++)
+               {
+                 if (!strcmp(displayed[i], attrname))
+                 {
+                   print_attr(cupsFileStdout(), _CUPS_OUTPUT_TEST, attrptr, NULL);
+                   break;
+                 }
+               }
+             }
+           }
+         }
+        }
+
+       if (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != cupsFileStdout()))
+       {
+         cupsFilePrintf(cupsFileStdout(), "    %-68.68s [", name);
+       }
+
+        ippDelete(response);
+        response = NULL;
+      }
+    }
+    while (repeat_test);
+
+    ippDelete(request);
+
+    request = NULL;
+
+    if (cupsArrayCount(errors) > 0)
+      prev_pass = pass = 0;
+
+    if (prev_pass)
+      PassCount ++;
+    else
+      FailCount ++;
+
+    if (Output == _CUPS_OUTPUT_PLIST)
+    {
+      cupsFilePuts(outfile, "<key>Successful</key>\n");
+      cupsFilePuts(outfile, prev_pass ? "<true />\n" : "<false />\n");
+      cupsFilePuts(outfile, "<key>StatusCode</key>\n");
+      print_xml_string(outfile, "string", ippErrorString(cupsLastError()));
+      cupsFilePuts(outfile, "<key>ResponseAttributes</key>\n");
+      cupsFilePuts(outfile, "<array>\n");
+      cupsFilePuts(outfile, "<dict>\n");
+      for (attrptr = response ? response->attrs : NULL,
+               group = attrptr ? attrptr->group_tag : IPP_TAG_ZERO;
+          attrptr;
+          attrptr = attrptr->next)
+       print_attr(outfile, Output, attrptr, &group);
+      cupsFilePuts(outfile, "</dict>\n");
+      cupsFilePuts(outfile, "</array>\n");
+    }
+    else if (Output == _CUPS_OUTPUT_IPPSERVER && response)
+    {
+      for (attrptr = ippFirstAttribute(response), group = IPP_TAG_ZERO; attrptr; attrptr = ippNextAttribute(response))
+      {
+        if (!ippGetName(attrptr) || ippGetGroupTag(attrptr) != IPP_TAG_PRINTER)
+          continue;
+
+        print_ippserver_attr(outfile, attrptr, 0);
+      }
+    }
+
+    if (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != cupsFileStdout()))
+    {
+      cupsFilePuts(cupsFileStdout(), prev_pass ? "PASS]\n" : "FAIL]\n");
+
+      if (!prev_pass || (Verbosity && response))
+      {
+       cupsFilePrintf(cupsFileStdout(), "        RECEIVED: %lu bytes in response\n", (unsigned long)ippLength(response));
+       cupsFilePrintf(cupsFileStdout(), "        status-code = %s (%s)\n", ippErrorString(cupsLastError()), cupsLastErrorString());
+
+        if (Verbosity && response)
+        {
+         for (attrptr = response->attrs;
+              attrptr != NULL;
+              attrptr = attrptr->next)
+           print_attr(cupsFileStdout(), _CUPS_OUTPUT_TEST, attrptr, NULL);
+       }
+      }
+    }
+    else if (!prev_pass && Output != _CUPS_OUTPUT_QUIET)
+      fprintf(stderr, "%s\n", cupsLastErrorString());
+
+    if (prev_pass && Output >= _CUPS_OUTPUT_LIST && !Verbosity &&
+        num_displayed > 0)
+    {
+      size_t   width;                  /* Length of value */
 
-         if ((attrptr = ippFindAttribute(response, "status-message",
-                                         IPP_TAG_TEXT)) != NULL)
-           print_test_error("status-message=\"%s\"",
-                            attrptr->values[0].string.text);
-        }
+      for (i = 0; i < num_displayed; i ++)
+      {
+       widths[i] = strlen(displayed[i]);
 
-       for (i = num_expects, expect = expects; i > 0; i --, expect ++)
+       for (attrptr = ippFindAttribute(response, displayed[i], IPP_TAG_ZERO);
+            attrptr;
+            attrptr = ippFindNextAttribute(response, displayed[i],
+                                           IPP_TAG_ZERO))
        {
-         if (expect->define_match || expect->define_no_match)
-           continue;
+         width = ippAttributeString(attrptr, NULL, 0);
+         if (width > widths[i])
+           widths[i] = width;
+       }
+      }
 
-         if (expect->if_defined && !get_variable(vars, expect->if_defined))
-           continue;
+      if (Output == _CUPS_OUTPUT_CSV)
+       print_csv(outfile, NULL, num_displayed, displayed, widths);
+      else
+       print_line(outfile, NULL, num_displayed, displayed, widths);
 
-         if (expect->if_not_defined &&
-             get_variable(vars, expect->if_not_defined))
-           continue;
+      attrptr = response->attrs;
 
-         found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO);
+      while (attrptr)
+      {
+       while (attrptr && attrptr->group_tag <= IPP_TAG_OPERATION)
+         attrptr = attrptr->next;
 
-         if (found && expect->not_expect)
-            print_test_error("NOT EXPECTED: %s", expect->name);
-         else if (!found && !(expect->not_expect || expect->optional))
-           print_test_error("EXPECTED: %s", expect->name);
-         else if (found)
-         {
-           if (!expect_matches(expect, found->value_tag))
-             print_test_error("EXPECTED: %s OF-TYPE %s (got %s)",
-                              expect->name, expect->of_type,
-                              ippTagString(found->value_tag));
+       if (attrptr)
+       {
+         if (Output == _CUPS_OUTPUT_CSV)
+           print_csv(outfile, attrptr, num_displayed, displayed, widths);
+         else
+           print_line(outfile, attrptr, num_displayed, displayed, widths);
 
-           if (expect->in_group && found->group_tag != expect->in_group)
-             print_test_error("EXPECTED: %s IN-GROUP %s (got %s).",
-                              expect->name, ippTagString(expect->in_group),
-                              ippTagString(found->group_tag));
+         while (attrptr && attrptr->group_tag > IPP_TAG_OPERATION)
+           attrptr = attrptr->next;
+       }
+      }
+    }
+    else if (!prev_pass)
+    {
+      if (Output == _CUPS_OUTPUT_PLIST)
+      {
+       cupsFilePuts(outfile, "<key>Errors</key>\n");
+       cupsFilePuts(outfile, "<array>\n");
 
-           if (!with_value(expect->with_value, expect->with_regex, found, 0))
-           {
-             if (expect->with_regex)
-               print_test_error("EXPECTED: %s WITH-VALUE /%s/",
-                                expect->name, expect->with_value);
-             else
-               print_test_error("EXPECTED: %s WITH-VALUE \"%s\"",
-                                expect->name, expect->with_value);
+       for (error = (char *)cupsArrayFirst(errors);
+            error;
+            error = (char *)cupsArrayNext(errors))
+         print_xml_string(outfile, "string", error);
 
-             with_value(expect->with_value, expect->with_regex, found, 1);
-           }
+       cupsFilePuts(outfile, "</array>\n");
+      }
 
-           if (expect->count > 0 && found->num_values != expect->count)
-           {
-             print_test_error("EXPECTED: %s COUNT %d (got %d)", expect->name,
-                              expect->count, found->num_values);
-           }
+      if (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != cupsFileStdout()))
+      {
+       for (error = (char *)cupsArrayFirst(errors);
+            error;
+            error = (char *)cupsArrayNext(errors))
+         cupsFilePrintf(cupsFileStdout(), "        %s\n", error);
+      }
+    }
 
-           if (expect->same_count_as)
+    if (num_displayed > 0 && !Verbosity && response && (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != cupsFileStdout())))
+    {
+      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))
            {
-             attrptr = ippFindAttribute(response, expect->same_count_as,
-                                        IPP_TAG_ZERO);
-
-             if (!attrptr)
-               print_test_error("EXPECTED: %s (%d values) SAME-COUNT-AS %s "
-                                "(not returned)", expect->name,
-                                found->num_values, expect->same_count_as);
-             else if (attrptr->num_values != found->num_values)
-               print_test_error("EXPECTED: %s (%d values) SAME-COUNT-AS %s "
-                                "(%d values)", expect->name, found->num_values,
-                                expect->same_count_as, attrptr->num_values);
+             print_attr(outfile, Output, attrptr, NULL);
+             break;
            }
          }
        }
       }
-
-      if (Output == _CUPS_OUTPUT_PLIST)
-       puts("</array>");
     }
 
-    if (Output == _CUPS_OUTPUT_PLIST)
-      puts("</dict>");
-
     skip_error:
 
+    if (Output == _CUPS_OUTPUT_PLIST)
+      cupsFilePuts(outfile, "</dict>\n");
+
     ippDelete(response);
     response = NULL;
 
@@ -2484,6 +3544,10 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
         free(statuses[i].if_defined);
       if (statuses[i].if_not_defined)
         free(statuses[i].if_not_defined);
+      if (statuses[i].define_match)
+        free(statuses[i].define_match);
+      if (statuses[i].define_no_match)
+        free(statuses[i].define_no_match);
     }
     num_statuses = 0;
 
@@ -2519,8 +3583,10 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
 
   test_exit:
 
+  cupsArrayDelete(errors);
+
   if (fp)
-    fclose(fp);
+    cupsFileClose(fp);
 
   httpClose(http);
   ippDelete(request);
@@ -2532,6 +3598,10 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
       free(statuses[i].if_defined);
     if (statuses[i].if_not_defined)
       free(statuses[i].if_not_defined);
+    if (statuses[i].define_match)
+      free(statuses[i].define_match);
+    if (statuses[i].define_no_match)
+      free(statuses[i].define_no_match);
   }
 
   for (i = num_expects, expect = expects; i > 0; i --, expect ++)
@@ -2609,16 +3679,28 @@ expand_variables(_cups_vars_t *vars,    /* I - Variables */
        value = getenv(temp);
         src   += tempptr - temp + 5;
       }
-      else if (vars)
+      else
       {
-       strlcpy(temp, src + 1, sizeof(temp));
+        if (src[1] == '{')
+       {
+         src += 2;
+         strlcpy(temp, src, sizeof(temp));
+         if ((tempptr = strchr(temp, '}')) != NULL)
+           *tempptr = '\0';
+         else
+           tempptr = temp + strlen(temp);
+       }
+       else
+       {
+         strlcpy(temp, src + 1, sizeof(temp));
 
-       for (tempptr = temp; *tempptr; tempptr ++)
-         if (!isalnum(*tempptr & 255) && *tempptr != '-' && *tempptr != '_')
-           break;
+         for (tempptr = temp; *tempptr; tempptr ++)
+           if (!isalnum(*tempptr & 255) && *tempptr != '-' && *tempptr != '_')
+             break;
 
-        if (*tempptr)
-         *tempptr = '\0';
+         if (*tempptr)
+           *tempptr = '\0';
+        }
 
        if (!strcmp(temp, "uri"))
          value = vars->uri;
@@ -2644,15 +3726,10 @@ expand_variables(_cups_vars_t *vars,    /* I - Variables */
 
         src += tempptr - temp + 1;
       }
-      else
-      {
-        value = "$";
-       src ++;
-      }
 
       if (value)
       {
-        strlcpy(dstptr, value, dstend - dstptr + 1);
+        strlcpy(dstptr, value, (size_t)(dstend - dstptr + 1));
        dstptr += strlen(dstptr);
       }
     }
@@ -2732,8 +3809,9 @@ expect_matches(
  */
 
 static ipp_t *                         /* O  - Collection value */
-get_collection(_cups_vars_t *vars,     /* I  - Variables */
-               FILE         *fp,       /* I  - File to read from */
+get_collection(cups_file_t  *outfile,  /* I  - Output file */
+               _cups_vars_t *vars,     /* I  - Variables */
+               cups_file_t  *fp,       /* I  - File to read from */
               int          *linenum)   /* IO - Line number */
 {
   char         token[1024],            /* Token from file */
@@ -2754,47 +3832,15 @@ get_collection(_cups_vars_t *vars,      /* I  - Variables */
       * Another collection value
       */
 
-      ipp_t    *subcol = get_collection(vars, fp, linenum);
+      ipp_t    *subcol = get_collection(outfile, vars, fp, linenum);
                                        /* Collection value */
 
       if (subcol)
-      {
-       ipp_attribute_t *tempcol;       /* Pointer to new buffer */
-
-
-       /*
-       * Reallocate memory...
-       */
-
-       if ((tempcol = realloc(lastcol, sizeof(ipp_attribute_t) +
-                                       (lastcol->num_values + 1) *
-                                       sizeof(ipp_value_t))) == NULL)
-       {
-         print_fatal_error("Unable to allocate memory on line %d.", *linenum);
-         goto col_error;
-       }
-
-       if (tempcol != lastcol)
-       {
-        /*
-         * Reset pointers in the list...
-         */
-
-         if (col->prev)
-           col->prev->next = tempcol;
-         else
-           col->attrs = tempcol;
-
-         lastcol = col->current = col->last = tempcol;
-       }
-
-       lastcol->values[lastcol->num_values].collection = subcol;
-       lastcol->num_values ++;
-      }
+        ippSetCollection(col, &lastcol, ippGetCount(lastcol), subcol);
       else
        goto col_error;
     }
-    else if (!strcasecmp(token, "MEMBER"))
+    else if (!_cups_strcasecmp(token, "MEMBER"))
     {
      /*
       * Attribute...
@@ -2804,38 +3850,64 @@ get_collection(_cups_vars_t *vars,      /* I  - Variables */
 
       if (!get_token(fp, token, sizeof(token), linenum))
       {
-       print_fatal_error("Missing MEMBER value tag on line %d.", *linenum);
+       print_fatal_error(outfile, "Missing MEMBER value tag on line %d.", *linenum);
        goto col_error;
       }
 
-      if ((value = ippTagValue(token)) == IPP_TAG_ZERO)
+      if ((value = ippTagValue(token)) < IPP_TAG_UNSUPPORTED_VALUE)
       {
-       print_fatal_error("Bad MEMBER value tag \"%s\" on line %d.", token,
+       print_fatal_error(outfile, "Bad MEMBER value tag \"%s\" on line %d.", token,
                          *linenum);
        goto col_error;
       }
 
       if (!get_token(fp, attr, sizeof(attr), linenum))
       {
-       print_fatal_error("Missing MEMBER name on line %d.", *linenum);
+       print_fatal_error(outfile, "Missing MEMBER name on line %d.", *linenum);
        goto col_error;
       }
 
-      if (!get_token(fp, temp, sizeof(temp), linenum))
+      if (value < IPP_TAG_INTEGER)
+      {
+       /*
+        * Out-of-band member attributes have no value...
+        */
+
+        token[0] = '\0';
+      }
+      else if (!get_token(fp, temp, sizeof(temp), linenum))
       {
-       print_fatal_error("Missing MEMBER value on line %d.", *linenum);
+       print_fatal_error(outfile, "Missing MEMBER value on line %d.", *linenum);
        goto col_error;
       }
-
-      expand_variables(vars, token, temp, sizeof(token));
+      else
+      {
+        expand_variables(vars, token, temp, sizeof(token));
+      }
 
       switch (value)
       {
-       case IPP_TAG_BOOLEAN :
-           if (!strcasecmp(token, "true"))
+        default :
+              if (value < IPP_TAG_INTEGER)
+              {
+               /*
+                * Add out-of-band value...
+                */
+
+                ippAddOutOfBand(col, IPP_TAG_ZERO, value, attr);
+              }
+              else
+              {
+                print_fatal_error(outfile, "Unsupported MEMBER value tag %s for \"%s\" on line %d.", ippTagString(value), attr, *linenum);
+                goto col_error;
+              }
+              break;
+
+        case IPP_TAG_BOOLEAN :
+           if (!_cups_strcasecmp(token, "true"))
              ippAddBoolean(col, IPP_TAG_ZERO, attr, 1);
            else
-             ippAddBoolean(col, IPP_TAG_ZERO, attr, atoi(token));
+             ippAddBoolean(col, IPP_TAG_ZERO, attr, (char)atoi(token));
            break;
 
        case IPP_TAG_INTEGER :
@@ -2850,23 +3922,23 @@ 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, "dpcm") &&
+                  _cups_strcasecmp(units, "other")))
              {
-               print_fatal_error("Bad resolution value \"%s\" on line %d.",
+               print_fatal_error(outfile, "Bad resolution value \"%s\" on line %d.",
                                  token, *linenum);
                goto col_error;
              }
 
-             if (!strcasecmp(units, "dpi"))
-               ippAddResolution(col, IPP_TAG_ZERO, attr, xres, yres,
-                                IPP_RES_PER_INCH);
-             else if (!strcasecmp(units, "dpc"))
-               ippAddResolution(col, IPP_TAG_ZERO, attr, xres, yres,
-                                IPP_RES_PER_CM);
+             if (!_cups_strcasecmp(units, "dpi"))
+               ippAddResolution(col, IPP_TAG_ZERO, attr, IPP_RES_PER_INCH, xres, yres);
+             else if (!_cups_strcasecmp(units, "dpc") ||
+                      !_cups_strcasecmp(units, "dpcm"))
+               ippAddResolution(col, IPP_TAG_ZERO, attr, IPP_RES_PER_CM, xres, yres);
              else
-               ippAddResolution(col, IPP_TAG_ZERO, attr, xres, yres,
-                                (ipp_res_t)0);
+               ippAddResolution(col, IPP_TAG_ZERO, attr, (ipp_res_t)0, xres, yres);
            }
            break;
 
@@ -2885,7 +3957,7 @@ get_collection(_cups_vars_t *vars,        /* I  - Variables */
 
              if ((num_vals & 1) || num_vals == 0)
              {
-               print_fatal_error("Bad rangeOfInteger value \"%s\" on line %d.",
+               print_fatal_error(outfile, "Bad rangeOfInteger value \"%s\" on line %d.",
                                  token, *linenum);
                goto col_error;
              }
@@ -2898,7 +3970,7 @@ get_collection(_cups_vars_t *vars,        /* I  - Variables */
        case IPP_TAG_BEGIN_COLLECTION :
            if (!strcmp(token, "{"))
            {
-             ipp_t     *subcol = get_collection(vars, fp, linenum);
+             ipp_t     *subcol = get_collection(outfile, vars, fp, linenum);
                                      /* Collection value */
 
              if (subcol)
@@ -2911,12 +3983,25 @@ get_collection(_cups_vars_t *vars,      /* I  - Variables */
            }
            else
            {
-             print_fatal_error("Bad collection value on line %d.", *linenum);
+             print_fatal_error(outfile, "Bad collection value on line %d.", *linenum);
              goto col_error;
            }
            break;
 
-       default :
+        case IPP_TAG_STRING :
+           ippAddOctetString(col, IPP_TAG_ZERO, attr, token, (int)strlen(token));
+           break;
+
+        case IPP_TAG_TEXTLANG :
+        case IPP_TAG_NAMELANG :
+        case IPP_TAG_TEXT :
+        case IPP_TAG_NAME :
+        case IPP_TAG_KEYWORD :
+        case IPP_TAG_URI :
+        case IPP_TAG_URISCHEME :
+        case IPP_TAG_CHARSET :
+        case IPP_TAG_LANGUAGE :
+        case IPP_TAG_MIMETYPE :
            if (!strchr(token, ','))
              ippAddString(col, IPP_TAG_ZERO, value, attr, NULL, token);
            else
@@ -2935,9 +4020,14 @@ get_collection(_cups_vars_t *vars,       /* I  - Variables */
 
              for (ptr = strchr(token, ','); ptr; ptr = strchr(ptr, ','))
              {
-               *ptr++ = '\0';
-               values[num_values] = ptr;
-               num_values ++;
+                if (ptr > token && ptr[-1] == '\\')
+                  _cups_strcpy(ptr - 1, ptr);
+                else
+                {
+                  *ptr++ = '\0';
+                  values[num_values] = ptr;
+                  num_values ++;
+                }
              }
 
              ippAddStrings(col, IPP_TAG_ZERO, value, attr, num_values,
@@ -2946,6 +4036,11 @@ get_collection(_cups_vars_t *vars,       /* I  - Variables */
            break;
       }
     }
+    else
+    {
+      print_fatal_error(outfile, "Unexpected token %s seen on line %d.", token, *linenum);
+      goto col_error;
+    }
   }
 
   return (col);
@@ -2988,7 +4083,11 @@ get_filename(const char *testfile,       /* I - Current test file */
     if (*dstptr == '>')
       *dstptr = '\0';
   }
-  else if (*src == '/' || !strchr(testfile, '/'))
+  else if (*src == '/' || !strchr(testfile, '/')
+#ifdef WIN32
+           || (isalpha(*src & 255) && src[1] == ':')
+#endif /* WIN32 */
+           )
   {
    /*
     * Use the path as-is...
@@ -3008,22 +4107,100 @@ get_filename(const char *testfile,     /* I - Current test file */
     else
       dstptr = dst; /* Should never happen */
 
-    strlcpy(dstptr, src, dstsize - (dstptr - dst));
+    strlcpy(dstptr, src, dstsize - (size_t)(dstptr - dst));
   }
 
   return (dst);
 }
 
 
+/*
+ * 'get_string()' - Get a pointer to a string value or the portion of interest.
+ */
+
+static const char *                    /* O - Pointer to string */
+get_string(ipp_attribute_t *attr,      /* I - IPP attribute */
+           int             element,    /* I - Element to fetch */
+           int             flags,      /* I - Value ("with") flags */
+           char            *buffer,    /* I - Temporary buffer */
+          size_t          bufsize)     /* I - Size of temporary buffer */
+{
+  const char   *value;                 /* Value */
+  char         *ptr,                   /* Pointer into value */
+               scheme[256],            /* URI scheme */
+               userpass[256],          /* Username/password */
+               hostname[256],          /* Hostname */
+               resource[1024];         /* Resource */
+  int          port;                   /* Port number */
+
+
+  value = ippGetString(attr, element, NULL);
+
+  if (flags & _CUPS_WITH_HOSTNAME)
+  {
+    if (httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme), userpass, sizeof(userpass), buffer, (int)bufsize, &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
+      buffer[0] = '\0';
+
+    ptr = buffer + strlen(buffer) - 1;
+    if (ptr >= buffer && *ptr == '.')
+      *ptr = '\0';                     /* Drop trailing "." */
+
+    return (buffer);
+  }
+  else if (flags & _CUPS_WITH_RESOURCE)
+  {
+    if (httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, buffer, (int)bufsize) < HTTP_URI_STATUS_OK)
+      buffer[0] = '\0';
+
+    return (buffer);
+  }
+  else if (flags & _CUPS_WITH_SCHEME)
+  {
+    if (httpSeparateURI(HTTP_URI_CODING_ALL, value, buffer, (int)bufsize, userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource)) < HTTP_URI_STATUS_OK)
+      buffer[0] = '\0';
+
+    return (buffer);
+  }
+  else if (ippGetValueTag(attr) == IPP_TAG_URI && (!strncmp(value, "ipp://", 6) || !strncmp(value, "http://", 7) || !strncmp(value, "ipps://", 7) || !strncmp(value, "https://", 8)))
+  {
+    http_uri_status_t status = httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource));
+
+    if (status < HTTP_URI_STATUS_OK)
+    {
+     /*
+      * Bad URI...
+      */
+
+      buffer[0] = '\0';
+    }
+    else
+    {
+     /*
+      * Normalize URI with no trailing dot...
+      */
+
+      if ((ptr = hostname + strlen(hostname) - 1) >= hostname && *ptr == '.')
+       *ptr = '\0';
+
+      httpAssembleURI(HTTP_URI_CODING_ALL, buffer, (int)bufsize, scheme, userpass, hostname, port, resource);
+    }
+
+    return (buffer);
+  }
+  else
+    return (value);
+}
+
+
 /*
  * 'get_token()' - Get a token from a file.
  */
 
 static char *                          /* O  - Token from file or NULL on EOF */
-get_token(FILE *fp,                    /* I  - File to read from */
-          char *buf,                   /* I  - Buffer to read into */
-         int  buflen,                  /* I  - Length of buffer */
-         int  *linenum)                /* IO - Current line number */
+get_token(cups_file_t *fp,             /* I  - File to read from */
+          char        *buf,            /* I  - Buffer to read into */
+         int         buflen,           /* I  - Length of buffer */
+         int         *linenum)         /* IO - Current line number */
 {
   int  ch,                             /* Character from file */
        quote;                          /* Quoting character */
@@ -3037,7 +4214,7 @@ get_token(FILE *fp,                       /* I  - File to read from */
     * Skip whitespace...
     */
 
-    while (isspace(ch = getc(fp)))
+    while (isspace(ch = cupsFileGetChar(fp)))
     {
       if (ch == '\n')
         (*linenum) ++;
@@ -3059,7 +4236,7 @@ get_token(FILE *fp,                       /* I  - File to read from */
       bufptr = buf;
       bufend = buf + buflen - 1;
 
-      while ((ch = getc(fp)) != EOF)
+      while ((ch = cupsFileGetChar(fp)) != EOF)
       {
         if (ch == '\\')
        {
@@ -3068,15 +4245,15 @@ get_token(FILE *fp,                     /* I  - File to read from */
          */
 
          if (bufptr < bufend)
-           *bufptr++ = ch;
+           *bufptr++ = (char)ch;
 
-         if ((ch = getc(fp)) != EOF && bufptr < bufend)
-           *bufptr++ = ch;
+         if ((ch = cupsFileGetChar(fp)) != EOF && bufptr < bufend)
+           *bufptr++ = (char)ch;
        }
        else if (ch == quote)
           break;
        else if (bufptr < bufend)
-          *bufptr++ = ch;
+          *bufptr++ = (char)ch;
       }
 
       *bufptr = '\0';
@@ -3089,31 +4266,38 @@ get_token(FILE *fp,                     /* I  - File to read from */
       * Comment...
       */
 
-      while ((ch = getc(fp)) != EOF)
+      while ((ch = cupsFileGetChar(fp)) != EOF)
        if (ch == '\n')
           break;
 
       (*linenum) ++;
     }
+    else if (ch == '{' || ch == '}' || ch == ',')
+    {
+      buf[0] = (char)ch;
+      buf[1] = '\0';
+
+      return (buf);
+    }
     else
     {
      /*
       * Whitespace delimited text...
       */
 
-      ungetc(ch, fp);
+      cupsFileSeek(fp, cupsFileTell(fp) - 1);
 
       bufptr = buf;
       bufend = buf + buflen - 1;
 
-      while ((ch = getc(fp)) != EOF)
+      while ((ch = cupsFileGetChar(fp)) != EOF)
        if (isspace(ch) || ch == '#')
           break;
        else if (bufptr < bufend)
-          *bufptr++ = ch;
+          *bufptr++ = (char)ch;
 
       if (ch == '#')
-        ungetc(ch, fp);
+        cupsFileSeek(fp, cupsFileTell(fp) - 1);
       else if (ch == '\n')
         (*linenum) ++;
 
@@ -3150,20 +4334,19 @@ 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 */
+iso_date(const 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);
 }
@@ -3178,266 +4361,264 @@ password_cb(const char *prompt)               /* I - Prompt (unused) */
 {
   (void)prompt;
 
-  return (Password);
+  if (PasswordTries < 3)
+  {
+    PasswordTries ++;
+
+    cupsSetUser(Username);
+
+    return (Password);
+  }
+  else
+    return (NULL);
 }
 
 
 /*
- * 'print_attr()' - Print an attribute on the screen.
+ * 'pause_message()' - Display the message and pause until the user presses a key.
  */
 
 static void
-print_attr(ipp_attribute_t *attr,      /* I  - Attribute to print */
-           ipp_tag_t       *group)     /* IO - Current group */
+pause_message(const char *message)     /* I - Message */
 {
-  int                  i;              /* Looping var */
-  ipp_attribute_t      *colattr;       /* Collection attribute */
-
-
-  if (Output == _CUPS_OUTPUT_PLIST)
-  {
-    if (!attr->name || (group && *group != attr->group_tag))
-    {
-      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;
-
-    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;
+#ifdef WIN32
+  HANDLE       tty;                    /* Console handle */
+  DWORD                mode;                   /* Console mode */
+  char         key;                    /* Key press */
+  DWORD                bytes;                  /* Bytes read for key press */
 
-    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;
+ /*
+  * Disable input echo and set raw input...
+  */
 
-    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;
+  if ((tty = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE)
+    return;
 
-    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;
+  if (!GetConsoleMode(tty, &mode))
+    return;
 
-    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.charset);
-           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.charset);
-       break;
+  if (!SetConsoleMode(tty, 0))
+    return;
 
-    case IPP_TAG_BEGIN_COLLECTION :
-       for (i = 0; i < attr->num_values; i ++)
-       {
-         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(' ');
+#else
+  int                  tty;            /* /dev/tty - never read from stdin */
+  struct termios       original,       /* Original input mode */
+                       noecho;         /* No echo input mode */
+  char                 key;            /* Current key press */
 
-           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;
+ /*
+  * Disable input echo and set raw input...
+  */
+
+  if ((tty = open("/dev/tty", O_RDONLY)) < 0)
+    return;
+
+  if (tcgetattr(tty, &original))
+  {
+    close(tty);
+    return;
   }
 
-  if (Output == _CUPS_OUTPUT_PLIST)
+  noecho = original;
+  noecho.c_lflag &= (tcflag_t)~(ICANON | ECHO | ECHOE | ISIG);
+
+  if (tcsetattr(tty, TCSAFLUSH, &noecho))
   {
-    if (attr->num_values > 1)
-      puts("</array>");
+    close(tty);
+    return;
   }
-  else
-    putchar('\n');
+#endif /* WIN32 */
+
+ /*
+  * Display the prompt...
+  */
+
+  cupsFilePrintf(cupsFileStdout(), "%s\n---- PRESS ANY KEY ----", message);
+
+#ifdef WIN32
+ /*
+  * Read a key...
+  */
+
+  ReadFile(tty, &key, 1, &bytes, NULL);
+
+ /*
+  * Cleanup...
+  */
+
+  SetConsoleMode(tty, mode);
+
+#else
+ /*
+  * Read a key...
+  */
+
+  read(tty, &key, 1);
+
+ /*
+  * Cleanup...
+  */
+
+  tcsetattr(tty, TCSAFLUSH, &original);
+  close(tty);
+#endif /* WIN32 */
+
+ /*
+  * Erase the "press any key" prompt...
+  */
+
+  cupsFilePuts(cupsFileStdout(), "\r                       \r");
 }
 
 
 /*
- * 'print_col()' - Print a collection attribute on the screen.
+ * 'print_attr()' - Print an attribute on the screen.
  */
 
 static void
-print_col(ipp_t *col)                  /* I - Collection attribute to print */
+print_attr(cups_file_t     *outfile,   /* I  - Output file */
+           int             format,     /* I  - Output format */
+           ipp_attribute_t *attr,      /* I  - Attribute to print */
+           ipp_tag_t       *group)     /* IO - Current group */
 {
   int                  i;              /* Looping var */
-  ipp_attribute_t      *attr;          /* Current attribute in collection */
+  ipp_attribute_t      *colattr;       /* Collection attribute */
 
 
-  fputs("{ ", stdout);
-  for (attr = col->attrs; attr; attr = attr->next)
+  if (format == _CUPS_OUTPUT_PLIST)
   {
-    printf("%s (%s%s) = ", attr->name, attr->num_values > 1 ? "1setOf " : "",
-          ippTagString(attr->value_tag));
+    if (!attr->name || (group && *group != attr->group_tag))
+    {
+      if (attr->group_tag != IPP_TAG_ZERO)
+      {
+       cupsFilePuts(outfile, "</dict>\n");
+       cupsFilePuts(outfile, "<dict>\n");
+      }
+
+      if (group)
+        *group = attr->group_tag;
+    }
+
+    if (!attr->name)
+      return;
+
+    print_xml_string(outfile, "key", attr->name);
+    if (attr->num_values > 1)
+      cupsFilePuts(outfile, "<array>\n");
 
     switch (attr->value_tag)
     {
       case IPP_TAG_INTEGER :
       case IPP_TAG_ENUM :
          for (i = 0; i < attr->num_values; i ++)
-           printf("%d ", attr->values[i].integer);
+           cupsFilePrintf(outfile, "<integer>%d</integer>\n", attr->values[i].integer);
          break;
 
       case IPP_TAG_BOOLEAN :
          for (i = 0; i < attr->num_values; i ++)
-           if (attr->values[i].boolean)
-             printf("true ");
-           else
-             printf("false ");
-         break;
-
-      case IPP_TAG_NOVALUE :
-         printf("novalue");
+           cupsFilePuts(outfile, attr->values[i].boolean ? "<true />\n" : "<false />\n");
          break;
 
       case IPP_TAG_RANGE :
          for (i = 0; i < attr->num_values; i ++)
-           printf("%d-%d ", attr->values[i].range.lower,
-                  attr->values[i].range.upper);
+           cupsFilePrintf(outfile, "<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);
          break;
 
       case IPP_TAG_RESOLUTION :
          for (i = 0; i < attr->num_values; i ++)
-           printf("%dx%d%s ", attr->values[i].resolution.xres,
+           cupsFilePrintf(outfile, "<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");
+                      "dpi" : "dpcm");
+         break;
+
+      case IPP_TAG_DATE :
+         for (i = 0; i < attr->num_values; i ++)
+           cupsFilePrintf(outfile, "<date>%s</date>\n", iso_date(attr->values[i].date));
          break;
 
       case IPP_TAG_STRING :
+          for (i = 0; i < attr->num_values; i ++)
+          {
+           char        buffer[IPP_MAX_LENGTH * 5 / 4 + 1];
+                                       /* Output buffer */
+
+           cupsFilePrintf(outfile, "<data>%s</data>\n",
+                   httpEncode64_2(buffer, sizeof(buffer),
+                                  attr->values[i].unknown.data,
+                                  attr->values[i].unknown.length));
+          }
+          break;
+
       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_URISCHEME :
+      case IPP_TAG_CHARSET :
       case IPP_TAG_LANGUAGE :
+      case IPP_TAG_MIMETYPE :
          for (i = 0; i < attr->num_values; i ++)
-           printf("\"%s\" ", attr->values[i].string.text);
+           print_xml_string(outfile, "string", attr->values[i].string.text);
          break;
 
       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);
+         {
+           cupsFilePuts(outfile, "<dict><key>language</key><string>");
+           print_xml_string(outfile, NULL, attr->values[i].string.language);
+           cupsFilePuts(outfile, "</string><key>string</key><string>");
+           print_xml_string(outfile, NULL, attr->values[i].string.text);
+           cupsFilePuts(outfile, "</string></dict>\n");
+         }
          break;
 
       case IPP_TAG_BEGIN_COLLECTION :
          for (i = 0; i < attr->num_values; i ++)
          {
-           print_col(attr->values[i].collection);
-           putchar(' ');
+           cupsFilePuts(outfile, "<dict>\n");
+           for (colattr = attr->values[i].collection->attrs;
+                colattr;
+                colattr = colattr->next)
+             print_attr(outfile, format, colattr, NULL);
+           cupsFilePuts(outfile, "</dict>\n");
          }
          break;
 
       default :
-         break; /* anti-compiler-warning-code */
+         cupsFilePrintf(outfile, "<string>&lt;&lt;%s&gt;&gt;</string>\n", ippTagString(attr->value_tag));
+         break;
     }
+
+    if (attr->num_values > 1)
+      cupsFilePuts(outfile, "</array>\n");
   }
+  else
+  {
+    char       buffer[131072];         /* Value buffer */
+
+    if (format == _CUPS_OUTPUT_TEST)
+    {
+      if (!attr->name)
+      {
+        cupsFilePuts(outfile, "        -- separator --\n");
+        return;
+      }
+
+      cupsFilePrintf(outfile, "        %s (%s%s) = ", attr->name, attr->num_values > 1 ? "1setOf " : "", ippTagString(attr->value_tag));
+    }
 
-  putchar('}');
+    ippAttributeString(attr, buffer, sizeof(buffer));
+    cupsFilePrintf(outfile, "%s\n", buffer);
+  }
 }
 
 
@@ -3447,6 +4628,7 @@ print_col(ipp_t *col)                     /* I - Collection attribute to print */
 
 static void
 print_csv(
+    cups_file_t     *outfile,          /* I - Output file */
     ipp_attribute_t *attr,             /* I - First attribute for line */
     int             num_displayed,     /* I - Number of attributes to display */
     char            **displayed,       /* I - Attributes to display */
@@ -3481,7 +4663,7 @@ print_csv(
     for (i = 0; i < num_displayed; i ++)
     {
       if (i)
-        putchar(',');
+        cupsFilePutChar(outfile, ',');
 
       buffer[0] = '\0';
 
@@ -3491,7 +4673,7 @@ print_csv(
           break;
         else if (!strcmp(current->name, displayed[i]))
         {
-          _ippAttrString(current, buffer, maxlength);
+          ippAttributeString(current, buffer, maxlength);
           break;
         }
       }
@@ -3499,30 +4681,30 @@ print_csv(
       if (strchr(buffer, ',') != NULL || strchr(buffer, '\"') != NULL ||
          strchr(buffer, '\\') != NULL)
       {
-        putchar('\"');
+        cupsFilePutChar(cupsFileStdout(), '\"');
         for (bufptr = buffer; *bufptr; bufptr ++)
         {
           if (*bufptr == '\\' || *bufptr == '\"')
-            putchar('\\');
-          putchar(*bufptr);
+            cupsFilePutChar(cupsFileStdout(), '\\');
+          cupsFilePutChar(cupsFileStdout(), *bufptr);
         }
-        putchar('\"');
+        cupsFilePutChar(cupsFileStdout(), '\"');
       }
       else
-        fputs(buffer, stdout);
+        cupsFilePuts(outfile, buffer);
     }
-    putchar('\n');
+    cupsFilePutChar(cupsFileStdout(), '\n');
   }
   else
   {
     for (i = 0; i < num_displayed; i ++)
     {
       if (i)
-        putchar(',');
+        cupsFilePutChar(cupsFileStdout(), ',');
 
-      fputs(displayed[i], stdout);
+      cupsFilePuts(outfile, displayed[i]);
     }
-    putchar('\n');
+    cupsFilePutChar(cupsFileStdout(), '\n');
   }
 
   free(buffer);
@@ -3534,7 +4716,8 @@ print_csv(
  */
 
 static void
-print_fatal_error(const char *s,       /* I - Printf-style format string */
+print_fatal_error(cups_file_t *outfile,        /* I - Output file */
+                 const char  *s,       /* I - Printf-style format string */
                   ...)                 /* I - Additional arguments as needed */
 {
   char         buffer[10240];          /* Format buffer */
@@ -3555,11 +4738,146 @@ print_fatal_error(const char *s,       /* I - Printf-style format string */
 
   if (Output == _CUPS_OUTPUT_PLIST)
   {
-    print_xml_header();
-    print_xml_trailer(0, buffer);
+    print_xml_header(outfile);
+    print_xml_trailer(outfile, 0, buffer);
   }
+
+  _cupsLangPrintf(stderr, "ipptool: %s", buffer);
+}
+
+
+/*
+ * 'print_ippserver_attr()' - Print a attribute suitable for use by ippserver.
+ */
+
+static void
+print_ippserver_attr(
+    cups_file_t     *outfile,          /* I - Output file */
+    ipp_attribute_t *attr,             /* I - Attribute to print */
+    int             indent)            /* I - Indentation level */
+{
+  int                  i,              /* Looping var */
+                       count = ippGetCount(attr);
+                                       /* Number of values */
+  ipp_attribute_t      *colattr;       /* Collection attribute */
+
+
+  if (indent == 0)
+    cupsFilePrintf(outfile, "ATTR %s %s", ippTagString(ippGetValueTag(attr)), ippGetName(attr));
   else
-    _cupsLangPrintf(stderr, "ipptool: %s", buffer);
+    cupsFilePrintf(outfile, "%*sMEMBER %s %s", indent, "", ippTagString(ippGetValueTag(attr)), ippGetName(attr));
+
+  switch (ippGetValueTag(attr))
+  {
+    case IPP_TAG_INTEGER :
+    case IPP_TAG_ENUM :
+       for (i = 0; i < count; i ++)
+         cupsFilePrintf(outfile, "%s%d", i ? "," : " ", ippGetInteger(attr, i));
+       break;
+
+    case IPP_TAG_BOOLEAN :
+       cupsFilePuts(outfile, ippGetBoolean(attr, 0) ? " true" : " false");
+
+       for (i = 1; i < count; i ++)
+         cupsFilePuts(outfile, ippGetBoolean(attr, 1) ? ",true" : ",false");
+       break;
+
+    case IPP_TAG_RANGE :
+       for (i = 0; i < count; i ++)
+       {
+         int upper, lower = ippGetRange(attr, i, &upper);
+
+         cupsFilePrintf(outfile, "%s%d-%d", i ? "," : " ", lower, upper);
+       }
+       break;
+
+    case IPP_TAG_RESOLUTION :
+       for (i = 0; i < count; i ++)
+       {
+         ipp_res_t units;
+         int yres, xres = ippGetResolution(attr, i, &yres, &units);
+
+         cupsFilePrintf(outfile, "%s%dx%d%s", i ? "," : " ", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+       }
+       break;
+
+    case IPP_TAG_DATE :
+       for (i = 0; i < count; i ++)
+         cupsFilePrintf(outfile, "%s%s", i ? "," : " ", iso_date(ippGetDate(attr, i)));
+       break;
+
+    case IPP_TAG_STRING :
+       for (i = 0; i < count; i ++)
+       {
+         int len;
+         const char *s = (const char *)ippGetOctetString(attr, i, &len);
+
+         cupsFilePuts(outfile, i ? "," : " ");
+         print_ippserver_string(outfile, s, (size_t)len);
+       }
+       break;
+
+    case IPP_TAG_TEXT :
+    case IPP_TAG_TEXTLANG :
+    case IPP_TAG_NAME :
+    case IPP_TAG_NAMELANG :
+    case IPP_TAG_KEYWORD :
+    case IPP_TAG_URI :
+    case IPP_TAG_URISCHEME :
+    case IPP_TAG_CHARSET :
+    case IPP_TAG_LANGUAGE :
+    case IPP_TAG_MIMETYPE :
+       for (i = 0; i < count; i ++)
+       {
+         const char *s = ippGetString(attr, i, NULL);
+
+         cupsFilePuts(outfile, i ? "," : " ");
+         print_ippserver_string(outfile, s, strlen(s));
+       }
+       break;
+
+    case IPP_TAG_BEGIN_COLLECTION :
+       for (i = 0; i < attr->num_values; i ++)
+       {
+         ipp_t *col = ippGetCollection(attr, i);
+
+         cupsFilePuts(outfile, i ? ",{\n" : " {\n");
+         for (colattr = ippFirstAttribute(col); colattr; colattr = ippNextAttribute(col))
+           print_ippserver_attr(outfile, colattr, indent + 4);
+         cupsFilePrintf(outfile, "%*s}", indent, "");
+       }
+       break;
+
+    default :
+       cupsFilePuts(outfile, " \"\"");
+       break;
+  }
+
+  cupsFilePuts(outfile, "\n");
+}
+
+
+/*
+ * 'print_ippserver_string()' - Print a string suitable for use by ippserver.
+ */
+
+static void
+print_ippserver_string(
+    cups_file_t *outfile,              /* I - Output file */
+    const char  *s,                    /* I - String to print */
+    size_t      len)                   /* I - Length of string */
+{
+  cupsFilePutChar(outfile, '\"');
+  while (len > 0)
+  {
+    if (*s == '\"')
+      cupsFilePutChar(outfile, '\\');
+    cupsFilePutChar(outfile, *s);
+
+    s ++;
+    len --;
+  }
+  cupsFilePutChar(outfile, '\"');
 }
 
 
@@ -3569,6 +4887,7 @@ print_fatal_error(const char *s,  /* I - Printf-style format string */
 
 static void
 print_line(
+    cups_file_t     *outfile,          /* I - Output file */
     ipp_attribute_t *attr,             /* I - First attribute for line */
     int             num_displayed,     /* I - Number of attributes to display */
     char            **displayed,       /* I - Attributes to display */
@@ -3602,7 +4921,7 @@ print_line(
     for (i = 0; i < num_displayed; i ++)
     {
       if (i)
-        putchar(' ');
+        cupsFilePutChar(cupsFileStdout(), ' ');
 
       buffer[0] = '\0';
 
@@ -3612,93 +4931,64 @@ print_line(
           break;
         else if (!strcmp(current->name, displayed[i]))
         {
-          _ippAttrString(current, buffer, maxlength);
+          ippAttributeString(current, buffer, maxlength);
           break;
         }
       }
 
-      printf("%*s", (int)-widths[i], buffer);
+      cupsFilePrintf(outfile, "%*s", (int)-widths[i], buffer);
     }
-    putchar('\n');
+    cupsFilePutChar(cupsFileStdout(), '\n');
   }
   else
   {
     for (i = 0; i < num_displayed; i ++)
     {
       if (i)
-        putchar(' ');
+        cupsFilePutChar(cupsFileStdout(), ' ');
 
-      printf("%*s", (int)-widths[i], displayed[i]);
+      cupsFilePrintf(outfile, "%*s", (int)-widths[i], displayed[i]);
     }
-    putchar('\n');
+    cupsFilePutChar(cupsFileStdout(), '\n');
 
     for (i = 0; i < num_displayed; i ++)
     {
       if (i)
-       putchar(' ');
+       cupsFilePutChar(cupsFileStdout(), ' ');
 
       memset(buffer, '-', widths[i]);
       buffer[widths[i]] = '\0';
-      fputs(buffer, stdout);
+      cupsFilePuts(outfile, buffer);
     }
-    putchar('\n');
+    cupsFilePutChar(cupsFileStdout(), '\n');
   }
 
   free(buffer);
 }
 
 
-/*
- * 'print_test_error()' - Print a test error message.
- */
-
-static void
-print_test_error(const char *s,                /* I - Printf-style format string */
-                 ...)                  /* I - Additional arguments as needed */
-{
-  char         buffer[10240];          /* Format buffer */
-  va_list      ap;                     /* Pointer to arguments */
-
-
- /*
-  * Format the error message...
-  */
-
-  va_start(ap, s);
-  vsnprintf(buffer, sizeof(buffer), s, ap);
-  va_end(ap);
-
- /*
-  * Then output it...
-  */
-
-  if (Output == _CUPS_OUTPUT_PLIST)
-    print_xml_string("string", buffer);
-  else
-    printf("        %s\n", buffer);
-}
-
-
 /*
  * 'print_xml_header()' - Print a standard XML plist header.
  */
 
 static void
-print_xml_header(void)
+print_xml_header(cups_file_t *outfile)         /* I - Output file */
 {
   if (!XMLHeader)
   {
-    puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
-    puts("<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
-         "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
-    puts("<plist version=\"1.0\">");
-    puts("<dict>");
-    puts("<key>Transfer</key>");
-    printf("<string>%s</string>\n",
-           Transfer == _CUPS_TRANSFER_AUTO ? "auto" :
-              Transfer == _CUPS_TRANSFER_CHUNKED ? "chunked" : "length");
-    puts("<key>Tests</key>");
-    puts("<array>");
+    cupsFilePuts(outfile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+    cupsFilePuts(outfile, "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
+         "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n");
+    cupsFilePuts(outfile, "<plist version=\"1.0\">\n");
+    cupsFilePuts(outfile, "<dict>\n");
+    cupsFilePuts(outfile, "<key>ipptoolVersion</key>\n");
+    cupsFilePuts(outfile, "<string>" CUPS_SVERSION "</string>\n");
+    cupsFilePuts(outfile, "<key>Transfer</key>\n");
+    cupsFilePrintf(outfile, "<string>%s</string>\n",
+           Transfer == _CUPS_TRANSFER_AUTO ? "auto" :
+               Transfer == _CUPS_TRANSFER_CHUNKED ? "chunked" : "length");
+    cupsFilePuts(outfile, "<key>Tests</key>\n");
+    cupsFilePuts(outfile, "<array>\n");
 
     XMLHeader = 1;
   }
@@ -3710,28 +5000,92 @@ print_xml_header(void)
  */
 
 static void
-print_xml_string(const char *element,  /* I - Element name or NULL */
-                const char *s)         /* I - String to print */
+print_xml_string(cups_file_t *outfile, /* I - Output file */
+                 const char  *element, /* I - Element name or NULL */
+                const char  *s)        /* I - String to print */
 {
   if (element)
-    printf("<%s>", element);
+    cupsFilePrintf(outfile, "<%s>", element);
 
   while (*s)
   {
     if (*s == '&')
-      fputs("&amp;", stdout);
+      cupsFilePuts(outfile, "&amp;");
     else if (*s == '<')
-      fputs("&lt;", stdout);
+      cupsFilePuts(outfile, "&lt;");
     else if (*s == '>')
-      fputs("&gt;", stdout);
+      cupsFilePuts(outfile, "&gt;");
+    else if ((*s & 0xe0) == 0xc0)
+    {
+     /*
+      * Validate UTF-8 two-byte sequence...
+      */
+
+      if ((s[1] & 0xc0) != 0x80)
+      {
+        cupsFilePutChar(outfile, '?');
+        s ++;
+      }
+      else
+      {
+        cupsFilePutChar(outfile, *s++);
+        cupsFilePutChar(outfile, *s);
+      }
+    }
+    else if ((*s & 0xf0) == 0xe0)
+    {
+     /*
+      * Validate UTF-8 three-byte sequence...
+      */
+
+      if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80)
+      {
+        cupsFilePutChar(outfile, '?');
+        s += 2;
+      }
+      else
+      {
+        cupsFilePutChar(outfile, *s++);
+        cupsFilePutChar(outfile, *s++);
+        cupsFilePutChar(outfile, *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)
+      {
+        cupsFilePutChar(outfile, '?');
+        s += 3;
+      }
+      else
+      {
+        cupsFilePutChar(outfile, *s++);
+        cupsFilePutChar(outfile, *s++);
+        cupsFilePutChar(outfile, *s++);
+        cupsFilePutChar(outfile, *s);
+      }
+    }
+    else if ((*s & 0x80) || (*s < ' ' && !isspace(*s & 255)))
+    {
+     /*
+      * Invalid control character...
+      */
+
+      cupsFilePutChar(outfile, '?');
+    }
     else
-      putchar(*s);
+      cupsFilePutChar(outfile, *s);
 
     s ++;
   }
 
   if (element)
-    printf("</%s>\n", element);
+    cupsFilePrintf(outfile, "</%s>\n", element);
 }
 
 
@@ -3740,21 +5094,22 @@ print_xml_string(const char *element,   /* I - Element name or NULL */
  */
 
 static void
-print_xml_trailer(int        success,  /* I - 1 on success, 0 on failure */
-                  const char *message) /* I - Error message or NULL */
+print_xml_trailer(cups_file_t *outfile,        /* I - Output file */
+                  int         success, /* I - 1 on success, 0 on failure */
+                  const char  *message)        /* I - Error message or NULL */
 {
   if (XMLHeader)
   {
-    puts("</array>");
-    puts("<key>Successful</key>");
-    puts(success ? "<true />" : "<false />");
+    cupsFilePuts(outfile, "</array>\n");
+    cupsFilePuts(outfile, "<key>Successful</key>\n");
+    cupsFilePuts(outfile, success ? "<true />\n" : "<false />\n");
     if (message)
     {
-      puts("<key>ErrorMessage</key>");
-      print_xml_string("string", message);
+      cupsFilePuts(outfile, "<key>ErrorMessage</key>\n");
+      print_xml_string(outfile, "string", message);
     }
-    puts("</dict>");
-    puts("</plist>");
+    cupsFilePuts(outfile, "</dict>\n");
+    cupsFilePuts(outfile, "</plist>\n");
 
     XMLHeader = 0;
   }
@@ -3766,7 +5121,8 @@ print_xml_trailer(int        success,     /* I - 1 on success, 0 on failure */
  */
 
 static void
-set_variable(_cups_vars_t *vars,       /* I - Variables */
+set_variable(cups_file_t  *outfile,    /* I - Output file */
+             _cups_vars_t *vars,       /* I - Variables */
              const char   *name,       /* I - Variable name */
              const char   *value)      /* I - Value string */
 {
@@ -3774,6 +5130,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)
   {
@@ -3782,7 +5147,7 @@ set_variable(_cups_vars_t *vars,  /* I - Variables */
   }
   else if ((var = malloc(sizeof(_cups_var_t))) == NULL)
   {
-    print_fatal_error("Unable to allocate memory for variable \"%s\".", name);
+    print_fatal_error(outfile, "Unable to allocate memory for variable \"%s\".", name);
     exit(1);
   }
   else
@@ -3795,18 +5160,58 @@ 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) */
+timeout_cb(http_t *http,               /* I - Connection to server */
            void   *user_data)          /* I - User data (unused) */
 {
+  int          buffered = 0;           /* Bytes buffered but not yet sent */
+
+
+  (void)user_data;
+
+  /*
+  * If the socket still have data waiting to be sent to the printer (as can
+  * happen if the printer runs out of paper), continue to wait until the output
+  * buffer is empty...
+  */
+
+#ifdef SO_NWRITE                       /* macOS and some versions of Linux */
+  socklen_t len = sizeof(buffered);    /* Size of return value */
+
+  if (getsockopt(httpGetFd(http), SOL_SOCKET, SO_NWRITE, &buffered, &len))
+    buffered = 0;
+
+#elif defined(SIOCOUTQ)                        /* Others except Windows */
+  if (ioctl(httpGetFd(http), SIOCOUTQ, &buffered))
+    buffered = 0;
+
+#else                                  /* Windows (not possible) */
   (void)http;
-  (void)user_data;
+#endif /* SO_NWRITE */
 
-  return (0);
+  return (buffered > 0);
 }
 
 
@@ -3817,34 +5222,35 @@ timeout_cb(http_t *http,                /* I - Connection to server (unused) */
 static void
 usage(void)
 {
-  _cupsLangPuts(stderr, _("Usage: ipptool [options] URI filename [ ... "
-                         "filenameN ]"));
+  _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  Define variable."));
-  _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."));
+  _cupsLangPuts(stderr, _("  --help                  Show help."));
+  _cupsLangPuts(stderr, _("  --ippserver filename    Produce ippserver attribute file."));
+  _cupsLangPuts(stderr, _("  --stop-after-include-error\n"
+                          "                          Stop tests after a failed INCLUDE."));
+  _cupsLangPuts(stderr, _("  --version               Show version."));
+  _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 encryption using HTTP Upgrade to TLS."));
+  _cupsLangPuts(stderr, _("  -I                      Ignore errors."));
+  _cupsLangPuts(stderr, _("  -L                      Send requests using content-length."));
+  _cupsLangPuts(stderr, _("  -P filename.plist       Produce XML plist to a file and test report to standard output."));
+  _cupsLangPuts(stderr, _("  -S                      Test with encryption using HTTPS."));
+  _cupsLangPuts(stderr, _("  -T seconds              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, _("  -c                      Produce CSV output."));
+  _cupsLangPuts(stderr, _("  -d name=value           Set named variable to value."));
+  _cupsLangPuts(stderr, _("  -f filename             Set default request filename."));
+  _cupsLangPuts(stderr, _("  -h                      Validate HTTP response headers."));
+  _cupsLangPuts(stderr, _("  -i seconds              Repeat the last file with the given time interval."));
+  _cupsLangPuts(stderr, _("  -l                      Produce plain text output."));
+  _cupsLangPuts(stderr, _("  -n count                Repeat the last file the given number of times."));
+  _cupsLangPuts(stderr, _("  -q                      Run silently."));
+  _cupsLangPuts(stderr, _("  -t                      Produce a test report."));
+  _cupsLangPuts(stderr, _("  -v                      Be verbose."));
 
   exit(1);
 }
@@ -3855,8 +5261,9 @@ usage(void)
  */
 
 static int                             /* O - 1 if valid, 0 otherwise */
-validate_attr(ipp_attribute_t *attr,   /* I - Attribute to validate */
-              int             print)   /* I - 1 = report issues to stdout */
+validate_attr(cups_file_t     *outfile,        /* I - Output file */
+              cups_array_t    *errors, /* I - Errors array */
+              ipp_attribute_t *attr)   /* I - Attribute to validate */
 {
   int          i;                      /* Looping var */
   char         scheme[64],             /* Scheme from URI */
@@ -3891,18 +5298,18 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
   {
     valid = 0;
 
-    if (print)
-      print_test_error("\"%s\": Bad attribute name - invalid character (RFC "
-                      "2911 section 4.1.3).", attr->name);
+    add_stringf(errors,
+               "\"%s\": Bad attribute name - invalid character "
+               "(RFC 2911 section 4.1.3).", attr->name);
   }
 
   if ((ptr - attr->name) > 255)
   {
     valid = 0;
 
-    if (print)
-      print_test_error("\"%s\": Bad attribute name - bad length (RFC 2911 "
-                      "section 4.1.3).", attr->name);
+    add_stringf(errors,
+               "\"%s\": Bad attribute name - bad length "
+               "(RFC 2911 section 4.1.3).", attr->name);
   }
 
   switch (attr->value_tag)
@@ -3918,11 +5325,10 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad boolen value %d (RFC 2911 section "
-                              "4.1.10).", attr->name, attr->values[i].boolean);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad boolen value %d "
+                       "(RFC 2911 section 4.1.11).", attr->name,
+                       attr->values[i].boolean);
          }
        }
         break;
@@ -3934,12 +5340,10 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad enum value %d - out of range "
-                              "(RFC 2911 section 4.1.4).", attr->name,
-                              attr->values[i].integer);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad enum value %d - out of range "
+                       "(RFC 2911 section 4.1.4).", attr->name,
+                       attr->values[i].integer);
          }
        }
         break;
@@ -3947,16 +5351,14 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
     case IPP_TAG_STRING :
         for (i = 0; i < attr->num_values; i ++)
        {
-         if (attr->values[i].unknown.length > 1023)
+         if (attr->values[i].unknown.length > IPP_MAX_OCTETSTRING)
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad octetString value - bad length %d "
-                              "(RFC 2911 section 4.1.10).", attr->name,
-                              attr->values[i].unknown.length);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad octetString value - bad length %d "
+                       "(RFC 2911 section 4.1.10).", attr->name,
+                       attr->values[i].unknown.length);
          }
        }
         break;
@@ -3970,99 +5372,81 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad dateTime month %u (RFC 2911 "
-                              "section 4.1.13).", attr->name, date[2]);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad dateTime month %u "
+                       "(RFC 2911 section 4.1.14).", attr->name, date[2]);
          }
 
           if (date[3] < 1 || date[3] > 31)
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad dateTime day %u (RFC 2911 "
-                              "section 4.1.13).", attr->name, date[3]);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad dateTime day %u "
+                       "(RFC 2911 section 4.1.14).", attr->name, date[3]);
          }
 
           if (date[4] > 23)
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad dateTime hours %u (RFC 2911 "
-                              "section 4.1.13).", attr->name, date[4]);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad dateTime hours %u "
+                       "(RFC 2911 section 4.1.14).", attr->name, date[4]);
          }
 
           if (date[5] > 59)
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad dateTime minutes %u (RFC 2911 "
-                              "section 4.1.13).", attr->name, date[5]);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad dateTime minutes %u "
+                       "(RFC 2911 section 4.1.14).", attr->name, date[5]);
          }
 
           if (date[6] > 60)
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad dateTime seconds %u (RFC 2911 "
-                              "section 4.1.13).", attr->name, date[6]);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad dateTime seconds %u "
+                       "(RFC 2911 section 4.1.14).", attr->name, date[6]);
          }
 
           if (date[7] > 9)
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad dateTime deciseconds %u (RFC 2911 "
-                              "section 4.1.13).", attr->name, date[7]);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad dateTime deciseconds %u "
+                       "(RFC 2911 section 4.1.14).", attr->name, date[7]);
          }
 
           if (date[8] != '-' && date[8] != '+')
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad dateTime UTC sign '%c' (RFC 2911 "
-                              "section 4.1.13).", attr->name, date[8]);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad dateTime UTC sign '%c' "
+                       "(RFC 2911 section 4.1.14).", attr->name, date[8]);
          }
 
           if (date[9] > 11)
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad dateTime UTC hours %u (RFC 2911 "
-                              "section 4.1.13).", attr->name, date[9]);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad dateTime UTC hours %u "
+                       "(RFC 2911 section 4.1.14).", attr->name, date[9]);
          }
 
           if (date[10] > 59)
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad dateTime UTC minutes %u (RFC 2911 "
-                              "section 4.1.13).", attr->name, date[10]);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad dateTime UTC minutes %u "
+                       "(RFC 2911 section 4.1.14).", attr->name, date[10]);
          }
        }
         break;
@@ -4074,36 +5458,32 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad resolution value %dx%d%s - cross "
-                              "feed resolution must be positive (RFC 2911 "
-                              "section 4.1.13).", attr->name,
-                              attr->values[i].resolution.xres,
-                              attr->values[i].resolution.yres,
-                              attr->values[i].resolution.units ==
-                                  IPP_RES_PER_INCH ? "dpi" :
-                                  attr->values[i].resolution.units ==
-                                      IPP_RES_PER_CM ? "dpc" : "unknown");
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad resolution value %dx%d%s - cross "
+                       "feed resolution must be positive "
+                       "(RFC 2911 section 4.1.15).", attr->name,
+                       attr->values[i].resolution.xres,
+                       attr->values[i].resolution.yres,
+                       attr->values[i].resolution.units ==
+                           IPP_RES_PER_INCH ? "dpi" :
+                           attr->values[i].resolution.units ==
+                               IPP_RES_PER_CM ? "dpcm" : "unknown");
          }
 
          if (attr->values[i].resolution.yres <= 0)
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad resolution value %dx%d%s - feed "
-                              "resolution must be positive (RFC 2911 section "
-                              "4.1.13).", attr->name,
-                              attr->values[i].resolution.xres,
-                              attr->values[i].resolution.yres,
-                              attr->values[i].resolution.units ==
-                                  IPP_RES_PER_INCH ? "dpi" :
-                                  attr->values[i].resolution.units ==
-                                      IPP_RES_PER_CM ? "dpc" : "unknown");
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad resolution value %dx%d%s - feed "
+                       "resolution must be positive "
+                       "(RFC 2911 section 4.1.15).", attr->name,
+                       attr->values[i].resolution.xres,
+                       attr->values[i].resolution.yres,
+                       attr->values[i].resolution.units ==
+                           IPP_RES_PER_INCH ? "dpi" :
+                           attr->values[i].resolution.units ==
+                               IPP_RES_PER_CM ? "dpcm" : "unknown");
          }
 
          if (attr->values[i].resolution.units != IPP_RES_PER_INCH &&
@@ -4111,17 +5491,15 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad resolution value %dx%d%s - bad "
-                              "units value (RFC 2911 section 4.1.13).",
-                              attr->name, attr->values[i].resolution.xres,
-                              attr->values[i].resolution.yres,
-                              attr->values[i].resolution.units ==
-                                  IPP_RES_PER_INCH ? "dpi" :
-                                  attr->values[i].resolution.units ==
-                                      IPP_RES_PER_CM ? "dpc" : "unknown");
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad resolution value %dx%d%s - bad "
+                       "units value (RFC 2911 section 4.1.15).",
+                       attr->name, attr->values[i].resolution.xres,
+                       attr->values[i].resolution.yres,
+                       attr->values[i].resolution.units ==
+                           IPP_RES_PER_INCH ? "dpi" :
+                           attr->values[i].resolution.units ==
+                               IPP_RES_PER_CM ? "dpcm" : "unknown");
          }
        }
         break;
@@ -4133,13 +5511,11 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad rangeOfInteger value %d-%d - lower "
-                              "greater than upper (RFC 2911 section 4.1.13).",
-                              attr->name, attr->values[i].range.lower,
-                              attr->values[i].range.upper);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad rangeOfInteger value %d-%d - lower "
+                       "greater than upper (RFC 2911 section 4.1.13).",
+                       attr->name, attr->values[i].range.lower,
+                       attr->values[i].range.upper);
          }
        }
         break;
@@ -4151,20 +5527,20 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
               colattr;
               colattr = colattr->next)
          {
-           if (!validate_attr(colattr, 0))
+           if (!validate_attr(outfile, NULL, colattr))
            {
              valid = 0;
              break;
            }
          }
 
-         if (colattr && print)
+         if (colattr && errors)
          {
-            print_test_error("\"%s\": Bad collection value.", attr->name);
+            add_stringf(errors, "\"%s\": Bad collection value.", attr->name);
 
            while (colattr)
            {
-             validate_attr(colattr, print);
+             validate_attr(outfile, errors, colattr);
              colattr = colattr->next;
            }
          }
@@ -4212,25 +5588,21 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad text value \"%s\" - bad UTF-8 "
-                              "sequence (RFC 2911 section 4.1.1).", attr->name,
-                              attr->values[i].string.text);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad text value \"%s\" - bad UTF-8 "
+                       "sequence (RFC 2911 section 4.1.1).", attr->name,
+                       attr->values[i].string.text);
          }
 
-         if ((ptr - attr->values[i].string.text) > 1023)
+         if ((ptr - attr->values[i].string.text) > (IPP_MAX_TEXT - 1))
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad text value \"%s\" - bad length %d "
-                              "(RFC 2911 section 4.1.1).", attr->name,
-                              attr->values[i].string.text,
-                              (int)strlen(attr->values[i].string.text));
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad text value \"%s\" - bad length %d "
+                       "(RFC 2911 section 4.1.1).", attr->name,
+                       attr->values[i].string.text,
+                       (int)strlen(attr->values[i].string.text));
          }
        }
         break;
@@ -4276,25 +5648,21 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad name value \"%s\" - bad UTF-8 "
-                              "sequence (RFC 2911 section 4.1.2).", attr->name,
-                              attr->values[i].string.text);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad name value \"%s\" - bad UTF-8 "
+                       "sequence (RFC 2911 section 4.1.2).", attr->name,
+                       attr->values[i].string.text);
          }
 
-         if ((ptr - attr->values[i].string.text) > 1023)
+         if ((ptr - attr->values[i].string.text) > (IPP_MAX_NAME - 1))
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad name value \"%s\" - bad length %d "
-                              "(RFC 2911 section 4.1.2).", attr->name,
-                              attr->values[i].string.text,
-                              (int)strlen(attr->values[i].string.text));
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad name value \"%s\" - bad length %d "
+                       "(RFC 2911 section 4.1.2).", attr->name,
+                       attr->values[i].string.text,
+                       (int)strlen(attr->values[i].string.text));
          }
        }
         break;
@@ -4311,25 +5679,21 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad keyword value \"%s\" - invalid "
-                              "character (RFC 2911 section 4.1.3).",
-                              attr->name, attr->values[i].string.text);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad keyword value \"%s\" - invalid "
+                       "character (RFC 2911 section 4.1.3).",
+                       attr->name, attr->values[i].string.text);
          }
 
-         if ((ptr - attr->values[i].string.text) > 255)
+         if ((ptr - attr->values[i].string.text) > (IPP_MAX_KEYWORD - 1))
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad keyword value \"%s\" - bad "
-                              "length %d (RFC 2911 section 4.1.3).",
-                              attr->name, attr->values[i].string.text,
-                              (int)strlen(attr->values[i].string.text));
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad keyword value \"%s\" - bad "
+                       "length %d (RFC 2911 section 4.1.3).",
+                       attr->name, attr->values[i].string.text,
+                       (int)strlen(attr->values[i].string.text));
          }
        }
         break;
@@ -4348,27 +5712,22 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad URI value \"%s\" - %s "
-                              "(RFC 2911 section 4.1.5).", attr->name,
-                              attr->values[i].string.text,
-                              URIStatusStrings[uri_status -
-                                               HTTP_URI_OVERFLOW]);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad URI value \"%s\" - %s "
+                       "(RFC 2911 section 4.1.5).", attr->name,
+                       attr->values[i].string.text,
+                       httpURIStatusString(uri_status));
          }
 
-         if (strlen(attr->values[i].string.text) > 1023)
+         if (strlen(attr->values[i].string.text) > (IPP_MAX_URI - 1))
          {
            valid = 0;
 
-           if (print)
-             print_test_error("\"%s\": Bad URI value \"%s\" - bad length %d "
-                              "(RFC 2911 section 4.1.5).", attr->name,
-                              attr->values[i].string.text,
-                              (int)strlen(attr->values[i].string.text));
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad URI value \"%s\" - bad length %d "
+                       "(RFC 2911 section 4.1.5).", attr->name,
+                       attr->values[i].string.text,
+                       (int)strlen(attr->values[i].string.text));
          }
        }
         break;
@@ -4389,25 +5748,21 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
          {
            valid = 0;
 
-            if (print)
-             print_test_error("\"%s\": Bad uriScheme value \"%s\" - bad "
-                              "characters (RFC 2911 section 4.1.6).",
-                              attr->name, attr->values[i].string.text);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad uriScheme value \"%s\" - bad "
+                       "characters (RFC 2911 section 4.1.6).",
+                       attr->name, attr->values[i].string.text);
          }
 
-         if ((ptr - attr->values[i].string.text) > 63)
+         if ((ptr - attr->values[i].string.text) > (IPP_MAX_URISCHEME - 1))
          {
            valid = 0;
 
-            if (print)
-             print_test_error("\"%s\": Bad uriScheme value \"%s\" - bad "
-                              "length %d (RFC 2911 section 4.1.6).",
-                              attr->name, attr->values[i].string.text,
-                              (int)strlen(attr->values[i].string.text));
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad uriScheme value \"%s\" - bad "
+                       "length %d (RFC 2911 section 4.1.6).",
+                       attr->name, attr->values[i].string.text,
+                       (int)strlen(attr->values[i].string.text));
          }
        }
         break;
@@ -4424,25 +5779,21 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
          {
            valid = 0;
 
-            if (print)
-             print_test_error("\"%s\": Bad charset value \"%s\" - bad "
-                              "characters (RFC 2911 section 4.1.7).",
-                              attr->name, attr->values[i].string.text);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad charset value \"%s\" - bad "
+                       "characters (RFC 2911 section 4.1.7).",
+                       attr->name, attr->values[i].string.text);
          }
 
-         if ((ptr - attr->values[i].string.text) > 40)
+         if ((ptr - attr->values[i].string.text) > (IPP_MAX_CHARSET - 1))
          {
            valid = 0;
 
-            if (print)
-             print_test_error("\"%s\": Bad charset value \"%s\" - bad "
-                              "length %d (RFC 2911 section 4.1.7).",
-                              attr->name, attr->values[i].string.text,
-                              (int)strlen(attr->values[i].string.text));
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad charset value \"%s\" - bad "
+                       "length %d (RFC 2911 section 4.1.7).",
+                       attr->name, attr->values[i].string.text,
+                       (int)strlen(attr->values[i].string.text));
          }
        }
         break;
@@ -4473,8 +5824,9 @@ validate_attr(ipp_attribute_t *attr,      /* I - Attribute to validate */
           char temp[256];              /* Temporary error string */
 
           regerror(i, &re, temp, sizeof(temp));
-         print_fatal_error("Unable to compile naturalLanguage regular "
+         print_fatal_error(outfile, "Unable to compile naturalLanguage regular "
                            "expression: %s.", temp);
+         break;
         }
 
         for (i = 0; i < attr->num_values; i ++)
@@ -4483,25 +5835,21 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
          {
            valid = 0;
 
-            if (print)
-             print_test_error("\"%s\": Bad naturalLanguage value \"%s\" - bad "
-                              "characters (RFC 2911 section 4.1.8).",
-                              attr->name, attr->values[i].string.text);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad naturalLanguage value \"%s\" - bad "
+                       "characters (RFC 2911 section 4.1.8).",
+                       attr->name, attr->values[i].string.text);
          }
 
-         if (strlen(attr->values[i].string.text) > 63)
+         if (strlen(attr->values[i].string.text) > (IPP_MAX_LANGUAGE - 1))
          {
            valid = 0;
 
-            if (print)
-             print_test_error("\"%s\": Bad naturalLanguage value \"%s\" - bad "
-                              "length %d (RFC 2911 section 4.1.8).",
-                              attr->name, attr->values[i].string.text,
-                              (int)strlen(attr->values[i].string.text));
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad naturalLanguage value \"%s\" - bad "
+                       "length %d (RFC 2911 section 4.1.8).",
+                       attr->name, attr->values[i].string.text,
+                       (int)strlen(attr->values[i].string.text));
          }
        }
 
@@ -4529,8 +5877,9 @@ validate_attr(ipp_attribute_t *attr,      /* I - Attribute to validate */
           char temp[256];              /* Temporary error string */
 
           regerror(i, &re, temp, sizeof(temp));
-         print_fatal_error("Unable to compile mimeMediaType regular "
+         print_fatal_error(outfile, "Unable to compile mimeMediaType regular "
                            "expression: %s.", temp);
+         break;
         }
 
         for (i = 0; i < attr->num_values; i ++)
@@ -4539,27 +5888,25 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
          {
            valid = 0;
 
-            if (print)
-             print_test_error("\"%s\": Bad mimeMediaType value \"%s\" - bad "
-                              "characters (RFC 2911 section 4.1.9).",
-                              attr->name, attr->values[i].string.text);
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad mimeMediaType value \"%s\" - bad "
+                       "characters (RFC 2911 section 4.1.9).",
+                       attr->name, attr->values[i].string.text);
          }
 
-         if (strlen(attr->values[i].string.text) > 255)
+         if (strlen(attr->values[i].string.text) > (IPP_MAX_MIMETYPE - 1))
          {
            valid = 0;
 
-            if (print)
-             print_test_error("\"%s\": Bad mimeMediaType value \"%s\" - bad "
-                              "length %d (RFC 2911 section 4.1.9).",
-                              attr->name, attr->values[i].string.text,
-                              (int)strlen(attr->values[i].string.text));
-            else
-             break;
+           add_stringf(errors,
+                       "\"%s\": Bad mimeMediaType value \"%s\" - bad "
+                       "length %d (RFC 2911 section 4.1.9).",
+                       attr->name, attr->values[i].string.text,
+                       (int)strlen(attr->values[i].string.text));
          }
        }
+
+       regfree(&re);
         break;
 
     default :
@@ -4570,19 +5917,57 @@ validate_attr(ipp_attribute_t *attr,    /* I - Attribute to validate */
 }
 
 
+/*
+ * 'with_flags_string()' - Return the "WITH-xxx" predicate that corresponds to
+                           the flags.
+ */
+
+static const char *                     /* O - WITH-xxx string */
+with_flags_string(int flags)            /* I - WITH flags */
+{
+  if (flags & _CUPS_WITH_ALL)
+  {
+    if (flags & _CUPS_WITH_HOSTNAME)
+      return ("WITH-ALL-HOSTNAMES");
+    else if (flags & _CUPS_WITH_RESOURCE)
+      return ("WITH-ALL-RESOURCES");
+    else if (flags & _CUPS_WITH_SCHEME)
+      return ("WITH-ALL-SCHEMES");
+    else
+      return ("WITH-ALL-VALUES");
+  }
+  else if (flags & _CUPS_WITH_HOSTNAME)
+    return ("WITH-HOSTNAME");
+  else if (flags & _CUPS_WITH_RESOURCE)
+    return ("WITH-RESOURCE");
+  else if (flags & _CUPS_WITH_SCHEME)
+    return ("WITH-SCHEME");
+  else
+    return ("WITH-VALUE");
+}
+
+
 /*
  * 'with_value()' - Test a WITH-VALUE predicate.
  */
 
 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 */
+with_value(cups_file_t     *outfile,   /* I - Output file */
+           cups_array_t    *errors,    /* I - Errors array */
+           char            *value,     /* I - Value string */
+           int             flags,      /* I - Flags for match */
            ipp_attribute_t *attr,      /* I - Attribute to compare */
-          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 */
+  int  i,                              /* Looping var */
+       match;                          /* Match? */
+  char temp[1024],                     /* Temporary value string */
+       *valptr;                        /* Pointer into value */
+
 
+  *matchbuf = '\0';
+  match     = (flags & _CUPS_WITH_ALL) ? 1 : 0;
 
  /*
   * NULL matches everything.
@@ -4603,12 +5988,10 @@ with_value(char            *value,      /* I - Value string */
         {
          char  op,                     /* Comparison operator */
                *nextptr;               /* Next pointer */
-         int   intvalue;               /* Integer value */
-
+         int   intvalue,               /* Integer value */
+               valmatch = 0;           /* Does the current value match? */
 
           valptr = value;
-         if (!strncmp(valptr, "no-value,", 9))
-           valptr += 9;
 
          while (isspace(*valptr & 255) || isdigit(*valptr & 255) ||
                 *valptr == '-' || *valptr == ',' || *valptr == '<' ||
@@ -4625,33 +6008,43 @@ with_value(char            *value,      /* I - Value string */
             if (!*valptr)
              break;
 
-           intvalue = strtol(valptr, &nextptr, 0);
+           intvalue = (int)strtol(valptr, &nextptr, 0);
            if (nextptr == valptr)
              break;
            valptr = nextptr;
 
-           switch (op)
+            if ((op == '=' && attr->values[i].integer == intvalue) ||
+                (op == '<' && attr->values[i].integer < intvalue) ||
+                (op == '>' && attr->values[i].integer > intvalue))
            {
-             case '=' :
-                 if (attr->values[i].integer == intvalue)
-                   return (1);
-                 break;
-             case '<' :
-                 if (attr->values[i].integer < intvalue)
-                   return (1);
-                 break;
-             case '>' :
-                 if (attr->values[i].integer > intvalue)
-                   return (1);
-                 break;
+             if (!matchbuf[0])
+               snprintf(matchbuf, matchlen, "%d", attr->values[i].integer);
+
+             valmatch = 1;
+             break;
            }
          }
+
+          if (flags & _CUPS_WITH_ALL)
+          {
+            if (!valmatch)
+            {
+              match = 0;
+              break;
+            }
+          }
+          else if (valmatch)
+          {
+            match = 1;
+            break;
+          }
         }
 
-       if (report)
+        if (!match && errors)
        {
          for (i = 0; i < attr->num_values; i ++)
-           print_test_error("GOT: %s=%d", attr->name, attr->values[i].integer);
+           add_stringf(errors, "GOT: %s=%d", attr->name,
+                       attr->values[i].integer);
        }
        break;
 
@@ -4660,12 +6053,10 @@ with_value(char            *value,      /* I - Value string */
         {
          char  op,                     /* Comparison operator */
                *nextptr;               /* Next pointer */
-         int   intvalue;               /* Integer value */
-
+         int   intvalue,               /* Integer value */
+               valmatch = 0;           /* Does the current value match? */
 
           valptr = value;
-         if (!strncmp(valptr, "no-value,", 9))
-           valptr += 9;
 
          while (isspace(*valptr & 255) || isdigit(*valptr & 255) ||
                 *valptr == '-' || *valptr == ',' || *valptr == '<' ||
@@ -4682,34 +6073,45 @@ with_value(char            *value,      /* I - Value string */
             if (!*valptr)
              break;
 
-           intvalue = strtol(valptr, &nextptr, 0);
+           intvalue = (int)strtol(valptr, &nextptr, 0);
            if (nextptr == valptr)
              break;
            valptr = nextptr;
 
-           switch (op)
+            if ((op == '=' && (attr->values[i].range.lower == intvalue ||
+                              attr->values[i].range.upper == intvalue)) ||
+               (op == '<' && attr->values[i].range.upper < intvalue) ||
+               (op == '>' && attr->values[i].range.upper > intvalue))
            {
-             case '=' :
-                 if (attr->values[i].range.lower == intvalue ||
-                     attr->values[i].range.upper == intvalue)
-                   return (1);
-                 break;
-             case '<' :
-                 if (attr->values[i].range.upper < intvalue)
-                   return (1);
-                 break;
-             case '>' :
-                 if (attr->values[i].range.upper > intvalue)
-                   return (1);
-                 break;
+             if (!matchbuf[0])
+               snprintf(matchbuf, matchlen, "%d-%d",
+                        attr->values[0].range.lower,
+                        attr->values[0].range.upper);
+
+             valmatch = 1;
+             break;
            }
          }
+
+          if (flags & _CUPS_WITH_ALL)
+          {
+            if (!valmatch)
+            {
+              match = 0;
+              break;
+            }
+          }
+          else if (valmatch)
+          {
+            match = 1;
+            break;
+          }
         }
 
-       if (report)
+        if (!match && errors)
        {
          for (i = 0; i < attr->num_values; i ++)
-           print_test_error("GOT: %s=%d-%d", attr->name,
+           add_stringf(errors, "GOT: %s=%d-%d", attr->name,
                             attr->values[i].range.lower,
                             attr->values[i].range.upper);
        }
@@ -4718,20 +6120,92 @@ with_value(char            *value,      /* I - Value string */
     case IPP_TAG_BOOLEAN :
        for (i = 0; i < attr->num_values; i ++)
        {
-          if (!strcmp(value, "true") == attr->values[i].boolean)
-           return (1);
+          if ((!strcmp(value, "true")) == attr->values[i].boolean)
+          {
+            if (!matchbuf[0])
+             strlcpy(matchbuf, value, matchlen);
+
+           if (!(flags & _CUPS_WITH_ALL))
+           {
+             match = 1;
+             break;
+           }
+         }
+         else if (flags & _CUPS_WITH_ALL)
+         {
+           match = 0;
+           break;
+         }
        }
 
-       if (report)
+       if (!match && errors)
        {
          for (i = 0; i < attr->num_values; i ++)
-           print_test_error("GOT: %s=%s", attr->name,
+           add_stringf(errors, "GOT: %s=%s", attr->name,
                             attr->values[i].boolean ? "true" : "false");
        }
        break;
 
+    case IPP_TAG_RESOLUTION :
+       for (i = 0; i < attr->num_values; i ++)
+       {
+         if (attr->values[i].resolution.xres ==
+                 attr->values[i].resolution.yres)
+           snprintf(temp, sizeof(temp), "%d%s",
+                    attr->values[i].resolution.xres,
+                    attr->values[i].resolution.units == IPP_RES_PER_INCH ?
+                        "dpi" : "dpcm");
+         else
+           snprintf(temp, sizeof(temp), "%dx%d%s",
+                    attr->values[i].resolution.xres,
+                    attr->values[i].resolution.yres,
+                    attr->values[i].resolution.units == IPP_RES_PER_INCH ?
+                        "dpi" : "dpcm");
+
+          if (!strcmp(value, temp))
+          {
+            if (!matchbuf[0])
+             strlcpy(matchbuf, value, matchlen);
+
+           if (!(flags & _CUPS_WITH_ALL))
+           {
+             match = 1;
+             break;
+           }
+         }
+         else if (flags & _CUPS_WITH_ALL)
+         {
+           match = 0;
+           break;
+         }
+       }
+
+       if (!match && errors)
+       {
+         for (i = 0; i < attr->num_values; i ++)
+         {
+           if (attr->values[i].resolution.xres ==
+                   attr->values[i].resolution.yres)
+             snprintf(temp, sizeof(temp), "%d%s",
+                      attr->values[i].resolution.xres,
+                      attr->values[i].resolution.units == IPP_RES_PER_INCH ?
+                          "dpi" : "dpcm");
+           else
+             snprintf(temp, sizeof(temp), "%dx%d%s",
+                      attr->values[i].resolution.xres,
+                      attr->values[i].resolution.yres,
+                      attr->values[i].resolution.units == IPP_RES_PER_INCH ?
+                          "dpi" : "dpcm");
+
+            if (strcmp(value, temp))
+             add_stringf(errors, "GOT: %s=%s", attr->name, temp);
+         }
+       }
+       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 :
@@ -4743,7 +6217,7 @@ with_value(char            *value,        /* I - Value string */
     case IPP_TAG_TEXTLANG :
     case IPP_TAG_URI :
     case IPP_TAG_URISCHEME :
-        if (regex)
+        if (flags & _CUPS_WITH_REGEX)
        {
         /*
          * Value is an extended, case-sensitive POSIX regular expression...
@@ -4753,11 +6227,9 @@ with_value(char            *value,       /* I - Value string */
 
           if ((i = regcomp(&re, value, REG_EXTENDED | REG_NOSUB)) != 0)
          {
-           char temp[256];             /* Temporary string */
-
             regerror(i, &re, temp, sizeof(temp));
 
-           print_fatal_error("Unable to compile WITH-VALUE regular expression "
+           print_fatal_error(outfile, "Unable to compile WITH-VALUE regular expression "
                              "\"%s\" - %s", value, temp);
            return (0);
          }
@@ -4768,50 +6240,320 @@ with_value(char            *value,     /* I - Value string */
 
          for (i = 0; i < attr->num_values; i ++)
          {
-           if (regexec(&re, attr->values[i].string.text, 0, NULL, 0))
+           if (!regexec(&re, get_string(attr, i, flags, temp, sizeof(temp)),
+                        0, NULL, 0))
            {
-             if (report)
-               print_test_error("GOT: %s=\"%s\"", attr->name,
-                                attr->values[i].string.text);
-             else
+             if (!matchbuf[0])
+               strlcpy(matchbuf,
+                       get_string(attr, i, flags, temp, sizeof(temp)),
+                       matchlen);
+
+             if (!(flags & _CUPS_WITH_ALL))
+             {
+               match = 1;
                break;
+             }
+           }
+           else if (flags & _CUPS_WITH_ALL)
+           {
+             match = 0;
+             break;
            }
          }
 
          regfree(&re);
-
-          return (i == attr->num_values);
        }
-       else
+       else if (ippGetValueTag(attr) == IPP_TAG_URI && !(flags & (_CUPS_WITH_SCHEME | _CUPS_WITH_HOSTNAME | _CUPS_WITH_RESOURCE)))
        {
         /*
-         * Value is a literal string, see if at least one value matches the
-         * literal string...
+         * Value is a literal URI string, see if the value(s) match...
          */
 
          for (i = 0; i < attr->num_values; i ++)
          {
-           if (!strcmp(value, attr->values[i].string.text))
-             return (1);
+           if (!compare_uris(value, get_string(attr, i, flags, temp, sizeof(temp))))
+           {
+             if (!matchbuf[0])
+               strlcpy(matchbuf,
+                       get_string(attr, i, flags, temp, sizeof(temp)),
+                       matchlen);
+
+             if (!(flags & _CUPS_WITH_ALL))
+             {
+               match = 1;
+               break;
+             }
+           }
+           else if (flags & _CUPS_WITH_ALL)
+           {
+             match = 0;
+             break;
+           }
          }
+       }
+       else
+       {
+        /*
+         * Value is a literal string, see if the value(s) match...
+         */
 
-         if (report)
+         for (i = 0; i < attr->num_values; i ++)
          {
-           for (i = 0; i < attr->num_values; i ++)
-             print_test_error("GOT: %s=\"%s\"", attr->name,
-                              attr->values[i].string.text);
+           int result;
+
+            switch (ippGetValueTag(attr))
+            {
+              case IPP_TAG_URI :
+                 /*
+                  * Some URI components are case-sensitive, some not...
+                  */
+
+                  if (flags & (_CUPS_WITH_SCHEME | _CUPS_WITH_HOSTNAME))
+                    result = _cups_strcasecmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
+                  else
+                    result = strcmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
+                  break;
+
+              case IPP_TAG_MIMETYPE :
+              case IPP_TAG_NAME :
+              case IPP_TAG_NAMELANG :
+              case IPP_TAG_TEXT :
+              case IPP_TAG_TEXTLANG :
+                 /*
+                  * mimeMediaType, nameWithoutLanguage, nameWithLanguage,
+                  * textWithoutLanguage, and textWithLanguage are defined to
+                  * be case-insensitive strings...
+                  */
+
+                  result = _cups_strcasecmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
+                  break;
+
+              default :
+                 /*
+                  * Other string syntaxes are defined as lowercased so we use
+                  * case-sensitive comparisons to catch problems...
+                  */
+
+                  result = strcmp(value, get_string(attr, i, flags, temp, sizeof(temp)));
+                  break;
+            }
+
+            if (!result)
+           {
+             if (!matchbuf[0])
+               strlcpy(matchbuf,
+                       get_string(attr, i, flags, temp, sizeof(temp)),
+                       matchlen);
+
+             if (!(flags & _CUPS_WITH_ALL))
+             {
+               match = 1;
+               break;
+             }
+           }
+           else if (flags & _CUPS_WITH_ALL)
+           {
+             match = 0;
+             break;
+           }
          }
        }
+
+        if (!match && errors)
+        {
+         for (i = 0; i < attr->num_values; i ++)
+           add_stringf(errors, "GOT: %s=\"%s\"", attr->name,
+                       attr->values[i].string.text);
+        }
        break;
 
     default :
         break;
   }
 
-  return (0);
+  return (match);
 }
 
 
 /*
- * End of "$Id$".
+ * 'with_value_from()' - Test a WITH-VALUE-FROM predicate.
  */
+
+static int                             /* O - 1 on match, 0 on non-match */
+with_value_from(
+    cups_array_t    *errors,           /* I - Errors array */
+    ipp_attribute_t *fromattr,         /* I - "From" attribute */
+    ipp_attribute_t *attr,             /* I - Attribute to compare */
+    char            *matchbuf,         /* I - Buffer to hold matching value */
+    size_t          matchlen)          /* I - Length of match buffer */
+{
+  int  i, j,                           /* Looping vars */
+       count = ippGetCount(attr),      /* Number of attribute values */
+       match = 1;                      /* Match? */
+
+
+  *matchbuf = '\0';
+
+ /*
+  * Compare the from value(s) to the attribute value(s)...
+  */
+
+  switch (ippGetValueTag(attr))
+  {
+    case IPP_TAG_INTEGER :
+        if (ippGetValueTag(fromattr) != IPP_TAG_INTEGER && ippGetValueTag(fromattr) != IPP_TAG_RANGE)
+         goto wrong_value_tag;
+
+       for (i = 0; i < count; i ++)
+       {
+         int value = ippGetInteger(attr, i);
+                                       /* Current integer value */
+
+         if (ippContainsInteger(fromattr, value))
+         {
+           if (!matchbuf[0])
+             snprintf(matchbuf, matchlen, "%d", value);
+         }
+         else
+         {
+           add_stringf(errors, "GOT: %s=%d", ippGetName(attr), value);
+           match = 0;
+         }
+       }
+       break;
+
+    case IPP_TAG_ENUM :
+        if (ippGetValueTag(fromattr) != IPP_TAG_ENUM)
+         goto wrong_value_tag;
+
+       for (i = 0; i < count; i ++)
+       {
+         int value = ippGetInteger(attr, i);
+                                       /* Current integer value */
+
+         if (ippContainsInteger(fromattr, value))
+         {
+           if (!matchbuf[0])
+             snprintf(matchbuf, matchlen, "%d", value);
+         }
+         else
+         {
+           add_stringf(errors, "GOT: %s=%d", ippGetName(attr), value);
+           match = 0;
+         }
+       }
+       break;
+
+    case IPP_TAG_RESOLUTION :
+        if (ippGetValueTag(fromattr) != IPP_TAG_RESOLUTION)
+         goto wrong_value_tag;
+
+       for (i = 0; i < count; i ++)
+       {
+         int xres, yres;
+         ipp_res_t units;
+          int fromcount = ippGetCount(fromattr);
+         int fromxres, fromyres;
+         ipp_res_t fromunits;
+
+         xres = ippGetResolution(attr, i, &yres, &units);
+
+          for (j = 0; j < fromcount; j ++)
+         {
+           fromxres = ippGetResolution(fromattr, j, &fromyres, &fromunits);
+           if (fromxres == xres && fromyres == yres && fromunits == units)
+             break;
+         }
+
+         if (j < fromcount)
+         {
+           if (!matchbuf[0])
+           {
+             if (xres == yres)
+               snprintf(matchbuf, matchlen, "%d%s", xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+             else
+               snprintf(matchbuf, matchlen, "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+           }
+         }
+         else
+         {
+           if (xres == yres)
+             add_stringf(errors, "GOT: %s=%d%s", ippGetName(attr), xres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+           else
+             add_stringf(errors, "GOT: %s=%dx%d%s", ippGetName(attr), xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+
+           match = 0;
+         }
+       }
+       break;
+
+    case IPP_TAG_NOVALUE :
+    case IPP_TAG_UNKNOWN :
+       return (1);
+
+    case IPP_TAG_CHARSET :
+    case IPP_TAG_KEYWORD :
+    case IPP_TAG_LANGUAGE :
+    case IPP_TAG_MIMETYPE :
+    case IPP_TAG_NAME :
+    case IPP_TAG_NAMELANG :
+    case IPP_TAG_TEXT :
+    case IPP_TAG_TEXTLANG :
+    case IPP_TAG_URISCHEME :
+       for (i = 0; i < count; i ++)
+       {
+         const char *value = ippGetString(attr, i, NULL);
+                                       /* Current string value */
+
+         if (ippContainsString(fromattr, value))
+         {
+           if (!matchbuf[0])
+             strlcpy(matchbuf, value, matchlen);
+         }
+         else
+         {
+           add_stringf(errors, "GOT: %s='%s'", ippGetName(attr), value);
+           match = 0;
+         }
+       }
+       break;
+
+    case IPP_TAG_URI :
+       for (i = 0; i < count; i ++)
+       {
+         const char *value = ippGetString(attr, i, NULL);
+                                       /* Current string value */
+          int fromcount = ippGetCount(fromattr);
+
+          for (j = 0; j < fromcount; j ++)
+          {
+            if (!compare_uris(value, ippGetString(fromattr, j, NULL)))
+            {
+              if (!matchbuf[0])
+                strlcpy(matchbuf, value, matchlen);
+              break;
+            }
+          }
+
+         if (j >= fromcount)
+         {
+           add_stringf(errors, "GOT: %s='%s'", ippGetName(attr), value);
+           match = 0;
+         }
+       }
+       break;
+
+    default :
+        match = 0;
+        break;
+  }
+
+  return (match);
+
+  /* value tag mismatch between fromattr and attr */
+  wrong_value_tag :
+
+  add_stringf(errors, "GOT: %s OF-TYPE %s", ippGetName(attr), ippTagString(ippGetValueTag(attr)));
+
+  return (0);
+}