]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - test/ipptool.c
Merge pull request #5297 from FedericoYundt/patch-1
[thirdparty/cups.git] / test / ipptool.c
index 9f4551566a12e9f91f2b4d8a77b7fe726900d0f9..697ea8a9623e0c2f4411e8694173d248a7d4dad4 100644 (file)
@@ -1,18 +1,11 @@
 /*
- * "$Id$"
- *
  * ipptool command for CUPS.
  *
- * Copyright 2007-2015 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/".
+ * Copyright © 2007-2018 by Apple Inc.
+ * Copyright © 1997-2007 by Easy Software Products.
  *
- * This file is subject to the Apple OS-Developed Software exception.
+ * Licensed under Apache License v2.0.  See the file "LICENSE" for more
+ * information.
  */
 
 /*
@@ -53,6 +46,7 @@ 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;
@@ -103,50 +97,68 @@ typedef struct _cups_status_s              /**** Status info ****/
                repeat_no_match;        /* Repeat the test when it matches */
 } _cups_status_t;
 
-typedef struct _cups_var_s             /**** Variable ****/
-{
-  char         *name,                  /* Name of variable */
-               *value;                 /* Value of variable */
-} _cups_var_t;
-
-typedef struct _cups_vars_s            /**** Set of variables ****/
+typedef struct _cups_testdata_s                /**** Test Data ****/
 {
-  char         *uri,                   /* URI for printer */
-               *filename,              /* Filename */
-               scheme[64],             /* Scheme from URI */
-               userpass[256],          /* Username/password from URI */
-               hostname[256],          /* Hostname from URI */
-               resource[1024];         /* Resource path from URI */
-  int          port;                   /* Port number from URI */
-  http_encryption_t encryption;                /* Encryption for connection? */
-  double       timeout;                /* Timeout for connection */
+  /* Global Options */
+  http_encryption_t encryption;                /* Encryption for connection */
   int          family;                 /* Address family */
-  cups_array_t *vars;                  /* Array of variables */
-} _cups_vars_t;
+  _cups_output_t output;               /* Output mode */
+  int          stop_after_include_error;
+                                       /* Stop after include errors? */
+  double       timeout;                /* Timeout for connection */
+  int          validate_headers,       /* Validate HTTP headers in response? */
+                verbosity;             /* Show all attributes? */
+
+  /* Test Defaults */
+  int          def_ignore_errors;      /* Default IGNORE-ERRORS value */
+  _cups_transfer_t def_transfer;       /* Default TRANSFER value */
+  int          def_version;            /* Default IPP version */
+
+  /* Global State */
+  http_t       *http;                  /* HTTP connection to printer/server */
+  cups_file_t  *outfile;               /* Output file */
+  int          show_header,            /* Show the test header? */
+               xml_header;             /* 1 if XML plist header was written */
+  int          pass,                   /* Have we passed all tests? */
+               test_count,             /* Number of tests (total) */
+               pass_count,             /* Number of tests that passed */
+               fail_count,             /* Number of tests that failed */
+               skip_count;             /* Number of tests that were skipped */
+
+  /* Per-Test State */
+  cups_array_t *errors;                /* Errors array */
+  int          prev_pass,              /* Result of previous test */
+               skip_previous;          /* Skip on previous test failure? */
+  char         compression[16];        /* COMPRESSION value */
+  useconds_t   delay;                  /* Initial delay */
+  int          num_displayed;          /* Number of displayed attributes */
+  char         *displayed[200];        /* Displayed attributes */
+  int          num_expects;            /* Number of expected attributes */
+  _cups_expect_t expects[200],         /* Expected attributes */
+               *expect,                /* Current expected attribute */
+               *last_expect;           /* Last EXPECT (for predicates) */
+  char         file[1024],             /* Data filename */
+               file_id[1024];          /* File identifier */
+  int          ignore_errors;          /* Ignore test failures? */
+  char         name[1024];             /* Test name */
+  useconds_t   repeat_interval;        /* Repeat interval (delay) */
+  int          request_id;             /* Current request ID */
+  char         resource[512];          /* Resource for request */
+  int          skip_test,              /* Skip this test? */
+               num_statuses;           /* Number of valid status codes */
+  _cups_status_t statuses[100],                /* Valid status codes */
+               *last_status;           /* Last STATUS (for predicates) */
+  char         test_id[1024];          /* Test identifier */
+  _cups_transfer_t transfer;           /* To chunk or not to chunk */
+  int          version;                /* IPP version number to use */
+} _cups_testdata_t;
 
 
 /*
  * Globals...
  */
 
-static _cups_transfer_t Transfer = _CUPS_TRANSFER_AUTO;
-                                       /* How to transfer requests */
-static _cups_output_t  Output = _CUPS_OUTPUT_LIST;
-                                       /* Output mode */
-static int     Cancel = 0,             /* Cancel test? */
-               IgnoreErrors = 0,       /* Ignore errors? */
-               StopAfterIncludeError = 0,
-                                       /* Stop after include errors? */
-               Verbosity = 0,          /* Show all attributes? */
-               Version = 11,           /* Default IPP version */
-               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 */
+static int     Cancel = 0;             /* Cancel test? */
 
 
 /*
@@ -154,33 +166,33 @@ static int        PasswordTries = 0;      /* Number of tries with password */
  */
 
 static void    add_stringf(cups_array_t *a, const char *s, ...) __attribute__ ((__format__ (__printf__, 2, 3)));
-static int     compare_vars(_cups_var_t *a, _cups_var_t *b);
-static int     do_tests(FILE *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      compare_uris(const char *a, const char *b);
+static int     do_test(_ipp_file_t *f, _ipp_vars_t *vars, _cups_testdata_t *data);
+static int     do_tests(const char *testfile, _ipp_vars_t *vars, _cups_testdata_t *data);
+static int     error_cb(_ipp_file_t *f, _cups_testdata_t *data, const char *error);
 static int      expect_matches(_cups_expect_t *expect, ipp_tag_t value_tag);
-static ipp_t   *get_collection(FILE *outfile, _cups_vars_t *vars, FILE *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(FILE *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 const char *password_cb(const char *prompt);
+static void    init_data(_cups_testdata_t *data);
+static char    *iso_date(const ipp_uchar_t *date);
 static void    pause_message(const char *message);
-static void    print_attr(FILE *outfile, int format, ipp_attribute_t *attr, ipp_tag_t *group);
-static void    print_csv(FILE *outfile, ipp_attribute_t *attr, int num_displayed, char **displayed, size_t *widths);
-static void    print_fatal_error(FILE *outfile, const char *s, ...) __attribute__ ((__format__ (__printf__, 2, 3)));
-static void    print_line(FILE *outfile, ipp_attribute_t *attr, int num_displayed, char **displayed, size_t *widths);
-static void    print_xml_header(FILE *outfile);
-static void    print_xml_string(FILE *outfile, const char *element, const char *s);
-static void    print_xml_trailer(FILE *outfile, int success, const char *message);
-static void    set_variable(FILE *outfile, _cups_vars_t *vars, const char *name, const char *value);
+static void    print_attr(cups_file_t *outfile, int output, ipp_attribute_t *attr, ipp_tag_t *group);
+static void    print_csv(_cups_testdata_t *data, ipp_t *ipp, ipp_attribute_t *attr, int num_displayed, char **displayed, size_t *widths);
+static void    print_fatal_error(_cups_testdata_t *data, const char *s, ...) __attribute__ ((__format__ (__printf__, 2, 3)));
+static void    print_ippserver_attr(_cups_testdata_t *data, ipp_attribute_t *attr, int indent);
+static void    print_ippserver_string(_cups_testdata_t *data, const char *s, size_t len);
+static void    print_line(_cups_testdata_t *data, ipp_t *ipp, ipp_attribute_t *attr, int num_displayed, char **displayed, size_t *widths);
+static void    print_xml_header(_cups_testdata_t *data);
+static void    print_xml_string(cups_file_t *outfile, const char *element, const char *s);
+static void    print_xml_trailer(_cups_testdata_t *data, int success, const char *message);
 #ifndef WIN32
 static void    sigterm_handler(int sig);
 #endif /* WIN32 */
 static int     timeout_cb(http_t *http, void *user_data);
+static int     token_cb(_ipp_file_t *f, _ipp_vars_t *vars, _cups_testdata_t *data, const char *token);
 static void    usage(void) __attribute__((noreturn));
-static int     validate_attr(FILE *outfile, cups_array_t *errors, ipp_attribute_t *attr);
-static int      with_value(FILE *outfile, cups_array_t *errors, char *value, int flags, ipp_attribute_t *attr, char *matchbuf, size_t matchlen);
+static const char *with_flags_string(int flags);
+static int      with_value(_cups_testdata_t *data, 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);
 
 
@@ -194,20 +206,17 @@ main(int  argc,                           /* I - Number of command-line args */
 {
   int                  i;              /* Looping var */
   int                  status;         /* Status of tests... */
-  FILE                 *outfile = stdout;
-                                       /* Output file */
   char                 *opt,           /* Current option */
                        name[1024],     /* Name/value buffer */
                        *value,         /* Pointer to value */
                        filename[1024], /* Real filename */
-                       testname[1024], /* Real test filename */
-                       uri[1024];      /* Copy of printer URI */
+                       testname[1024]; /* Real test filename */
   const char           *ext,           /* Extension on filename */
                        *testfile;      /* Test file to use */
   int                  interval,       /* Test interval in microseconds */
                        repeat;         /* Repeat count */
-  _cups_vars_t         vars;           /* Variables */
-  http_uri_status_t    uri_status;     /* URI separation status */
+  _cups_testdata_t     data;           /* Test data */
+  _ipp_vars_t          vars;           /* Variables */
   _cups_globals_t      *cg = _cupsGlobals();
                                        /* Global data */
 
@@ -227,9 +236,9 @@ main(int  argc,                             /* I - Number of command-line args */
 
   _cupsSetLocale(argv);
 
-  memset(&vars, 0, sizeof(vars));
-  vars.family = AF_UNSPEC;
-  vars.vars   = cupsArrayNew((cups_array_func_t)compare_vars, NULL);
+  init_data(&data);
+
+  _ippVarsInit(&vars, NULL, (_ipp_ferror_cb_t)error_cb, (_ipp_ftoken_cb_t)token_cb);
 
  /*
   * We need at least:
@@ -248,9 +257,30 @@ main(int  argc,                            /* I - Number of command-line args */
     {
       usage();
     }
+    else if (!strcmp(argv[i], "--ippserver"))
+    {
+      i ++;
+
+      if (i >= argc)
+      {
+       _cupsLangPuts(stderr, _("ipptool: Missing filename for \"--ippserver\"."));
+       usage();
+      }
+
+      if (data.outfile != cupsFileStdout())
+       usage();
+
+      if ((data.outfile = cupsFileOpen(argv[i], "w")) == NULL)
+      {
+       _cupsLangPrintf(stderr, _("%s: Unable to open \"%s\": %s"), "ipptool", argv[i], strerror(errno));
+       exit(1);
+      }
+
+      data.output = _CUPS_OUTPUT_IPPSERVER;
+    }
     else if (!strcmp(argv[i], "--stop-after-include-error"))
     {
-      StopAfterIncludeError = 1;
+      data.stop_after_include_error = 1;
     }
     else if (!strcmp(argv[i], "--version"))
     {
@@ -264,22 +294,22 @@ main(int  argc,                           /* I - Number of command-line args */
         switch (*opt)
         {
          case '4' : /* Connect using IPv4 only */
-             vars.family = AF_INET;
+             data.family = AF_INET;
              break;
 
 #ifdef AF_INET6
          case '6' : /* Connect using IPv6 only */
-             vars.family = AF_INET6;
+             data.family = AF_INET6;
              break;
 #endif /* AF_INET6 */
 
           case 'C' : /* Enable HTTP chunking */
-              Transfer = _CUPS_TRANSFER_CHUNKED;
+              data.def_transfer = _CUPS_TRANSFER_CHUNKED;
               break;
 
          case 'E' : /* Encrypt with TLS */
 #ifdef HAVE_SSL
-             vars.encryption = HTTP_ENCRYPT_REQUIRED;
+             data.encryption = HTTP_ENCRYPT_REQUIRED;
 #else
              _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."),
                              argv[0]);
@@ -287,11 +317,11 @@ main(int  argc,                           /* I - Number of command-line args */
              break;
 
           case 'I' : /* Ignore errors */
-             IgnoreErrors = 1;
+             data.def_ignore_errors = 1;
              break;
 
           case 'L' : /* Disable HTTP chunking */
-              Transfer = _CUPS_TRANSFER_LENGTH;
+              data.def_transfer = _CUPS_TRANSFER_LENGTH;
               break;
 
           case 'P' : /* Output to plist file */
@@ -303,16 +333,16 @@ main(int  argc,                           /* I - Number of command-line args */
                usage();
               }
 
-              if (outfile != stdout)
+              if (data.outfile != cupsFileStdout())
                 usage();
 
-              if ((outfile = fopen(argv[i], "w")) == NULL)
+              if ((data.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;
+             data.output = _CUPS_OUTPUT_PLIST;
 
               if (interval || repeat)
              {
@@ -323,7 +353,7 @@ main(int  argc,                             /* I - Number of command-line args */
 
          case 'S' : /* Encrypt with SSL */
 #ifdef HAVE_SSL
-             vars.encryption = HTTP_ENCRYPT_ALWAYS;
+             data.encryption = HTTP_ENCRYPT_ALWAYS;
 #else
              _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."),
                              argv[0]);
@@ -341,7 +371,7 @@ main(int  argc,                             /* I - Number of command-line args */
                usage();
               }
 
-             vars.timeout = _cupsStrScand(argv[i], NULL, localeconv());
+             data.timeout = _cupsStrScand(argv[i], NULL, localeconv());
              break;
 
          case 'V' : /* Set IPP version */
@@ -356,26 +386,34 @@ main(int  argc,                           /* I - Number of command-line args */
               }
 
              if (!strcmp(argv[i], "1.0"))
-               Version = 10;
+             {
+               data.def_version = 10;
+             }
              else if (!strcmp(argv[i], "1.1"))
-               Version = 11;
+             {
+               data.def_version = 11;
+             }
              else if (!strcmp(argv[i], "2.0"))
-               Version = 20;
+             {
+               data.def_version = 20;
+             }
              else if (!strcmp(argv[i], "2.1"))
-               Version = 21;
+             {
+               data.def_version = 21;
+             }
              else if (!strcmp(argv[i], "2.2"))
-               Version = 22;
+             {
+               data.def_version = 22;
+             }
              else
              {
-               _cupsLangPrintf(stderr,
-                               _("%s: Bad version %s for \"-V\"."),
-                               "ipptool", argv[i]);
+               _cupsLangPrintf(stderr, _("%s: Bad version %s for \"-V\"."), "ipptool", argv[i]);
                usage();
              }
              break;
 
           case 'X' : /* Produce XML output */
-             Output = _CUPS_OUTPUT_PLIST;
+             data.output = _CUPS_OUTPUT_PLIST;
 
               if (interval || repeat)
              {
@@ -385,7 +423,7 @@ main(int  argc,                             /* I - Number of command-line args */
              break;
 
           case 'c' : /* CSV output */
-              Output = _CUPS_OUTPUT_CSV;
+              data.output = _CUPS_OUTPUT_CSV;
               break;
 
           case 'd' : /* Define a variable */
@@ -404,7 +442,7 @@ main(int  argc,                             /* I - Number of command-line args */
              else
                value = name + strlen(name);
 
-             set_variable(outfile, &vars, name, value);
+             _ippVarsSet(&vars, name, value);
              break;
 
           case 'f' : /* Set the default test filename */
@@ -417,12 +455,6 @@ main(int  argc,                            /* I - Number of command-line args */
                usage();
               }
 
-              if (vars.filename)
-              {
-               free(vars.filename);
-               vars.filename = NULL;
-             }
-
               if (access(argv[i], 0))
               {
                /*
@@ -436,70 +468,64 @@ main(int  argc,                           /* I - Number of command-line args */
 #endif /* WIN32 */
                     )
                {
-                 snprintf(filename, sizeof(filename), "%s/ipptool/%s",
-                          cg->cups_datadir, argv[i]);
+                 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]);
+                   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);
+                     strlcpy(filename, argv[i], sizeof(filename));
                  }
-                 else
-                   vars.filename = strdup(filename);
                }
-               else
-                 vars.filename = strdup(filename);
              }
               else
-               vars.filename = strdup(argv[i]);
+               strlcpy(filename, argv[i], sizeof(filename));
+
+             _ippVarsSet(&vars, "filename", filename);
 
-              if ((ext = strrchr(vars.filename, '.')) != NULL)
+              if ((ext = strrchr(filename, '.')) != NULL)
               {
                /*
                 * Guess the MIME media type based on the extension...
                 */
 
                 if (!_cups_strcasecmp(ext, ".gif"))
-                  set_variable(outfile, &vars, "filetype", "image/gif");
+                  _ippVarsSet(&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");
+                  _ippVarsSet(&vars, "filetype", "text/html");
                 else if (!_cups_strcasecmp(ext, ".jpg") ||
                          !_cups_strcasecmp(ext, ".jpeg"))
-                  set_variable(outfile, &vars, "filetype", "image/jpeg");
+                  _ippVarsSet(&vars, "filetype", "image/jpeg");
                 else if (!_cups_strcasecmp(ext, ".pcl") ||
                          !_cups_strcasecmp(ext, ".pcl.gz"))
-                  set_variable(outfile, &vars, "filetype", "application/vnd.hp-PCL");
+                  _ippVarsSet(&vars, "filetype", "application/vnd.hp-PCL");
                 else if (!_cups_strcasecmp(ext, ".pdf"))
-                  set_variable(outfile, &vars, "filetype", "application/pdf");
+                  _ippVarsSet(&vars, "filetype", "application/pdf");
                 else if (!_cups_strcasecmp(ext, ".png"))
-                  set_variable(outfile, &vars, "filetype", "image/png");
+                  _ippVarsSet(&vars, "filetype", "image/png");
                 else if (!_cups_strcasecmp(ext, ".ps") ||
                          !_cups_strcasecmp(ext, ".ps.gz"))
-                  set_variable(outfile, &vars, "filetype", "application/postscript");
+                  _ippVarsSet(&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");
+                  _ippVarsSet(&vars, "filetype", "image/pwg-raster");
                 else if (!_cups_strcasecmp(ext, ".tif") ||
                          !_cups_strcasecmp(ext, ".tiff"))
-                  set_variable(outfile, &vars, "filetype", "image/tiff");
+                  _ippVarsSet(&vars, "filetype", "image/tiff");
                 else if (!_cups_strcasecmp(ext, ".txt") ||
                          !_cups_strcasecmp(ext, ".txt.gz"))
-                  set_variable(outfile, &vars, "filetype", "text/plain");
+                  _ippVarsSet(&vars, "filetype", "text/plain");
                 else if (!_cups_strcasecmp(ext, ".urf") ||
                          !_cups_strcasecmp(ext, ".urf.gz"))
-                  set_variable(outfile, &vars, "filetype", "image/urf");
+                  _ippVarsSet(&vars, "filetype", "image/urf");
                 else if (!_cups_strcasecmp(ext, ".xps"))
-                  set_variable(outfile, &vars, "filetype", "application/openxps");
+                  _ippVarsSet(&vars, "filetype", "application/openxps");
                 else
-                 set_variable(outfile, &vars, "filetype", "application/octet-stream");
+                 _ippVarsSet(&vars, "filetype", "application/octet-stream");
               }
               else
               {
@@ -507,40 +533,41 @@ main(int  argc,                           /* I - Number of command-line args */
                 * Use the "auto-type" MIME media type...
                 */
 
-               set_variable(outfile, &vars, "filetype", "application/octet-stream");
+               _ippVarsSet(&vars, "filetype", "application/octet-stream");
               }
              break;
 
+          case 'h' : /* Validate response headers */
+              data.validate_headers = 1;
+              break;
+
           case 'i' : /* Test every N seconds */
              i ++;
 
              if (i >= argc)
              {
-               _cupsLangPuts(stderr,
-                             _("ipptool: Missing seconds for \"-i\"."));
+               _cupsLangPuts(stderr, _("ipptool: Missing seconds for \"-i\"."));
                usage();
               }
              else
              {
-               interval = (int)(_cupsStrScand(argv[i], NULL, localeconv()) *
-                                1000000.0);
+               interval = (int)(_cupsStrScand(argv[i], NULL, localeconv()) * 1000000.0);
                if (interval <= 0)
                {
-                 _cupsLangPuts(stderr,
-                               _("ipptool: Invalid seconds for \"-i\"."));
+                 _cupsLangPuts(stderr, _("ipptool: Invalid seconds for \"-i\"."));
                  usage();
                }
               }
 
-              if (Output == _CUPS_OUTPUT_PLIST && interval)
+              if ((data.output == _CUPS_OUTPUT_PLIST || data.output == _CUPS_OUTPUT_IPPSERVER) && interval)
              {
-               _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"-P\" and \"-X\"."));
+               _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"--ippserver\", \"-P\", and \"-X\"."));
                usage();
              }
              break;
 
           case 'l' : /* List as a table */
-              Output = _CUPS_OUTPUT_LIST;
+              data.output = _CUPS_OUTPUT_LIST;
               break;
 
           case 'n' : /* Repeat count */
@@ -548,43 +575,40 @@ main(int  argc,                           /* I - Number of command-line args */
 
              if (i >= argc)
              {
-               _cupsLangPuts(stderr,
-                             _("ipptool: Missing count for \"-n\"."));
+               _cupsLangPuts(stderr, _("ipptool: Missing count for \"-n\"."));
                usage();
               }
              else
                repeat = atoi(argv[i]);
 
-              if (Output == _CUPS_OUTPUT_PLIST && repeat)
+              if ((data.output == _CUPS_OUTPUT_PLIST || data.output == _CUPS_OUTPUT_IPPSERVER) && repeat)
              {
-               _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"-P\" and \"-X\"."));
+               _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are incompatible with \"--ippserver\", \"-P\", and \"-X\"."));
                usage();
              }
              break;
 
           case 'q' : /* Be quiet */
-              Output = _CUPS_OUTPUT_QUIET;
+              data.output = _CUPS_OUTPUT_QUIET;
               break;
 
           case 't' : /* CUPS test output */
-              Output = _CUPS_OUTPUT_TEST;
+              data.output = _CUPS_OUTPUT_TEST;
               break;
 
           case 'v' : /* Be verbose */
-             Verbosity ++;
+             data.verbosity ++;
              break;
 
          default :
-             _cupsLangPrintf(stderr, _("ipptool: Unknown option \"-%c\"."),
-                             *opt);
+             _cupsLangPrintf(stderr, _("%s: Unknown option \"-%c\"."), "ipptool", *opt);
              usage();
        }
       }
     }
     else if (!strncmp(argv[i], "ipp://", 6) || !strncmp(argv[i], "http://", 7)
 #ifdef HAVE_SSL
-            || !strncmp(argv[i], "ipps://", 7)
-            || !strncmp(argv[i], "https://", 8)
+            || !strncmp(argv[i], "ipps://", 7) || !strncmp(argv[i], "https://", 8)
 #endif /* HAVE_SSL */
             )
     {
@@ -600,36 +624,17 @@ main(int  argc,                           /* I - Number of command-line args */
 
 #ifdef HAVE_SSL
       if (!strncmp(argv[i], "ipps://", 7) || !strncmp(argv[i], "https://", 8))
-        vars.encryption = HTTP_ENCRYPT_ALWAYS;
+        data.encryption = HTTP_ENCRYPT_ALWAYS;
 #endif /* HAVE_SSL */
 
-      vars.uri   = argv[i];
-      uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, vars.uri,
-                                   vars.scheme, sizeof(vars.scheme),
-                                   vars.userpass, sizeof(vars.userpass),
-                                  vars.hostname, sizeof(vars.hostname),
-                                  &(vars.port),
-                                  vars.resource, sizeof(vars.resource));
-
-      if (uri_status != HTTP_URI_OK)
+      if (!_ippVarsSet(&vars, "uri", argv[i]))
       {
-        _cupsLangPrintf(stderr, _("ipptool: Bad URI - %s."), httpURIStatusString(uri_status));
+        _cupsLangPrintf(stderr, _("ipptool: Bad URI \"%s\"."), argv[i]);
         return (1);
       }
 
-      if (vars.userpass[0])
-      {
-        if ((Password = strchr(vars.userpass, ':')) != NULL)
-         *Password++ = '\0';
-
-        Username = vars.userpass;
-       cupsSetPasswordCB(password_cb);
-       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;
+      if (vars.username[0] && vars.password)
+       cupsSetPasswordCB2(_ippVarsPasswordCB, &vars);
     }
     else
     {
@@ -650,8 +655,7 @@ main(int  argc,                             /* I - Number of command-line args */
 #endif /* WIN32 */
           )
       {
-        snprintf(testname, sizeof(testname), "%s/ipptool/%s", cg->cups_datadir,
-                 argv[i]);
+        snprintf(testname, sizeof(testname), "%s/ipptool/%s", cg->cups_datadir, argv[i]);
         if (access(testname, 0))
           testfile = argv[i];
         else
@@ -660,7 +664,7 @@ main(int  argc,                             /* I - Number of command-line args */
       else
         testfile = argv[i];
 
-      if (!do_tests(outfile, &vars, testfile))
+      if (!do_tests(testfile, &vars, &data))
         status = 1;
     }
   }
@@ -672,14 +676,14 @@ main(int  argc,                           /* I - Number of command-line args */
   * Loop if the interval is set...
   */
 
-  if (Output == _CUPS_OUTPUT_PLIST)
-    print_xml_trailer(outfile, !status, NULL);
+  if (data.output == _CUPS_OUTPUT_PLIST)
+    print_xml_trailer(&data, !status, NULL);
   else if (interval > 0 && repeat > 0)
   {
     while (repeat > 1)
     {
       usleep((useconds_t)interval);
-      do_tests(outfile, &vars, testfile);
+      do_tests(testfile, &vars, &data);
       repeat --;
     }
   }
@@ -688,22 +692,22 @@ main(int  argc,                           /* I - Number of command-line args */
     for (;;)
     {
       usleep((useconds_t)interval);
-      do_tests(outfile, &vars, testfile);
+      do_tests(testfile, &vars, &data);
     }
   }
 
-  if ((Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile)) && TestCount > 1)
+  if ((data.output == _CUPS_OUTPUT_TEST || (data.output == _CUPS_OUTPUT_PLIST && data.outfile)) && data.test_count > 1)
   {
    /*
     * Show a summary report if there were multiple tests...
     */
 
-    printf("\nSummary: %d tests, %d passed, %d failed, %d skipped\n"
-           "Score: %d%%\n", TestCount, PassCount, FailCount, SkipCount,
-           100 * (PassCount + SkipCount) / TestCount);
+    cupsFilePrintf(cupsFileStdout(), "\nSummary: %d tests, %d passed, %d failed, %d skipped\nScore: %d%%\n", data.test_count, data.pass_count, data.fail_count, data.skip_count, 100 * (data.pass_count + data.skip_count) / data.test_count);
   }
 
- /*
+  cupsFileClose(data.outfile);
+
+/*
   * Exit...
   */
 
@@ -748,4861 +752,3540 @@ add_stringf(cups_array_t *a,          /* I - Array */
 
 
 /*
- * 'compare_vars()' - Compare two variables.
+ * 'compare_uris()' - Compare two URIs...
  */
 
-static int                             /* O - Result of comparison */
-compare_vars(_cups_var_t *a,           /* I - First variable */
-             _cups_var_t *b)           /* I - Second variable */
+static int                              /* O - Result of comparison */
+compare_uris(const char *a,             /* I - First URI */
+             const char *b)             /* I - Second URI */
 {
-  return (_cups_strcasecmp(a->name, b->name));
+  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));
 }
 
 
 /*
- * 'do_tests()' - Do tests as specified in the test file.
+ * 'do_test()' - Do a single test from the test file.
  */
 
-static int                             /* 1 = success, 0 = failure */
-do_tests(FILE         *outfile,                /* I - Output file */
-         _cups_vars_t *vars,           /* I - Variables */
-         const char   *testfile)       /* I - Test file to use */
+static int                             /* O - 1 on success, 0 on failure */
+do_test(_ipp_file_t      *f,           /* I - IPP data file */
+        _ipp_vars_t      *vars,                /* I - IPP variables */
+        _cups_testdata_t *data)                /* I - Test data */
+
 {
-  int          i,                      /* Looping var */
-               linenum,                /* Current line number */
-               pass,                   /* Did we pass the test? */
-               prev_pass = 1,          /* Did we pass the previous test? */
-               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? */
-               repeat_count,           /* Repeat count */
-               repeat_interval,        /* Repeat interval */
-               repeat_prev,            /* Previous repeat interval */
-               repeat_test;            /* Repeat a test? */
-  http_t       *http = NULL;           /* HTTP connection to server */
-  FILE         *fp = NULL;             /* Test file */
-  char         resource[512],          /* Resource for request */
-               token[1024],            /* Token from file */
-               *tokenptr,              /* Pointer into token */
-               temp[1024],             /* Temporary string */
-               buffer[8192],           /* Copy buffer */
-               compression[16];        /* COMPRESSION value */
-  ipp_t                *request = NULL,        /* IPP request */
-               *response = NULL;       /* IPP response */
+  int          i,                      /* Looping var */
+               status_ok,              /* Did we get a matching status? */
+               repeat_count = 0,       /* Repeat count */
+               repeat_test;            /* Repeat the test? */
+  _cups_expect_t *expect;              /* Current expected attribute */
+  ipp_t                *request,               /* IPP request */
+               *response;              /* 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 */
+  cups_array_t *a;                     /* Duplicate attribute array */
   ipp_tag_t    group;                  /* Current group */
-  ipp_tag_t    value;                  /* Current value type */
   ipp_attribute_t *attrptr,            /* Attribute pointer */
-               *found,                 /* Found attribute */
-               *lastcol = NULL;        /* Last collection attribute */
-  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 */
-               skip_test;              /* Skip this test? */
-  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 */
-  _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 */
+               *found;                 /* Found attribute */
+  char         temp[1024];             /* Temporary string */
+  cups_file_t  *reqfile;               /* File to send */
+  ssize_t      bytes;                  /* Bytes read/written */
+  char         buffer[131072];         /* Copy buffer */
   size_t       widths[200];            /* Width of columns */
-  cups_array_t *a,                     /* Duplicate attribute array */
-               *errors = NULL;         /* Errors array */
   const char   *error;                 /* Current error */
 
 
+  if (Cancel)
+    return (0);
+
  /*
-  * Open the test file...
+  * Take over control of the attributes in the request...
   */
 
-  if ((fp = fopen(testfile, "r")) == NULL)
-  {
-    print_fatal_error(outfile, "Unable to open test file %s - %s", testfile,
-                      strerror(errno));
-    pass = 0;
-    goto test_exit;
-  }
+  request  = f->attrs;
+  f->attrs = NULL;
 
  /*
-  * Connect to the server...
+  * Submit the IPP request...
   */
 
-  if ((http = httpConnect2(vars->hostname, vars->port, NULL, vars->family,
-                           vars->encryption, 1, 30000, NULL)) == NULL)
+  data->test_count ++;
+
+  ippSetVersion(request, data->version / 10, data->version % 10);
+  ippSetRequestId(request, data->request_id);
+
+  if (data->output == _CUPS_OUTPUT_PLIST)
   {
-    print_fatal_error(outfile, "Unable to connect to %s on port %d - %s", vars->hostname,
-                      vars->port, cupsLastErrorString());
-    pass = 0;
-    goto test_exit;
+    cupsFilePuts(data->outfile, "<dict>\n");
+    cupsFilePuts(data->outfile, "<key>Name</key>\n");
+    print_xml_string(data->outfile, "string", data->name);
+    if (data->file_id[0])
+    {
+      cupsFilePuts(data->outfile, "<key>FileId</key>\n");
+      print_xml_string(data->outfile, "string", data->file_id);
+    }
+    if (data->test_id[0])
+    {
+      cupsFilePuts(data->outfile, "<key>TestId</key>\n");
+      print_xml_string(data->outfile, "string", data->test_id);
+    }
+    cupsFilePuts(data->outfile, "<key>Version</key>\n");
+    cupsFilePrintf(data->outfile, "<string>%d.%d</string>\n", data->version / 10, data->version % 10);
+    cupsFilePuts(data->outfile, "<key>Operation</key>\n");
+    print_xml_string(data->outfile, "string", ippOpString(ippGetOperation(request)));
+    cupsFilePuts(data->outfile, "<key>RequestId</key>\n");
+    cupsFilePrintf(data->outfile, "<integer>%d</integer>\n", data->request_id);
+    cupsFilePuts(data->outfile, "<key>RequestAttributes</key>\n");
+    cupsFilePuts(data->outfile, "<array>\n");
+    if (ippFirstAttribute(request))
+    {
+      cupsFilePuts(data->outfile, "<dict>\n");
+      for (attrptr = ippFirstAttribute(request), group = ippGetGroupTag(attrptr); attrptr; attrptr = ippNextAttribute(request))
+       print_attr(data->outfile, data->output, attrptr, &group);
+      cupsFilePuts(data->outfile, "</dict>\n");
+    }
+    cupsFilePuts(data->outfile, "</array>\n");
   }
 
-#ifdef HAVE_LIBZ
-  httpSetDefaultField(http, HTTP_FIELD_ACCEPT_ENCODING,
-                      "deflate, gzip, identity");
-#else
-  httpSetDefaultField(http, HTTP_FIELD_ACCEPT_ENCODING, "identity");
-#endif /* HAVE_LIBZ */
+  if (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
+  {
+    if (data->verbosity)
+    {
+      cupsFilePrintf(cupsFileStdout(), "    %s:\n", ippOpString(ippGetOperation(request)));
 
-  if (vars->timeout > 0.0)
-    httpSetTimeout(http, vars->timeout, timeout_cb, NULL);
+      for (attrptr = ippFirstAttribute(request); attrptr; attrptr = ippNextAttribute(request))
+       print_attr(cupsFileStdout(), _CUPS_OUTPUT_TEST, attrptr, NULL);
+    }
 
- /*
-  * Loop on tests...
-  */
+    cupsFilePrintf(cupsFileStdout(), "    %-68.68s [", data->name);
+  }
 
-  CUPS_SRAND(time(NULL));
+  if ((data->skip_previous && !data->prev_pass) || data->skip_test)
+  {
+    data->skip_count ++;
+
+    ippDelete(request);
+    request  = NULL;
+    response = NULL;
+
+    if (data->output == _CUPS_OUTPUT_PLIST)
+    {
+      cupsFilePuts(data->outfile, "<key>Successful</key>\n");
+      cupsFilePuts(data->outfile, "<true />\n");
+      cupsFilePuts(data->outfile, "<key>Skipped</key>\n");
+      cupsFilePuts(data->outfile, "<true />\n");
+      cupsFilePuts(data->outfile, "<key>StatusCode</key>\n");
+      print_xml_string(data->outfile, "string", "skip");
+      cupsFilePuts(data->outfile, "<key>ResponseAttributes</key>\n");
+      cupsFilePuts(data->outfile, "<dict />\n");
+    }
+
+    if (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
+      cupsFilePuts(cupsFileStdout(), "SKIP]\n");
 
-  errors     = cupsArrayNew3(NULL, NULL, NULL, 0, (cups_acopy_func_t)strdup,
-                             (cups_afree_func_t)free);
-  file_id[0] = '\0';
-  pass       = 1;
-  linenum    = 1;
-  request_id = (CUPS_RAND() % 1000) * 137 + 1;
+    goto skip_error;
+  }
+
+  vars->password_tries = 0;
 
-  while (!Cancel && get_token(fp, token, sizeof(token), &linenum) != NULL)
+  do
   {
-   /*
-    * Expect an open brace...
-    */
+    if (data->delay > 0)
+      usleep(data->delay);
 
-    if (!strcmp(token, "DEFINE"))
+    data->delay = data->repeat_interval;
+    repeat_count ++;
+
+    status = HTTP_STATUS_OK;
+
+    if (data->transfer == _CUPS_TRANSFER_CHUNKED || (data->transfer == _CUPS_TRANSFER_AUTO && data->file[0]))
     {
      /*
-      * DEFINE name value
+      * Send request using chunking - a 0 length means "chunk".
       */
 
-      if (get_token(fp, attr, sizeof(attr), &linenum) &&
-          get_token(fp, temp, sizeof(temp), &linenum))
-      {
-        expand_variables(vars, token, temp, sizeof(token));
-       set_variable(outfile, vars, attr, token);
-      }
-      else
-      {
-        print_fatal_error(outfile, "Missing DEFINE name and/or value on line %d.",
-                         linenum);
-       pass = 0;
-       goto test_exit;
-      }
-
-      continue;
+      length = 0;
     }
-    else if (!strcmp(token, "DEFINE-DEFAULT"))
+    else
     {
      /*
-      * DEFINE-DEFAULT name value
+      * Send request using content length...
       */
 
-      if (get_token(fp, attr, sizeof(attr), &linenum) &&
-          get_token(fp, temp, sizeof(temp), &linenum))
+      length = ippLength(request);
+
+      if (data->file[0] && (reqfile = cupsFileOpen(data->file, "r")) != NULL)
       {
-        expand_variables(vars, token, temp, sizeof(token));
-       if (!get_variable(vars, attr))
-         set_variable(outfile, vars, attr, token);
+       /*
+       * Read the file to get the uncompressed file size...
+       */
+
+       while ((bytes = cupsFileRead(reqfile, buffer, sizeof(buffer))) > 0)
+         length += (size_t)bytes;
+
+       cupsFileClose(reqfile);
       }
-      else
+    }
+
+   /*
+    * Send the request...
+    */
+
+    data->prev_pass = 1;
+    repeat_test     = 0;
+    response        = NULL;
+
+    if (status != HTTP_STATUS_ERROR)
+    {
+      while (!response && !Cancel && data->prev_pass)
       {
-        print_fatal_error(outfile, "Missing DEFINE-DEFAULT name and/or value on line "
-                         "%d.", linenum);
-       pass = 0;
-       goto test_exit;
+       status = cupsSendRequest(data->http, request, data->resource, length);
+
+#ifdef HAVE_LIBZ
+       if (data->compression[0])
+         httpSetField(data->http, HTTP_FIELD_CONTENT_ENCODING, data->compression);
+#endif /* HAVE_LIBZ */
+
+       if (!Cancel && status == HTTP_STATUS_CONTINUE && ippGetState(request) == IPP_DATA && data->file[0])
+       {
+         if ((reqfile = cupsFileOpen(data->file, "r")) != NULL)
+         {
+           while (!Cancel && (bytes = cupsFileRead(reqfile, buffer, sizeof(buffer))) > 0)
+           {
+             if ((status = cupsWriteRequestData(data->http, buffer, (size_t)bytes)) != HTTP_STATUS_CONTINUE)
+               break;
+            }
+
+           cupsFileClose(reqfile);
+         }
+         else
+         {
+           snprintf(buffer, sizeof(buffer), "%s: %s", data->file, strerror(errno));
+           _cupsSetError(IPP_INTERNAL_ERROR, buffer, 0);
+
+           status = HTTP_STATUS_ERROR;
+         }
+       }
+
+       /*
+       * Get the server's response...
+       */
+
+       if (!Cancel && status != HTTP_STATUS_ERROR)
+       {
+         response = cupsGetResponse(data->http, data->resource);
+         status   = httpGetStatus(data->http);
+       }
+
+       if (!Cancel && status == HTTP_STATUS_ERROR && httpError(data->http) != EINVAL &&
+#ifdef WIN32
+           httpError(data->http) != WSAETIMEDOUT)
+#else
+           httpError(data->http) != ETIMEDOUT)
+#endif /* WIN32 */
+       {
+         if (httpReconnect2(data->http, 30000, NULL))
+           data->prev_pass = 0;
+       }
+       else if (status == HTTP_STATUS_ERROR || status == HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED)
+       {
+         data->prev_pass = 0;
+         break;
+       }
+       else if (status != HTTP_STATUS_OK)
+       {
+         httpFlush(data->http);
+
+         if (status == HTTP_STATUS_UNAUTHORIZED)
+           continue;
+
+         break;
+       }
       }
+    }
 
-      continue;
+    if (!Cancel && status == HTTP_STATUS_ERROR && httpError(data->http) != EINVAL &&
+#ifdef WIN32
+       httpError(data->http) != WSAETIMEDOUT)
+#else
+       httpError(data->http) != ETIMEDOUT)
+#endif /* WIN32 */
+    {
+      if (httpReconnect2(data->http, 30000, NULL))
+       data->prev_pass = 0;
     }
-    else if (!strcmp(token, "FILE-ID"))
+    else if (status == HTTP_STATUS_ERROR)
     {
-     /*
-      * FILE-ID "string"
-      */
+      if (!Cancel)
+       httpReconnect2(data->http, 30000, NULL);
 
-      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;
-      }
+      data->prev_pass = 0;
+    }
+    else if (status != HTTP_STATUS_OK)
+    {
+      httpFlush(data->http);
+      data->prev_pass = 0;
+    }
+
+   /*
+    * Check results of request...
+    */
+
+    cupsArrayClear(data->errors);
+
+    if (httpGetVersion(data->http) != HTTP_1_1)
+    {
+      int version = httpGetVersion(data->http);
 
-      continue;
+      add_stringf(data->errors, "Bad HTTP version (%d.%d)", version / 100, version % 100);
     }
-    else if (!strcmp(token, "IGNORE-ERRORS"))
+
+    if (data->validate_headers)
+    {
+      const char *header;               /* HTTP header value */
+
+      if ((header = httpGetField(data->http, HTTP_FIELD_CONTENT_TYPE)) == NULL || _cups_strcasecmp(header, "application/ipp"))
+       add_stringf(data->errors, "Bad HTTP Content-Type in response (%s)", header && *header ? header : "<missing>");
+
+      if ((header = httpGetField(data->http, HTTP_FIELD_DATE)) != NULL && *header && httpGetDateTime(header) == 0)
+       add_stringf(data->errors, "Bad HTTP Date in response (%s)", header);
+    }
+
+    if (!response)
     {
      /*
-      * IGNORE-ERRORS yes
-      * IGNORE-ERRORS no
+      * No response, log error...
       */
 
-      if (get_token(fp, temp, sizeof(temp), &linenum) &&
-          (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
-      {
-        IgnoreErrors = !_cups_strcasecmp(temp, "yes");
-      }
-      else
-      {
-        print_fatal_error(outfile, "Missing IGNORE-ERRORS value on line %d.", linenum);
-       pass = 0;
-       goto test_exit;
-      }
-
-      continue;
+      add_stringf(data->errors, "IPP request failed with status %s (%s)", ippErrorString(cupsLastError()), cupsLastErrorString());
     }
-    else if (!strcmp(token, "INCLUDE"))
+    else
     {
      /*
-      * INCLUDE "filename"
-      * INCLUDE <filename>
+      * Collect common attribute values...
       */
 
-      if (get_token(fp, temp, sizeof(temp), &linenum))
+      if ((attrptr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) != NULL)
       {
-       /*
-        * Map the filename to and then run the tests...
-       */
+       snprintf(temp, sizeof(temp), "%d", ippGetInteger(attrptr, 0));
+       _ippVarsSet(vars, "job-id", temp);
+      }
 
-        if (!do_tests(outfile, vars, get_filename(testfile, filename, temp, sizeof(filename))))
-       {
-         pass = 0;
+      if ((attrptr = ippFindAttribute(response, "job-uri", IPP_TAG_URI)) != NULL)
+       _ippVarsSet(vars, "job-uri", ippGetString(attrptr, 0, NULL));
 
-         if (StopAfterIncludeError)
-           goto test_exit;
-       }
-      }
-      else
+      if ((attrptr = ippFindAttribute(response, "notify-subscription-id", IPP_TAG_INTEGER)) != NULL)
       {
-        print_fatal_error(outfile, "Missing INCLUDE filename on line %d.", linenum);
-       pass = 0;
-       goto test_exit;
+       snprintf(temp, sizeof(temp), "%d", ippGetInteger(attrptr, 0));
+       _ippVarsSet(vars, "notify-subscription-id", temp);
       }
 
-      show_header = 1;
-      continue;
-    }
-    else if (!strcmp(token, "INCLUDE-IF-DEFINED"))
-    {
      /*
-      * INCLUDE-IF-DEFINED name "filename"
-      * INCLUDE-IF-DEFINED name <filename>
+      * Check response, validating groups and attributes and logging errors
+      * as needed...
       */
 
-      if (get_token(fp, attr, sizeof(attr), &linenum) &&
-          get_token(fp, temp, sizeof(temp), &linenum))
+      if (ippGetState(response) != IPP_DATA)
+       add_stringf(data->errors, "Missing end-of-attributes-tag in response (RFC 2910 section 3.5.1)");
+
+      if (data->version)
       {
-       /*
-        * Map the filename to and then run the tests...
-       */
+        int major, minor;              /* IPP version */
 
-        if (get_variable(vars, attr) &&
-           !do_tests(outfile, vars, get_filename(testfile, filename, temp, sizeof(filename))))
-       {
-         pass = 0;
+        major = ippGetVersion(response, &minor);
 
-         if (StopAfterIncludeError)
-           goto test_exit;
-       }
+        if (major != (data->version / 10) || minor != (data->version % 10))
+         add_stringf(data->errors, "Bad version %d.%d in response - expected %d.%d (RFC 2911 section 3.1.8).", major, minor, data->version / 10, data->version % 10);
+      }
+
+      if (ippGetRequestId(response) != data->request_id)
+       add_stringf(data->errors, "Bad request ID %d in response - expected %d (RFC 2911 section 3.1.1)", ippGetRequestId(response), data->request_id);
+
+      attrptr = ippFirstAttribute(response);
+      if (!attrptr)
+      {
+       add_stringf(data->errors, "Missing first attribute \"attributes-charset (charset)\" in group operation-attributes-tag (RFC 2911 section 3.1.4).");
       }
       else
       {
-        print_fatal_error(outfile, "Missing INCLUDE-IF-DEFINED name or filename on line "
-                         "%d.", linenum);
-       pass = 0;
-       goto test_exit;
+       if (!ippGetName(attrptr) || ippGetValueTag(attrptr) != IPP_TAG_CHARSET || ippGetGroupTag(attrptr) != IPP_TAG_OPERATION || ippGetCount(attrptr) != 1 ||strcmp(ippGetName(attrptr), "attributes-charset"))
+         add_stringf(data->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).", ippGetName(attrptr) ? ippGetName(attrptr) : "(null)", ippGetCount(attrptr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attrptr)), ippTagString(ippGetGroupTag(attrptr)));
+
+       attrptr = ippNextAttribute(response);
+       if (!attrptr)
+         add_stringf(data->errors, "Missing second attribute \"attributes-natural-language (naturalLanguage)\" in group operation-attributes-tag (RFC 2911 section 3.1.4).");
+       else if (!ippGetName(attrptr) || ippGetValueTag(attrptr) != IPP_TAG_LANGUAGE || ippGetGroupTag(attrptr) != IPP_TAG_OPERATION || ippGetCount(attrptr) != 1 || strcmp(ippGetName(attrptr), "attributes-natural-language"))
+         add_stringf(data->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).", ippGetName(attrptr) ? ippGetName(attrptr) : "(null)", ippGetCount(attrptr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attrptr)), ippTagString(ippGetGroupTag(attrptr)));
       }
 
-      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 (StopAfterIncludeError)
-           goto test_exit;
-       }
-      }
-      else
-      {
-        print_fatal_error(outfile, "Missing INCLUDE-IF-NOT-DEFINED name or filename on "
-                         "line %d.", linenum);
-       pass = 0;
-       goto test_exit;
-      }
-
-      show_header = 1;
-      continue;
-    }
-    else if (!strcmp(token, "SKIP-IF-DEFINED"))
-    {
-     /*
-      * SKIP-IF-DEFINED variable
-      */
-
-      if (get_token(fp, temp, sizeof(temp), &linenum))
-      {
-        if (get_variable(vars, temp))
-         goto test_exit;
-      }
-      else
-      {
-        print_fatal_error(outfile, "Missing SKIP-IF-DEFINED variable on line %d.",
-                         linenum);
-       pass = 0;
-       goto test_exit;
-      }
-    }
-    else if (!strcmp(token, "SKIP-IF-NOT-DEFINED"))
-    {
-     /*
-      * SKIP-IF-NOT-DEFINED variable
-      */
-
-      if (get_token(fp, temp, sizeof(temp), &linenum))
-      {
-        if (!get_variable(vars, temp))
-         goto test_exit;
-      }
-      else
+      if ((attrptr = ippFindAttribute(response, "status-message", IPP_TAG_ZERO)) != NULL)
       {
-        print_fatal_error(outfile, "Missing SKIP-IF-NOT-DEFINED variable on line %d.",
-                         linenum);
-       pass = 0;
-       goto test_exit;
+        const char *status_message = ippGetString(attrptr, 0, NULL);
+                                               /* String value */
+
+       if (ippGetValueTag(attrptr) != IPP_TAG_TEXT)
+         add_stringf(data->errors, "status-message (text(255)) has wrong value tag %s (RFC 2911 section 3.1.6.2).", ippTagString(ippGetValueTag(attrptr)));
+       if (ippGetGroupTag(attrptr) != IPP_TAG_OPERATION)
+         add_stringf(data->errors, "status-message (text(255)) has wrong group tag %s (RFC 2911 section 3.1.6.2).", ippTagString(ippGetGroupTag(attrptr)));
+       if (ippGetCount(attrptr) != 1)
+         add_stringf(data->errors, "status-message (text(255)) has %d values (RFC 2911 section 3.1.6.2).", ippGetCount(attrptr));
+       if (status_message && strlen(status_message) > 255)
+         add_stringf(data->errors, "status-message (text(255)) has bad length %d (RFC 2911 section 3.1.6.2).", (int)strlen(status_message));
       }
-    }
-    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
+      if ((attrptr = ippFindAttribute(response, "detailed-status-message",
+                                      IPP_TAG_ZERO)) != NULL)
       {
-        print_fatal_error(outfile, "Missing STOP-AFTER-INCLUDE-ERROR value on line %d.",
-                          linenum);
-       pass = 0;
-       goto test_exit;
+        const char *detailed_status_message = ippGetString(attrptr, 0, NULL);
+                                               /* String value */
+
+       if (ippGetValueTag(attrptr) != IPP_TAG_TEXT)
+         add_stringf(data->errors,
+                     "detailed-status-message (text(MAX)) has wrong "
+                     "value tag %s (RFC 2911 section 3.1.6.3).",
+                     ippTagString(ippGetValueTag(attrptr)));
+       if (ippGetGroupTag(attrptr) != IPP_TAG_OPERATION)
+         add_stringf(data->errors,
+                     "detailed-status-message (text(MAX)) has wrong "
+                     "group tag %s (RFC 2911 section 3.1.6.3).",
+                     ippTagString(ippGetGroupTag(attrptr)));
+       if (ippGetCount(attrptr) != 1)
+         add_stringf(data->errors,
+                     "detailed-status-message (text(MAX)) has %d values"
+                     " (RFC 2911 section 3.1.6.3).",
+                     ippGetCount(attrptr));
+       if (detailed_status_message && strlen(detailed_status_message) > 1023)
+         add_stringf(data->errors,
+                     "detailed-status-message (text(MAX)) has bad "
+                     "length %d (RFC 2911 section 3.1.6.3).",
+                     (int)strlen(detailed_status_message));
       }
 
-      continue;
-    }
-    else if (!strcmp(token, "TRANSFER"))
-    {
-     /*
-      * TRANSFER auto
-      * TRANSFER chunked
-      * TRANSFER length
-      */
-
-      if (get_token(fp, temp, sizeof(temp), &linenum))
-      {
-        if (!strcmp(temp, "auto"))
-         Transfer = _CUPS_TRANSFER_AUTO;
-       else if (!strcmp(temp, "chunked"))
-         Transfer = _CUPS_TRANSFER_CHUNKED;
-       else if (!strcmp(temp, "length"))
-         Transfer = _CUPS_TRANSFER_LENGTH;
-       else
-       {
-         print_fatal_error(outfile, "Bad TRANSFER value \"%s\" on line %d.", temp,
-                           linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else
-      {
-        print_fatal_error(outfile, "Missing TRANSFER value on line %d.", linenum);
-       pass = 0;
-       goto test_exit;
-      }
+      a = cupsArrayNew((cups_array_func_t)strcmp, NULL);
 
-      continue;
-    }
-    else if (!strcmp(token, "VERSION"))
-    {
-      if (get_token(fp, temp, sizeof(temp), &linenum))
+      for (attrptr = ippFirstAttribute(response), group = ippGetGroupTag(attrptr);
+          attrptr;
+          attrptr = ippNextAttribute(response))
       {
-        if (!strcmp(temp, "1.0"))
-         Version = 10;
-       else if (!strcmp(temp, "1.1"))
-         Version = 11;
-       else if (!strcmp(temp, "2.0"))
-         Version = 20;
-       else if (!strcmp(temp, "2.1"))
-         Version = 21;
-       else if (!strcmp(temp, "2.2"))
-         Version = 22;
-       else
+       if (ippGetGroupTag(attrptr) != group)
        {
-         print_fatal_error(outfile, "Bad VERSION \"%s\" on line %d.", temp, linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else
-      {
-        print_fatal_error(outfile, "Missing VERSION number on line %d.", linenum);
-       pass = 0;
-       goto test_exit;
-      }
-
-      continue;
-    }
-    else if (strcmp(token, "{"))
-    {
-      print_fatal_error(outfile, "Unexpected token %s seen on line %d.", token, linenum);
-      pass = 0;
-      goto test_exit;
-    }
+         int out_of_order = 0; /* Are attribute groups out-of-order? */
+         cupsArrayClear(a);
 
-   /*
-    * Initialize things...
-    */
+         switch (ippGetGroupTag(attrptr))
+         {
+           case IPP_TAG_ZERO :
+               break;
 
-    if (show_header)
-    {
-      if (Output == _CUPS_OUTPUT_PLIST)
-       print_xml_header(outfile);
-      if (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != stdout))
-       printf("\"%s\":\n", testfile);
+           case IPP_TAG_OPERATION :
+               out_of_order = 1;
+               break;
 
-      show_header = 0;
-    }
+           case IPP_TAG_UNSUPPORTED_GROUP :
+               if (group != IPP_TAG_OPERATION)
+                 out_of_order = 1;
+               break;
 
-    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_previous  = 0;
-    skip_test      = 0;
-    test_id[0]     = '\0';
-    version        = Version;
-    transfer       = Transfer;
-    compression[0] = '\0';
-
-    strlcpy(name, testfile, sizeof(name));
-    if (strrchr(name, '.') != NULL)
-      *strrchr(name, '.') = '\0';
+           case IPP_TAG_JOB :
+           case IPP_TAG_PRINTER :
+               if (group != IPP_TAG_OPERATION && group != IPP_TAG_UNSUPPORTED_GROUP)
+                 out_of_order = 1;
+               break;
 
-   /*
-    * Parse until we see a close brace...
-    */
+           case IPP_TAG_SUBSCRIPTION :
+               if (group > ippGetGroupTag(attrptr) && group != IPP_TAG_DOCUMENT)
+                 out_of_order = 1;
+               break;
 
-    while (get_token(fp, token, sizeof(token), &linenum) != NULL)
-    {
-      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 (_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, "}"))
-        break;
-      else if (!strcmp(token, "{") && lastcol)
-      {
-       /*
-       * Another collection value
-       */
+           default :
+               if (group > ippGetGroupTag(attrptr))
+                 out_of_order = 1;
+               break;
+         }
 
-       ipp_t   *col = get_collection(outfile, vars, fp, &linenum);
-                                       /* Collection value */
+         if (out_of_order)
+           add_stringf(data->errors, "Attribute groups out of order (%s < %s)",
+                       ippTagString(ippGetGroupTag(attrptr)),
+                       ippTagString(group));
 
-       if (col)
-       {
-          ippSetCollection(request, &lastcol, ippGetCount(lastcol), col);
-        }
-       else
-       {
-         pass = 0;
-         goto test_exit;
+         if (ippGetGroupTag(attrptr) != IPP_TAG_ZERO)
+           group = ippGetGroupTag(attrptr);
        }
-      }
-      else if (!strcmp(token, "COMPRESSION"))
-      {
-       /*
-       * COMPRESSION none
-       * COMPRESSION deflate
-       * COMPRESSION gzip
-       */
 
-       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 (!ippValidateAttribute(attrptr))
+         cupsArrayAdd(data->errors, (void *)cupsLastErrorString());
 
-          if (!strcmp(compression, "none"))
-            compression[0] = '\0';
-       }
-       else
+       if (ippGetName(attrptr))
        {
-         print_fatal_error(outfile, "Missing COMPRESSION value on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else if (!strcmp(token, "DEFINE"))
-      {
-       /*
-       * DEFINE name value
-       */
+         if (cupsArrayFind(a, (void *)ippGetName(attrptr)) && data->output < _CUPS_OUTPUT_LIST)
+           add_stringf(data->errors, "Duplicate \"%s\" attribute in %s group",
+                       ippGetName(attrptr), ippTagString(group));
 
-       if (get_token(fp, attr, sizeof(attr), &linenum) &&
-           get_token(fp, temp, sizeof(temp), &linenum))
-       {
-         expand_variables(vars, token, temp, sizeof(token));
-         set_variable(outfile, vars, attr, token);
-       }
-       else
-       {
-         print_fatal_error(outfile, "Missing DEFINE name and/or value on line %d.",
-                           linenum);
-         pass = 0;
-         goto test_exit;
+         cupsArrayAdd(a, (void *)ippGetName(attrptr));
        }
       }
-      else if (!strcmp(token, "IGNORE-ERRORS"))
-      {
-       /*
-       * IGNORE-ERRORS yes
-       * IGNORE-ERRORS no
-       */
-
-       if (get_token(fp, temp, sizeof(temp), &linenum) &&
-           (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
-       {
-         ignore_errors = !_cups_strcasecmp(temp, "yes");
-       }
-       else
-       {
-         print_fatal_error(outfile, "Missing IGNORE-ERRORS value on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
 
-       continue;
-      }
-      else if (!_cups_strcasecmp(token, "NAME"))
-      {
-       /*
-        * Name of test...
-       */
+      cupsArrayDelete(a);
 
-       get_token(fp, temp, sizeof(temp), &linenum);
-       expand_variables(vars, name, temp, sizeof(name));
-      }
-      else if (!_cups_strcasecmp(token, "PAUSE"))
-      {
-       /*
-        * Pause with a message...
-       */
+     /*
+      * Now check the test-defined expected status-code and attribute
+      * values...
+      */
 
-       get_token(fp, token, sizeof(token), &linenum);
-       pause_message(token);
-      }
-      else if (!strcmp(token, "REQUEST-ID"))
+      for (i = 0, status_ok = 0; i < data->num_statuses; i ++)
       {
-       /*
-       * REQUEST-ID #
-       * REQUEST-ID random
-       */
+       if (data->statuses[i].if_defined &&
+           !_ippVarsGet(vars, data->statuses[i].if_defined))
+         continue;
 
-       if (get_token(fp, temp, sizeof(temp), &linenum))
-       {
-         if (isdigit(temp[0] & 255))
-           request_id = atoi(temp);
-         else if (!_cups_strcasecmp(temp, "random"))
-           request_id = (CUPS_RAND() % 1000) * 137 + 1;
-         else
-         {
-           print_fatal_error(outfile, "Bad REQUEST-ID value \"%s\" on line %d.", temp,
-                             linenum);
-           pass = 0;
-           goto test_exit;
-         }
-       }
-       else
-       {
-         print_fatal_error(outfile, "Missing REQUEST-ID value on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else if (!strcmp(token, "SKIP-IF-DEFINED"))
-      {
-       /*
-       * SKIP-IF-DEFINED variable
-       */
+       if (data->statuses[i].if_not_defined &&
+           _ippVarsGet(vars, data->statuses[i].if_not_defined))
+         continue;
 
-       if (get_token(fp, temp, sizeof(temp), &linenum))
-       {
-         if (get_variable(vars, temp))
-           skip_test = 1;
-       }
-       else
+       if (ippGetStatusCode(response) == data->statuses[i].status)
        {
-         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
-       */
+         status_ok = 1;
 
-       if (get_token(fp, temp, sizeof(temp), &linenum))
-       {
-         expand_variables(vars, token, temp, sizeof(token));
-         get_filename(testfile, filename, token, sizeof(filename));
+         if (data->statuses[i].repeat_match && repeat_count < data->statuses[i].repeat_limit)
+           repeat_test = 1;
 
-         if (access(filename, R_OK))
-           skip_test = 1;
+         if (data->statuses[i].define_match)
+           _ippVarsSet(vars, data->statuses[i].define_match, "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"))
-      {
-       /*
-       * SKIP-IF-NOT-DEFINED variable
-       */
+         if (data->statuses[i].repeat_no_match && repeat_count < data->statuses[i].repeat_limit)
+           repeat_test = 1;
 
-       if (get_token(fp, temp, sizeof(temp), &linenum))
-       {
-         if (!get_variable(vars, temp))
-           skip_test = 1;
-       }
-       else
-       {
-         print_fatal_error(outfile, "Missing SKIP-IF-NOT-DEFINED value on line %d.",
-                           linenum);
-         pass = 0;
-         goto test_exit;
+         if (data->statuses[i].define_no_match)
+         {
+           _ippVarsSet(vars, data->statuses[i].define_no_match, "1");
+           status_ok = 1;
+         }
        }
       }
-      else if (!strcmp(token, "SKIP-PREVIOUS-ERROR"))
-      {
-       /*
-       * SKIP-PREVIOUS-ERROR yes
-       * SKIP-PREVIOUS-ERROR no
-       */
 
-       if (get_token(fp, temp, sizeof(temp), &linenum) &&
-           (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
-       {
-         skip_previous = !_cups_strcasecmp(temp, "yes");
-       }
-       else
+      if (!status_ok && data->num_statuses > 0)
+      {
+       for (i = 0; i < data->num_statuses; i ++)
        {
-         print_fatal_error(outfile, "Missing SKIP-PREVIOUS-ERROR value on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+         if (data->statuses[i].if_defined &&
+             !_ippVarsGet(vars, data->statuses[i].if_defined))
+           continue;
 
-       continue;
-      }
-      else if (!strcmp(token, "TEST-ID"))
-      {
-       /*
-       * TEST-ID "string"
-       */
+         if (data->statuses[i].if_not_defined &&
+             _ippVarsGet(vars, data->statuses[i].if_not_defined))
+           continue;
 
-       if (get_token(fp, temp, sizeof(temp), &linenum))
-       {
-         expand_variables(vars, test_id, temp, sizeof(test_id));
-       }
-       else
-       {
-         print_fatal_error(outfile, "Missing TEST-ID value on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
+         if (!data->statuses[i].repeat_match || repeat_count >= data->statuses[i].repeat_limit)
+           add_stringf(data->errors, "EXPECTED: STATUS %s (got %s)",
+                       ippErrorString(data->statuses[i].status),
+                       ippErrorString(cupsLastError()));
        }
 
-       continue;
+       if ((attrptr = ippFindAttribute(response, "status-message",
+                                       IPP_TAG_TEXT)) != NULL)
+         add_stringf(data->errors, "status-message=\"%s\"", ippGetString(attrptr, 0, NULL));
       }
-      else if (!strcmp(token, "TRANSFER"))
-      {
-       /*
-       * TRANSFER auto
-       * TRANSFER chunked
-       * TRANSFER length
-       */
 
-       if (get_token(fp, temp, sizeof(temp), &linenum))
-       {
-         if (!strcmp(temp, "auto"))
-           transfer = _CUPS_TRANSFER_AUTO;
-         else if (!strcmp(temp, "chunked"))
-           transfer = _CUPS_TRANSFER_CHUNKED;
-         else if (!strcmp(temp, "length"))
-           transfer = _CUPS_TRANSFER_LENGTH;
-         else
-         {
-           print_fatal_error(outfile, "Bad TRANSFER value \"%s\" on line %d.", temp,
-                             linenum);
-           pass = 0;
-           goto test_exit;
-         }
-       }
-       else
-       {
-         print_fatal_error(outfile, "Missing TRANSFER value on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else if (!_cups_strcasecmp(token, "VERSION"))
-      {
-       if (get_token(fp, temp, sizeof(temp), &linenum))
-       {
-         if (!strcmp(temp, "0.0"))
-           version = 0;
-         else if (!strcmp(temp, "1.0"))
-           version = 10;
-         else if (!strcmp(temp, "1.1"))
-           version = 11;
-         else if (!strcmp(temp, "2.0"))
-           version = 20;
-         else if (!strcmp(temp, "2.1"))
-           version = 21;
-         else if (!strcmp(temp, "2.2"))
-           version = 22;
-         else
-         {
-           print_fatal_error(outfile, "Bad VERSION \"%s\" on line %d.", temp, linenum);
-           pass = 0;
-           goto test_exit;
-         }
-       }
-       else
-       {
-         print_fatal_error(outfile, "Missing VERSION number on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else if (!_cups_strcasecmp(token, "RESOURCE"))
+      for (i = data->num_expects, expect = data->expects; i > 0; i --, expect ++)
       {
-       /*
-        * Resource name...
-       */
+       ipp_attribute_t *group_found;   /* Found parent attribute for group tests */
 
-       if (!get_token(fp, resource, sizeof(resource), &linenum))
-       {
-         print_fatal_error(outfile, "Missing RESOURCE path on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else if (!_cups_strcasecmp(token, "OPERATION"))
-      {
-       /*
-        * Operation...
-       */
+       if (expect->if_defined && !_ippVarsGet(vars, expect->if_defined))
+         continue;
 
-       if (!get_token(fp, temp, sizeof(temp), &linenum))
-       {
-         print_fatal_error(outfile, "Missing OPERATION code on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+       if (expect->if_not_defined &&
+           _ippVarsGet(vars, expect->if_not_defined))
+         continue;
 
-       expand_variables(vars, token, temp, sizeof(token));
+       found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO);
 
-       if ((op = ippOpValue(token)) == (ipp_op_t)-1 &&
-           (op = (ipp_op_t)strtol(token, NULL, 0)) == 0)
+       do
        {
-         print_fatal_error(outfile, "Bad OPERATION code \"%s\" on line %d.", token,
-                           linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else if (!_cups_strcasecmp(token, "GROUP"))
-      {
-       /*
-        * Attribute group...
-       */
+         group_found = found;
 
-       if (!get_token(fp, token, sizeof(token), &linenum))
-       {
-         print_fatal_error(outfile, "Missing GROUP tag on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+          if (expect->in_group && strchr(expect->name, '/'))
+          {
+            char       group_name[256],/* Parent attribute name */
+                       *group_ptr;     /* Pointer into parent attribute name */
 
-       if ((value = ippTagValue(token)) < 0)
-       {
-         print_fatal_error(outfile, "Bad GROUP tag \"%s\" on line %d.", token, linenum);
-         pass = 0;
-         goto test_exit;
-       }
+           strlcpy(group_name, expect->name, sizeof(group_name));
+           if ((group_ptr = strchr(group_name, '/')) != NULL)
+             *group_ptr = '\0';
 
-       if (value == group)
-         ippAddSeparator(request);
+           group_found = ippFindAttribute(response, group_name, IPP_TAG_ZERO);
+         }
 
-        group = value;
-      }
-      else if (!_cups_strcasecmp(token, "DELAY"))
-      {
-       /*
-        * Delay before operation...
-       */
-
-        double delay;
-
-       if (!get_token(fp, temp, sizeof(temp), &linenum))
-       {
-         print_fatal_error(outfile, "Missing DELAY value on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
-
-       expand_variables(vars, token, temp, sizeof(token));
+         if ((found && expect->not_expect) ||
+             (!found && !(expect->not_expect || expect->optional)) ||
+             (found && !expect_matches(expect, ippGetValueTag(found))) ||
+             (group_found && expect->in_group && ippGetGroupTag(group_found) != expect->in_group))
+         {
+           if (expect->define_no_match)
+             _ippVarsSet(vars, expect->define_no_match, "1");
+           else if (!expect->define_match && !expect->define_value)
+           {
+             if (found && expect->not_expect)
+               add_stringf(data->errors, "NOT EXPECTED: %s", expect->name);
+             else if (!found && !(expect->not_expect || expect->optional))
+               add_stringf(data->errors, "EXPECTED: %s", expect->name);
+             else if (found)
+             {
+               if (!expect_matches(expect, ippGetValueTag(found)))
+                 add_stringf(data->errors, "EXPECTED: %s OF-TYPE %s (got %s)",
+                             expect->name, expect->of_type,
+                             ippTagString(ippGetValueTag(found)));
+
+               if (expect->in_group && ippGetGroupTag(group_found) != expect->in_group)
+                 add_stringf(data->errors, "EXPECTED: %s IN-GROUP %s (got %s).",
+                             expect->name, ippTagString(expect->in_group),
+                             ippTagString(ippGetGroupTag(group_found)));
+             }
+           }
 
-       if ((delay = _cupsStrScand(token, NULL, localeconv())) <= 0.0)
-       {
-         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);
+           if (expect->repeat_no_match && repeat_count < expect->repeat_limit)
+             repeat_test = 1;
+           break;
+         }
 
-         usleep((useconds_t)(1000000.0 * delay));
-       }
-      }
-      else if (!_cups_strcasecmp(token, "ATTR"))
-      {
-       /*
-        * Attribute...
-       */
+         if (found)
+           ippAttributeString(found, buffer, sizeof(buffer));
 
-       if (!get_token(fp, token, sizeof(token), &linenum))
-       {
-         print_fatal_error(outfile, "Missing ATTR value tag on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+         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)
+             _ippVarsSet(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(data->errors, "EXPECTED: %s WITH-VALUES-FROM %s", expect->name, expect->with_value_from);
 
-       if ((value = ippTagValue(token)) == IPP_TAG_ZERO)
-       {
-         print_fatal_error(outfile, "Bad ATTR value tag \"%s\" on line %d.", token,
-                           linenum);
-         pass = 0;
-         goto test_exit;
-       }
+             with_value_from(data->errors, ippFindAttribute(response, expect->with_value_from, IPP_TAG_ZERO), found, buffer, sizeof(buffer));
+           }
 
-       if (!get_token(fp, attr, sizeof(attr), &linenum))
-       {
-         print_fatal_error(outfile, "Missing ATTR name on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+           if (expect->repeat_no_match && repeat_count < expect->repeat_limit)
+             repeat_test = 1;
 
-       if (!get_token(fp, temp, sizeof(temp), &linenum))
-       {
-         print_fatal_error(outfile, "Missing ATTR value on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+           break;
+         }
+         else if (found && !with_value(data, NULL, expect->with_value, expect->with_flags, found, buffer, sizeof(buffer)))
+         {
+           if (expect->define_no_match)
+             _ippVarsSet(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(data->errors, "EXPECTED: %s %s /%s/", expect->name, with_flags_string(expect->with_flags), expect->with_value);
+             else
+               add_stringf(data->errors, "EXPECTED: %s %s \"%s\"", expect->name, with_flags_string(expect->with_flags), expect->with_value);
 
-        expand_variables(vars, token, temp, sizeof(token));
-        attrptr = NULL;
+             with_value(data, data->errors, expect->with_value, expect->with_flags, found, buffer, sizeof(buffer));
+           }
 
-        switch (value)
-       {
-         case IPP_TAG_BOOLEAN :
-             if (!_cups_strcasecmp(token, "true"))
-               attrptr = ippAddBoolean(request, group, attr, 1);
-              else
-               attrptr = ippAddBoolean(request, group, attr, (char)atoi(token));
-             break;
+           if (expect->repeat_no_match &&
+               repeat_count < expect->repeat_limit)
+             repeat_test = 1;
 
-         case IPP_TAG_INTEGER :
-         case IPP_TAG_ENUM :
-             if (!strchr(token, ','))
-             {
-               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 */
+           break;
+         }
 
-               values[0] = (int)strtol(token, &tokenptr, 10);
-               while (tokenptr && *tokenptr &&
-                      num_values < (int)(sizeof(values) / sizeof(values[0])))
-               {
-                 if (*tokenptr == ',')
-                   tokenptr ++;
+         if (found && expect->count > 0 && ippGetCount(found) != expect->count)
+         {
+           if (expect->define_no_match)
+             _ippVarsSet(vars, expect->define_no_match, "1");
+           else if (!expect->define_match && !expect->define_value)
+           {
+             add_stringf(data->errors, "EXPECTED: %s COUNT %d (got %d)", expect->name,
+                         expect->count, ippGetCount(found));
+           }
 
-                 if (!isdigit(*tokenptr & 255) && *tokenptr != '-')
-                 {
-                   char *ptr, ch;      /* Pointer to next terminator */
-
-                   if (value != IPP_TAG_ENUM)
-                     break;
-
-                    if ((ptr = strchr(tokenptr, ',')) != NULL)
-                    {
-                      ch   = *ptr;
-                      *ptr = '\0';
-                    }
-                    else
-                    {
-                      ch  = '\0';
-                      ptr = tokenptr + strlen(tokenptr);
-                    }
-
-                    if ((i = ippEnumValue(attr, tokenptr)) < 0)
-                      break;
-                 }
-                 else
-                   i = (int)strtol(tokenptr, &tokenptr, 0);
+           if (expect->repeat_no_match &&
+               repeat_count < expect->repeat_limit)
+             repeat_test = 1;
 
-                 values[num_values ++] = i;
-               }
+           break;
+         }
 
-               attrptr = ippAddIntegers(request, group, value, attr, num_values, values);
-             }
+         if (found && expect->same_count_as)
+         {
+           attrptr = ippFindAttribute(response, expect->same_count_as,
+                                      IPP_TAG_ZERO);
 
-             if (!tokenptr || *tokenptr)
+           if (!attrptr || ippGetCount(attrptr) != ippGetCount(found))
+           {
+             if (expect->define_no_match)
+               _ippVarsSet(vars, expect->define_no_match, "1");
+             else if (!expect->define_match && !expect->define_value)
              {
-               print_fatal_error(outfile, "Bad %s value \"%s\" on line %d.",
-                                 ippTagString(value), token, linenum);
-               pass = 0;
-               goto test_exit;
+               if (!attrptr)
+                 add_stringf(data->errors,
+                             "EXPECTED: %s (%d values) SAME-COUNT-AS %s "
+                             "(not returned)", expect->name,
+                             ippGetCount(found), expect->same_count_as);
+               else if (ippGetCount(attrptr) != ippGetCount(found))
+                 add_stringf(data->errors,
+                             "EXPECTED: %s (%d values) SAME-COUNT-AS %s "
+                             "(%d values)", expect->name, ippGetCount(found),
+                             expect->same_count_as, ippGetCount(attrptr));
              }
-             break;
 
-         case IPP_TAG_RESOLUTION :
-             {
-               int     xres,           /* X resolution */
-                       yres;           /* Y resolution */
-               char    *ptr;           /* Pointer into value */
-
-               xres = yres = (int)strtol(token, (char **)&ptr, 10);
-               if (ptr > token && xres > 0)
-               {
-                 if (*ptr == 'x')
-                   yres = (int)strtol(ptr + 1, (char **)&ptr, 10);
-               }
-
-               if (ptr <= token || xres <= 0 || yres <= 0 || !ptr ||
-                   (_cups_strcasecmp(ptr, "dpi") &&
-                    _cups_strcasecmp(ptr, "dpc") &&
-                    _cups_strcasecmp(ptr, "dpcm") &&
-                    _cups_strcasecmp(ptr, "other")))
-               {
-                 print_fatal_error(outfile, "Bad resolution value \"%s\" on line %d.",
-                                   token, linenum);
-                 pass = 0;
-                 goto test_exit;
-               }
-
-               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
-                 attrptr = ippAddResolution(request, group, attr, (ipp_res_t)0, xres, yres);
-             }
+             if (expect->repeat_no_match &&
+                 repeat_count < expect->repeat_limit)
+               repeat_test = 1;
+
              break;
+           }
+         }
 
-         case IPP_TAG_RANGE :
+         if (found && expect->define_match)
+           _ippVarsSet(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))
              {
-               int     lowers[4],      /* Lower value */
-                       uppers[4],      /* Upper values */
-                       num_vals;       /* Number of values */
+               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;
 
-               num_vals = sscanf(token, "%d-%d,%d-%d,%d-%d,%d-%d",
-                                 lowers + 0, uppers + 0,
-                                 lowers + 1, uppers + 1,
-                                 lowers + 2, uppers + 2,
-                                 lowers + 3, uppers + 3);
+               case IPP_TAG_RESOLUTION :
+                   {
+                     int       xres,   /* Horizontal resolution */
+                               yres;   /* Vertical resolution */
+                     ipp_res_t units;  /* Resolution units */
 
-                if ((num_vals & 1) || num_vals == 0)
-               {
-                 print_fatal_error(outfile, "Bad rangeOfInteger value \"%s\" on line "
-                                   "%d.", token, linenum);
-                 pass = 0;
-                 goto test_exit;
-               }
+                     xres = ippGetResolution(found, last, &yres, &units);
 
-               attrptr = ippAddRanges(request, group, attr, num_vals / 2, lowers,
-                                      uppers);
-             }
-             break;
+                     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_BEGIN_COLLECTION :
-             if (!strcmp(token, "{"))
-             {
-               ipp_t   *col = get_collection(outfile, vars, fp, &linenum);
-                                       /* Collection value */
+               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;
 
-                if (col)
-                {
-                 attrptr = lastcol = ippAddCollection(request, group, attr, col);
-                 ippDelete(col);
-               }
-               else
-               {
-                 pass = 0;
-                 goto test_exit;
-               }
-              }
-             else
-             {
-               print_fatal_error(outfile, "Bad ATTR collection value on line %d.",
-                                 linenum);
-               pass = 0;
-               goto test_exit;
+               default :
+                   ippAttributeString(found, buffer, sizeof(buffer));
+                   break;
              }
+           }
 
-             do
-             {
-               ipp_t   *col;                   /* Collection value */
-               long    pos = ftell(fp);        /* Save position of file */
+           _ippVarsSet(vars, expect->define_value, buffer);
+         }
 
-               if (!get_token(fp, token, sizeof(token), &linenum))
-                 break;
+         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 (strcmp(token, ","))
-               {
-                 fseek(fp, pos, SEEK_SET);
-                 break;
-               }
+   /*
+    * If we are going to repeat this test, display intermediate results...
+    */
 
-               if (!get_token(fp, token, sizeof(token), &linenum) || strcmp(token, "{"))
+    if (repeat_test)
+    {
+      if (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
+      {
+       cupsFilePrintf(cupsFileStdout(), "%04d]\n", repeat_count);
+\
+       if (data->num_displayed > 0)
+       {
+         for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(response))
+         {
+           const char *attrname = ippGetName(attrptr);
+           if (attrname)
+           {
+             for (i = 0; i < data->num_displayed; i ++)
+             {
+               if (!strcmp(data->displayed[i], attrname))
                {
-                 print_fatal_error(outfile, "Unexpected \"%s\" on line %d.", token, linenum);
-                 pass = 0;
-                 goto test_exit;
+                 print_attr(cupsFileStdout(), _CUPS_OUTPUT_TEST, attrptr, NULL);
                  break;
                }
-
-               if ((col = get_collection(outfile, vars, fp, &linenum)) == NULL)
-                 break;
-
-               ippSetCollection(request, &attrptr, ippGetCount(attrptr), col);
-               lastcol = attrptr;
              }
-             while (!strcmp(token, "{"));
-             break;
+           }
+         }
+       }
+      }
 
-          case IPP_TAG_STRING :
-              attrptr = ippAddOctetString(request, group, attr, token, (int)strlen(token));
-             break;
+      if (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
+      {
+       cupsFilePrintf(cupsFileStdout(), "    %-68.68s [", data->name);
+      }
 
-         default :
-             print_fatal_error(outfile, "Unsupported ATTR value tag %s on line %d.",
-                               ippTagString(value), linenum);
-             pass = 0;
-             goto test_exit;
-
-         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, ','))
-               attrptr = ippAddString(request, group, value, attr, NULL, token);
-             else
-             {
-              /*
-               * Multiple string values...
-               */
+      ippDelete(response);
+      response = NULL;
+    }
+  }
+  while (repeat_test);
 
-                int    num_values;     /* Number of values */
-                char   *values[100],   /* Values */
-                       *ptr;           /* Pointer to next value */
+  ippDelete(request);
 
+  request = NULL;
 
-                values[0]  = token;
-               num_values = 1;
+  if (cupsArrayCount(data->errors) > 0)
+    data->prev_pass = data->pass = 0;
 
-                for (ptr = strchr(token, ','); ptr; ptr = strchr(ptr, ','))
-               {
-                 if (ptr > token && ptr[-1] == '\\')
-                   _cups_strcpy(ptr - 1, ptr);
-                 else
-                 {
-                   *ptr++ = '\0';
-                   values[num_values] = ptr;
-                   num_values ++;
-                 }
-               }
+  if (data->prev_pass)
+    data->pass_count ++;
+  else
+    data->fail_count ++;
 
-               attrptr = ippAddStrings(request, group, value, attr, num_values,
-                                       NULL, (const char **)values);
-             }
-             break;
-       }
+  if (data->output == _CUPS_OUTPUT_PLIST)
+  {
+    cupsFilePuts(data->outfile, "<key>Successful</key>\n");
+    cupsFilePuts(data->outfile, data->prev_pass ? "<true />\n" : "<false />\n");
+    cupsFilePuts(data->outfile, "<key>StatusCode</key>\n");
+    print_xml_string(data->outfile, "string", ippErrorString(cupsLastError()));
+    cupsFilePuts(data->outfile, "<key>ResponseAttributes</key>\n");
+    cupsFilePuts(data->outfile, "<array>\n");
+    cupsFilePuts(data->outfile, "<dict>\n");
+    for (attrptr = ippFirstAttribute(response), group = ippGetGroupTag(attrptr);
+        attrptr;
+        attrptr = ippNextAttribute(response))
+      print_attr(data->outfile, data->output, attrptr, &group);
+    cupsFilePuts(data->outfile, "</dict>\n");
+    cupsFilePuts(data->outfile, "</array>\n");
+  }
+  else if (data->output == _CUPS_OUTPUT_IPPSERVER && response)
+  {
+    for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(response))
+    {
+      if (!ippGetName(attrptr) || ippGetGroupTag(attrptr) != IPP_TAG_PRINTER)
+       continue;
 
-       if (!attrptr)
-       {
-         print_fatal_error(outfile, "Unable to add attribute on line %d: %s", linenum,
-                           cupsLastErrorString());
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else if (!_cups_strcasecmp(token, "FILE"))
-      {
-       /*
-        * File...
-       */
+      print_ippserver_attr(data, attrptr, 0);
+    }
+  }
 
-       if (!get_token(fp, temp, sizeof(temp), &linenum))
-       {
-         print_fatal_error(outfile, "Missing FILE filename on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+  if (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
+  {
+    cupsFilePuts(cupsFileStdout(), data->prev_pass ? "PASS]\n" : "FAIL]\n");
 
-        expand_variables(vars, token, temp, sizeof(token));
-       get_filename(testfile, filename, token, sizeof(filename));
+    if (!data->prev_pass || (data->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 (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 (!_cups_strcasecmp(token, "STATUS"))
+      if (data->verbosity && response)
       {
-       /*
-        * Status...
-       */
-
-        if (num_statuses >= (int)(sizeof(statuses) / sizeof(statuses[0])))
-       {
-         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(outfile, "Missing STATUS code on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+       for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(response))
+         print_attr(cupsFileStdout(), _CUPS_OUTPUT_TEST, attrptr, NULL);
+      }
+    }
+  }
+  else if (!data->prev_pass && data->output != _CUPS_OUTPUT_QUIET)
+    fprintf(stderr, "%s\n", cupsLastErrorString());
 
-       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(outfile, "Bad STATUS code \"%s\" on line %d.", token,
-                           linenum);
-         pass = 0;
-         goto test_exit;
-       }
+  if (data->prev_pass && data->output >= _CUPS_OUTPUT_LIST && !data->verbosity && data->num_displayed > 0)
+  {
+    size_t     width;                  /* Length of value */
 
-        last_status = statuses + num_statuses;
-       num_statuses ++;
+    for (i = 0; i < data->num_displayed; i ++)
+    {
+      widths[i] = strlen(data->displayed[i]);
 
-        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 (!_cups_strcasecmp(token, "EXPECT") || !_cups_strcasecmp(token, "EXPECT-ALL"))
+      for (attrptr = ippFindAttribute(response, data->displayed[i], IPP_TAG_ZERO);
+          attrptr;
+          attrptr = ippFindNextAttribute(response, data->displayed[i], IPP_TAG_ZERO))
       {
-       /*
-        * Expected attributes...
-       */
-
-       int expect_all = !_cups_strcasecmp(token, "EXPECT-ALL");
+       width = ippAttributeString(attrptr, NULL, 0);
+       if (width > widths[i])
+         widths[i] = width;
+      }
+    }
 
-        if (num_expects >= (int)(sizeof(expects) / sizeof(expects[0])))
-        {
-         print_fatal_error(outfile, "Too many EXPECT's on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-        }
+    if (data->output == _CUPS_OUTPUT_CSV)
+      print_csv(data, NULL, NULL, data->num_displayed, data->displayed, widths);
+    else
+      print_line(data, NULL, NULL, data->num_displayed, data->displayed, widths);
 
-       if (!get_token(fp, token, sizeof(token), &linenum))
-       {
-         print_fatal_error(outfile, "Missing EXPECT name on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+    attrptr = ippFirstAttribute(response);
 
-        last_expect = expects + num_expects;
-       num_expects ++;
+    while (attrptr)
+    {
+      while (attrptr && ippGetGroupTag(attrptr) <= IPP_TAG_OPERATION)
+       attrptr = ippNextAttribute(response);
 
-       memset(last_expect, 0, sizeof(_cups_expect_t));
-       last_expect->repeat_limit = 1000;
-       last_expect->expect_all   = expect_all;
+      if (attrptr)
+      {
+       if (data->output == _CUPS_OUTPUT_CSV)
+         print_csv(data, response, attrptr, data->num_displayed, data->displayed, widths);
+       else
+         print_line(data, response, attrptr, data->num_displayed, data->displayed, widths);
 
-        if (token[0] == '!')
-        {
-          last_expect->not_expect = 1;
-          last_expect->name       = strdup(token + 1);
-        }
-        else if (token[0] == '?')
-        {
-          last_expect->optional = 1;
-          last_expect->name     = strdup(token + 1);
-        }
-        else
-         last_expect->name = strdup(token);
+       while (attrptr && ippGetGroupTag(attrptr) > IPP_TAG_OPERATION)
+         attrptr = ippNextAttribute(response);
       }
-      else if (!_cups_strcasecmp(token, "COUNT"))
-      {
-       if (!get_token(fp, token, sizeof(token), &linenum))
-       {
-         print_fatal_error(outfile, "Missing COUNT number on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+    }
+  }
+  else if (!data->prev_pass)
+  {
+    if (data->output == _CUPS_OUTPUT_PLIST)
+    {
+      cupsFilePuts(data->outfile, "<key>Errors</key>\n");
+      cupsFilePuts(data->outfile, "<array>\n");
 
-        if ((i = atoi(token)) <= 0)
-       {
-         print_fatal_error(outfile, "Bad COUNT \"%s\" on line %d.", token, linenum);
-         pass = 0;
-         goto test_exit;
-       }
+      for (error = (char *)cupsArrayFirst(data->errors);
+          error;
+          error = (char *)cupsArrayNext(data->errors))
+       print_xml_string(data->outfile, "string", error);
 
-       if (last_expect)
-         last_expect->count = i;
-       else
-       {
-         print_fatal_error(outfile, "COUNT without a preceding EXPECT on line %d.",
-                           linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else if (!_cups_strcasecmp(token, "DEFINE-MATCH"))
-      {
-       if (!get_token(fp, token, sizeof(token), &linenum))
-       {
-         print_fatal_error(outfile, "Missing DEFINE-MATCH variable on line %d.",
-                           linenum);
-         pass = 0;
-         goto test_exit;
-       }
+      cupsFilePuts(data->outfile, "</array>\n");
+    }
 
-       if (last_expect)
-         last_expect->define_match = strdup(token);
-       else if (last_status)
-         last_status->define_match = strdup(token);
-       else
-       {
-         print_fatal_error(outfile, "DEFINE-MATCH without a preceding EXPECT or STATUS "
-                           "on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else if (!_cups_strcasecmp(token, "DEFINE-NO-MATCH"))
-      {
-       if (!get_token(fp, token, sizeof(token), &linenum))
-       {
-         print_fatal_error(outfile, "Missing DEFINE-NO-MATCH variable on line %d.",
-                           linenum);
-         pass = 0;
-         goto test_exit;
-       }
+    if (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
+    {
+      for (error = (char *)cupsArrayFirst(data->errors);
+          error;
+          error = (char *)cupsArrayNext(data->errors))
+       cupsFilePrintf(cupsFileStdout(), "        %s\n", error);
+    }
+  }
 
-       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(outfile, "DEFINE-NO-MATCH without a preceding EXPECT or "
-                           "STATUS on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else if (!_cups_strcasecmp(token, "DEFINE-VALUE"))
+  if (data->num_displayed > 0 && !data->verbosity && response && (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout())))
+  {
+    for (attrptr = ippFirstAttribute(response); attrptr; attrptr = ippNextAttribute(response))
+    {
+      if (ippGetName(attrptr))
       {
-       if (!get_token(fp, token, sizeof(token), &linenum))
-       {
-         print_fatal_error(outfile, "Missing DEFINE-VALUE variable on line %d.",
-                           linenum);
-         pass = 0;
-         goto test_exit;
-       }
-
-       if (last_expect)
-         last_expect->define_value = strdup(token);
-       else
+       for (i = 0; i < data->num_displayed; i ++)
        {
-         print_fatal_error(outfile, "DEFINE-VALUE without a preceding EXPECT on "
-                           "line %d.", linenum);
-         pass = 0;
-         goto test_exit;
+         if (!strcmp(data->displayed[i], ippGetName(attrptr)))
+         {
+           print_attr(data->outfile, data->output, attrptr, NULL);
+           break;
+         }
        }
       }
-      else if (!_cups_strcasecmp(token, "OF-TYPE"))
-      {
-       if (!get_token(fp, token, sizeof(token), &linenum))
-       {
-         print_fatal_error(outfile, "Missing OF-TYPE value tag(s) on line %d.",
-                           linenum);
-         pass = 0;
-         goto test_exit;
-       }
+    }
+  }
 
-       if (last_expect)
-         last_expect->of_type = strdup(token);
-       else
-       {
-         print_fatal_error(outfile, "OF-TYPE without a preceding EXPECT on line %d.",
-                           linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else if (!_cups_strcasecmp(token, "IN-GROUP"))
-      {
-        ipp_tag_t      in_group;       /* IN-GROUP value */
+  skip_error:
 
+  if (data->output == _CUPS_OUTPUT_PLIST)
+    cupsFilePuts(data->outfile, "</dict>\n");
 
-       if (!get_token(fp, token, sizeof(token), &linenum))
-       {
-         print_fatal_error(outfile, "Missing IN-GROUP group tag on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+  ippDelete(response);
+  response = NULL;
 
-        if ((in_group = ippTagValue(token)) == (ipp_tag_t)-1)
-       {
-       }
-       else if (last_expect)
-         last_expect->in_group = in_group;
-       else
-       {
-         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;
-       }
+  for (i = 0; i < data->num_statuses; i ++)
+  {
+    if (data->statuses[i].if_defined)
+      free(data->statuses[i].if_defined);
+    if (data->statuses[i].if_not_defined)
+      free(data->statuses[i].if_not_defined);
+    if (data->statuses[i].define_match)
+      free(data->statuses[i].define_match);
+    if (data->statuses[i].define_no_match)
+      free(data->statuses[i].define_no_match);
+  }
+  data->num_statuses = 0;
 
-        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 (!_cups_strcasecmp(token, "SAME-COUNT-AS"))
-      {
-       if (!get_token(fp, token, sizeof(token), &linenum))
-       {
-         print_fatal_error(outfile, "Missing SAME-COUNT-AS name on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+  for (i = data->num_expects, expect = data->expects; i > 0; i --, expect ++)
+  {
+    free(expect->name);
+    if (expect->of_type)
+      free(expect->of_type);
+    if (expect->same_count_as)
+      free(expect->same_count_as);
+    if (expect->if_defined)
+      free(expect->if_defined);
+    if (expect->if_not_defined)
+      free(expect->if_not_defined);
+    if (expect->with_value)
+      free(expect->with_value);
+    if (expect->define_match)
+      free(expect->define_match);
+    if (expect->define_no_match)
+      free(expect->define_no_match);
+    if (expect->define_value)
+      free(expect->define_value);
+  }
+  data->num_expects = 0;
 
-       if (last_expect)
-         last_expect->same_count_as = strdup(token);
-       else
-       {
-         print_fatal_error(outfile, "SAME-COUNT-AS without a preceding EXPECT on line "
-                           "%d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else if (!_cups_strcasecmp(token, "IF-DEFINED"))
-      {
-       if (!get_token(fp, token, sizeof(token), &linenum))
-       {
-         print_fatal_error(outfile, "Missing IF-DEFINED name on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+  for (i = 0; i < data->num_displayed; i ++)
+    free(data->displayed[i]);
+  data->num_displayed = 0;
 
-       if (last_expect)
-         last_expect->if_defined = strdup(token);
-       else if (last_status)
-         last_status->if_defined = strdup(token);
-       else
-       {
-         print_fatal_error(outfile, "IF-DEFINED without a preceding EXPECT or STATUS "
-                           "on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      else if (!_cups_strcasecmp(token, "IF-NOT-DEFINED"))
-      {
-       if (!get_token(fp, token, sizeof(token), &linenum))
-       {
-         print_fatal_error(outfile, "Missing IF-NOT-DEFINED name on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+  return (data->ignore_errors || data->prev_pass);
+}
 
-       if (last_expect)
-         last_expect->if_not_defined = strdup(token);
-       else if (last_status)
-         last_status->if_not_defined = strdup(token);
-       else
-       {
-         print_fatal_error(outfile, "IF-NOT-DEFINED without a preceding EXPECT or STATUS "
-                           "on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
-      }
-      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(outfile, "Missing %s value on line %d.", token, linenum);
-         pass = 0;
-         goto test_exit;
-       }
+/*
+ * 'do_tests()' - Do tests as specified in the test file.
+ */
 
-        if (last_expect)
-       {
-        /*
-         * Expand any variables in the value and then save it.
-         */
+static int                             /* O - 1 on success, 0 on failure */
+do_tests(const char       *testfile,   /* I - Test file to use */
+         _ipp_vars_t      *vars,       /* I - Variables */
+         _cups_testdata_t *data)       /* I - Test data */
+{
+  http_encryption_t encryption;                /* Encryption mode */
 
-         expand_variables(vars, token, temp, sizeof(token));
 
-         tokenptr = token + strlen(token) - 1;
+ /*
+  * Connect to the printer/server...
+  */
 
-         if (token[0] == '/' && tokenptr > token && *tokenptr == '/')
-         {
-          /*
-           * WITH-VALUE is a POSIX extended regular expression.
-           */
+  if (!_cups_strcasecmp(vars->scheme, "https") || !_cups_strcasecmp(vars->scheme, "ipps"))
+    encryption = HTTP_ENCRYPTION_ALWAYS;
+  else
+    encryption = data->encryption;
 
-           last_expect->with_value = calloc(1, (size_t)(tokenptr - token));
-           last_expect->with_flags |= _CUPS_WITH_REGEX;
+  if ((data->http = httpConnect2(vars->host, vars->port, NULL, data->family, encryption, 1, 30000, NULL)) == NULL)
+  {
+    print_fatal_error(data, "Unable to connect to \"%s\" on port %d - %s", vars->host, vars->port, cupsLastErrorString());
+    return (0);
+  }
 
-           if (last_expect->with_value)
-             memcpy(last_expect->with_value, token + 1, (size_t)(tokenptr - token - 1));
-         }
-         else
-         {
-          /*
-           * WITH-VALUE is a literal value...
-           */
+#ifdef HAVE_LIBZ
+  httpSetDefaultField(data->http, HTTP_FIELD_ACCEPT_ENCODING, "deflate, gzip, identity");
+#else
+  httpSetDefaultField(data->http, HTTP_FIELD_ACCEPT_ENCODING, "identity");
+#endif /* HAVE_LIBZ */
 
-           char *ptr;                  /* Pointer into value */
+  if (data->timeout > 0.0)
+    httpSetTimeout(data->http, data->timeout, timeout_cb, NULL);
 
-            for (ptr = token; *ptr; ptr ++)
-            {
-             if (*ptr == '\\' && ptr[1])
-             {
-              /*
-               * Remove \ from \foo...
-               */
+ /*
+  * Run tests...
+  */
 
-               _cups_strcpy(ptr, ptr + 1);
-             }
-           }
+  _ippFileParse(vars, testfile, (void *)data);
 
-           last_expect->with_value = 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 (!_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;
-       }
+ /*
+  * Close connection and return...
+  */
 
-        if (last_expect)
-       {
-        /*
-         * Expand any variables in the value and then save it.
-         */
+  httpClose(data->http);
+  data->http = NULL;
 
-         expand_variables(vars, token, temp, sizeof(token));
+  return (data->pass);
+}
 
-         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 (!_cups_strcasecmp(token, "DISPLAY"))
-      {
-       /*
-        * Display attributes...
-       */
 
-        if (num_displayed >= (int)(sizeof(displayed) / sizeof(displayed[0])))
-       {
-         print_fatal_error(outfile, "Too many DISPLAY's on line %d", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+/*
+ * 'error_cb()' - Print/add an error message.
+ */
 
-       if (!get_token(fp, token, sizeof(token), &linenum))
-       {
-         print_fatal_error(outfile, "Missing DISPLAY name on line %d.", linenum);
-         pass = 0;
-         goto test_exit;
-       }
+static int                             /* O - 1 to continue, 0 to stop */
+error_cb(_ipp_file_t      *f,          /* I - IPP file data */
+         _cups_testdata_t *data,       /* I - Test data */
+         const char       *error)      /* I - Error message */
+{
+  (void)f;
 
-       displayed[num_displayed] = strdup(token);
-       num_displayed ++;
-      }
-      else
-      {
-       print_fatal_error(outfile, "Unexpected token %s seen on line %d.", token,
-                         linenum);
-       pass = 0;
-       goto test_exit;
-      }
-    }
+  print_fatal_error(data, "%s", error);
 
-   /*
-    * Submit the IPP request...
-    */
+  return (1);
+}
 
-    TestCount ++;
 
-    ippSetVersion(request, version / 10, version % 10);
-    ippSetOperation(request, op);
-    ippSetRequestId(request, request_id);
+/*
+ * 'expect_matches()' - Return true if the tag matches the specification.
+ */
 
-    if (Output == _CUPS_OUTPUT_PLIST)
-    {
-      fputs("<dict>\n", outfile);
-      fputs("<key>Name</key>\n", outfile);
-      print_xml_string(outfile, "string", name);
-      if (file_id[0])
-      {
-       fputs("<key>FileId</key>\n", outfile);
-       print_xml_string(outfile, "string", file_id);
-      }
-      if (test_id[0])
-      {
-        fputs("<key>TestId</key>\n", outfile);
-        print_xml_string(outfile, "string", test_id);
-      }
-      fputs("<key>Version</key>\n", outfile);
-      fprintf(outfile, "<string>%d.%d</string>\n", version / 10, version % 10);
-      fputs("<key>Operation</key>\n", outfile);
-      print_xml_string(outfile, "string", ippOpString(op));
-      fputs("<key>RequestId</key>\n", outfile);
-      fprintf(outfile, "<integer>%d</integer>\n", request_id);
-      fputs("<key>RequestAttributes</key>\n", outfile);
-      fputs("<array>\n", outfile);
-      if (request->attrs)
-      {
-       fputs("<dict>\n", outfile);
-       for (attrptr = request->attrs,
-                group = attrptr ? attrptr->group_tag : IPP_TAG_ZERO;
-            attrptr;
-            attrptr = attrptr->next)
-         print_attr(outfile, Output, attrptr, &group);
-       fputs("</dict>\n", outfile);
-      }
-      fputs("</array>\n", outfile);
-    }
+static int                             /* O - 1 if matches, 0 otherwise */
+expect_matches(
+    _cups_expect_t *expect,            /* I - Expected attribute */
+    ipp_tag_t      value_tag)          /* I - Value tag for attribute */
+{
+  int  match;                          /* Match? */
+  char *of_type,                       /* Type name to match */
+       *next,                          /* Next name to match */
+       sep;                            /* Separator character */
 
-    if (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != stdout))
-    {
-      if (Verbosity)
-      {
-       printf("    %s:\n", ippOpString(op));
 
-       for (attrptr = request->attrs; attrptr; attrptr = attrptr->next)
-         print_attr(stdout, _CUPS_OUTPUT_TEST, attrptr, NULL);
-      }
+ /*
+  * If we don't expect a particular type, return immediately...
+  */
 
-      printf("    %-68.68s [", name);
-      fflush(stdout);
-    }
+  if (!expect->of_type)
+    return (1);
 
-    if ((skip_previous && !prev_pass) || skip_test)
-    {
-      SkipCount ++;
+ /*
+  * Parse the "of_type" value since the string can contain multiple attribute
+  * types separated by "," or "|"...
+  */
 
-      ippDelete(request);
-      request = NULL;
+  for (of_type = expect->of_type, match = 0; !match && *of_type; of_type = next)
+  {
+   /*
+    * Find the next separator, and set it (temporarily) to nul if present.
+    */
 
-      if (Output == _CUPS_OUTPUT_PLIST)
-      {
-       fputs("<key>Successful</key>\n", outfile);
-       fputs("<true />\n", outfile);
-       fputs("<key>Skipped</key>\n", outfile);
-       fputs("<true />\n", outfile);
-       fputs("<key>StatusCode</key>\n", outfile);
-       print_xml_string(outfile, "string", "skip");
-       fputs("<key>ResponseAttributes</key>\n", outfile);
-       fputs("<dict />\n", outfile);
-      }
+    for (next = of_type; *next && *next != '|' && *next != ','; next ++);
 
-      if (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != stdout))
-       puts("SKIP]");
+    if ((sep = *next) != '\0')
+      *next = '\0';
 
-      goto skip_error;
-    }
+   /*
+    * Support some meta-types to make it easier to write the test file.
+    */
 
-    PasswordTries   = 0;
-    repeat_count    = 0;
-    repeat_interval = 1;
-    repeat_prev     = 1;
+    if (!strcmp(of_type, "text"))
+      match = value_tag == IPP_TAG_TEXTLANG || value_tag == IPP_TAG_TEXT;
+    else if (!strcmp(of_type, "name"))
+      match = value_tag == IPP_TAG_NAMELANG || value_tag == IPP_TAG_NAME;
+    else if (!strcmp(of_type, "collection"))
+      match = value_tag == IPP_TAG_BEGIN_COLLECTION;
+    else
+      match = value_tag == ippTagValue(of_type);
 
-    do
-    {
-      repeat_count ++;
+   /*
+    * Restore the separator if we have one...
+    */
 
-      status = HTTP_STATUS_OK;
+    if (sep)
+      *next++ = sep;
+  }
 
-      if (transfer == _CUPS_TRANSFER_CHUNKED ||
-         (transfer == _CUPS_TRANSFER_AUTO && filename[0]))
-      {
-       /*
-       * Send request using chunking - a 0 length means "chunk".
-       */
+  return (match);
+}
 
-       length = 0;
-      }
-      else
-      {
-       /*
-       * Send request using content length...
-       */
 
-       length = ippLength(request);
+/*
+ * 'get_filename()' - Get a filename based on the current test file.
+ */
 
-       if (filename[0] && (reqfile = cupsFileOpen(filename, "r")) != NULL)
-       {
-        /*
-         * Read the file to get the uncompressed file size...
-         */
+static char *                          /* O - Filename */
+get_filename(const char *testfile,     /* I - Current test file */
+             char       *dst,          /* I - Destination filename */
+            const char *src,           /* I - Source filename */
+             size_t     dstsize)       /* I - Size of destination buffer */
+{
+  char                 *dstptr;        /* Pointer into destination */
+  _cups_globals_t      *cg = _cupsGlobals();
+                                       /* Global data */
 
-         while ((bytes = cupsFileRead(reqfile, buffer, sizeof(buffer))) > 0)
-           length += (size_t)bytes;
 
-         cupsFileClose(reqfile);
-       }
-      }
+  if (*src == '<' && src[strlen(src) - 1] == '>')
+  {
+   /*
+    * Map <filename> to CUPS_DATADIR/ipptool/filename...
+    */
 
-     /*
-      * Send the request...
-      */
+    snprintf(dst, dstsize, "%s/ipptool/%s", cg->cups_datadir, src + 1);
+    dstptr = dst + strlen(dst) - 1;
+    if (*dstptr == '>')
+      *dstptr = '\0';
+  }
+  else if (!access(src, R_OK) || *src == '/'
+#ifdef WIN32
+           || (isalpha(*src & 255) && src[1] == ':')
+#endif /* WIN32 */
+           )
+  {
+   /*
+    * Use the path as-is...
+    */
 
-      response    = NULL;
-      repeat_test = 0;
-      prev_pass   = 1;
+    strlcpy(dst, src, dstsize);
+  }
+  else
+  {
+   /*
+    * Make path relative to testfile...
+    */
 
-      if (status != HTTP_STATUS_ERROR)
-      {
-       while (!response && !Cancel && prev_pass)
-       {
-         status = cupsSendRequest(http, request, resource, length);
+    strlcpy(dst, testfile, dstsize);
+    if ((dstptr = strrchr(dst, '/')) != NULL)
+      dstptr ++;
+    else
+      dstptr = dst; /* Should never happen */
 
-#ifdef HAVE_LIBZ
-         if (compression[0])
-           httpSetField(http, HTTP_FIELD_CONTENT_ENCODING, compression);
-#endif /* HAVE_LIBZ */
+    strlcpy(dstptr, src, dstsize - (size_t)(dstptr - dst));
+  }
 
-         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;
+  return (dst);
+}
 
-             cupsFileClose(reqfile);
-           }
-           else
-           {
-             snprintf(buffer, sizeof(buffer), "%s: %s", filename,
-                      strerror(errno));
-             _cupsSetError(IPP_INTERNAL_ERROR, buffer, 0);
 
-             status = HTTP_STATUS_ERROR;
-           }
-         }
+/*
+ * 'get_string()' - Get a pointer to a string value or the portion of interest.
+ */
 
-        /*
-         * Get the server's response...
-         */
+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 */
 
-         if (!Cancel && status != HTTP_STATUS_ERROR)
-         {
-           response = cupsGetResponse(http, resource);
-           status   = httpGetStatus(http);
-         }
 
-         if (!Cancel && status == HTTP_STATUS_ERROR && http->error != EINVAL &&
-#ifdef WIN32
-             http->error != WSAETIMEDOUT)
-#else
-             http->error != ETIMEDOUT)
-#endif /* WIN32 */
-         {
-           if (httpReconnect(http))
-             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);
+  value = ippGetString(attr, element, NULL);
 
-           if (status == HTTP_STATUS_UNAUTHORIZED)
-             continue;
+  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';
 
-           break;
-         }
-       }
-      }
+    ptr = buffer + strlen(buffer) - 1;
+    if (ptr >= buffer && *ptr == '.')
+      *ptr = '\0';                     /* Drop trailing "." */
 
-      if (!Cancel && status == HTTP_STATUS_ERROR && http->error != EINVAL &&
-#ifdef WIN32
-         http->error != WSAETIMEDOUT)
-#else
-         http->error != ETIMEDOUT)
-#endif /* WIN32 */
-      {
-       if (httpReconnect(http))
-         prev_pass = 0;
-      }
-      else if (status == HTTP_STATUS_ERROR)
-      {
-        if (!Cancel)
-          httpReconnect(http);
+    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';
 
-       prev_pass = 0;
-      }
-      else if (status != HTTP_STATUS_OK)
-      {
-        httpFlush(http);
-        prev_pass = 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)
+    {
      /*
-      * Check results of request...
+      * Bad URI...
       */
 
-      cupsArrayClear(errors);
+      buffer[0] = '\0';
+    }
+    else
+    {
+     /*
+      * Normalize URI with no trailing dot...
+      */
 
-      if (http->version != HTTP_1_1)
-       add_stringf(errors, "Bad HTTP version (%d.%d)", http->version / 100,
-                   http->version % 100);
+      if ((ptr = hostname + strlen(hostname) - 1) >= hostname && *ptr == '.')
+       *ptr = '\0';
 
-      if (!response)
-      {
-       /*
-        * No response, log error...
-        */
+      httpAssembleURI(HTTP_URI_CODING_ALL, buffer, (int)bufsize, scheme, userpass, hostname, port, resource);
+    }
 
-       add_stringf(errors, "IPP request failed with status %s (%s)",
-                   ippErrorString(cupsLastError()),
-                   cupsLastErrorString());
-      }
-      else
-      {
-       /*
-        * Collect common attribute values...
-        */
+    return (buffer);
+  }
+  else
+    return (value);
+}
 
-       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 = ippFindAttribute(response, "job-uri",
-                                       IPP_TAG_URI)) != NULL)
-         set_variable(outfile, vars, "job-uri", attrptr->values[0].string.text);
+/*
+ * 'init_data()' - Initialize test data.
+ */
 
-       if ((attrptr = ippFindAttribute(response, "notify-subscription-id",
-                                       IPP_TAG_INTEGER)) != NULL)
-       {
-         snprintf(temp, sizeof(temp), "%d", attrptr->values[0].integer);
-         set_variable(outfile, vars, "notify-subscription-id", temp);
-       }
+static void
+init_data(_cups_testdata_t *data)      /* I - Data */
+{
+  memset(data, 0, sizeof(_cups_testdata_t));
+
+  data->output       = _CUPS_OUTPUT_LIST;
+  data->outfile      = cupsFileStdout();
+  data->family       = AF_UNSPEC;
+  data->def_transfer = _CUPS_TRANSFER_AUTO;
+  data->def_version  = 11;
+  data->errors       = cupsArrayNew3(NULL, NULL, NULL, 0, (cups_acopy_func_t)strdup, (cups_afree_func_t)free);
+  data->pass         = 1;
+  data->prev_pass    = 1;
+  data->request_id   = (CUPS_RAND() % 1000) * 137 + 1;
+  data->show_header  = 1;
+}
 
-       /*
-        * 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 (version &&
-           (response->request.status.version[0] != (version / 10) ||
-            response->request.status.version[1] != (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)
-         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)
-         add_stringf(errors,
-                     "Missing first attribute \"attributes-charset "
-                     "(charset)\" in group operation-attributes-tag "
-                     "(RFC 2911 section 3.1.4).");
-       else
-       {
-         if (!attrptr->name ||
-             attrptr->value_tag != IPP_TAG_CHARSET ||
-             attrptr->group_tag != IPP_TAG_OPERATION ||
-             attrptr->num_values != 1 ||
-             strcmp(attrptr->name, "attributes-charset"))
-           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)
-           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"))
-           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)
-           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)
-           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)
-           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)
-           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));
-        }
+/*
+ * 'iso_date()' - Return an ISO 8601 date/time string for the given IPP dateTime
+ *                value.
+ */
 
-       if ((attrptr = ippFindAttribute(response, "detailed-status-message",
-                                        IPP_TAG_ZERO)) != NULL)
-       {
-         if (attrptr->value_tag != IPP_TAG_TEXT)
-           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)
-           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)
-           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)
-           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));
-        }
+static char *                          /* O - ISO 8601 date/time string */
+iso_date(const ipp_uchar_t *date)      /* I - IPP (RFC 1903) date/time value */
+{
+  time_t       utctime;                /* UTC time since 1970 */
+  struct tm    *utcdate;               /* UTC date/time */
+  static char  buffer[255];            /* String buffer */
 
-       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)
-         {
-           int out_of_order = 0;       /* Are attribute groups out-of-order? */
-           cupsArrayClear(a);
+  utctime = ippDateToTime(date);
+  utcdate = gmtime(&utctime);
 
-            switch (attrptr->group_tag)
-            {
-              case IPP_TAG_ZERO :
-                  break;
+  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);
 
-              case IPP_TAG_OPERATION :
-                  out_of_order = 1;
-                  break;
+  return (buffer);
+}
 
-              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;
+/*
+ * 'pause_message()' - Display the message and pause until the user presses a key.
+ */
 
-              case IPP_TAG_SUBSCRIPTION :
-                  if (group > attrptr->group_tag &&
-                      group != IPP_TAG_DOCUMENT)
-                   out_of_order = 1;
-                  break;
+static void
+pause_message(const char *message)     /* I - Message */
+{
+#ifdef WIN32
+  HANDLE       tty;                    /* Console handle */
+  DWORD                mode;                   /* Console mode */
+  char         key;                    /* Key press */
+  DWORD                bytes;                  /* Bytes read for key press */
 
-              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));
+ /*
+  * Disable input echo and set raw input...
+  */
 
-           if (attrptr->group_tag != IPP_TAG_ZERO)
-             group = attrptr->group_tag;
-         }
+  if ((tty = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE)
+    return;
 
-         validate_attr(outfile, errors, attrptr);
+  if (!GetConsoleMode(tty, &mode))
+    return;
 
-          if (attrptr->name)
-          {
-            if (cupsArrayFind(a, attrptr->name))
-              add_stringf(errors, "Duplicate \"%s\" attribute in %s group",
-                         attrptr->name, ippTagString(group));
+  if (!SetConsoleMode(tty, 0))
+    return;
 
-            cupsArrayAdd(a, attrptr->name);
-          }
-       }
+#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 */
 
-        cupsArrayDelete(a);
 
-       /*
-        * Now check the test-defined expected status-code and attribute
-        * values...
-        */
+ /*
+  * Disable input echo and set raw input...
+  */
 
-       for (i = 0; i < num_statuses; i ++)
-       {
-         if (statuses[i].if_defined &&
-             !get_variable(vars, statuses[i].if_defined))
-           continue;
+  if ((tty = open("/dev/tty", O_RDONLY)) < 0)
+    return;
 
-         if (statuses[i].if_not_defined &&
-             get_variable(vars, statuses[i].if_not_defined))
-           continue;
+  if (tcgetattr(tty, &original))
+  {
+    close(tty);
+    return;
+  }
 
-         if (response->request.status.status_code == statuses[i].status)
-         {
-           if (statuses[i].repeat_match &&
-               repeat_count < statuses[i].repeat_limit)
-             repeat_test = 1;
+  noecho = original;
+  noecho.c_lflag &= (tcflag_t)~(ICANON | ECHO | ECHOE | ISIG);
 
-            if (statuses[i].define_match)
-              set_variable(outfile, vars, statuses[i].define_match, "1");
+  if (tcsetattr(tty, TCSAFLUSH, &noecho))
+  {
+    close(tty);
+    return;
+  }
+#endif /* WIN32 */
 
-            break;
-         }
-         else
-         {
-           if (statuses[i].repeat_no_match &&
-               repeat_count < statuses[i].repeat_limit)
-             repeat_test = 1;
+ /*
+  * Display the prompt...
+  */
 
-            if (statuses[i].define_no_match)
-            {
-              set_variable(outfile, vars, statuses[i].define_no_match, "1");
-              break;
-            }
-          }
-       }
+  cupsFilePrintf(cupsFileStdout(), "%s\n---- PRESS ANY KEY ----", message);
 
-       if (i == num_statuses && num_statuses > 0)
-       {
-         for (i = 0; i < num_statuses; i ++)
-         {
-           if (statuses[i].if_defined &&
-               !get_variable(vars, statuses[i].if_defined))
-             continue;
-
-           if (statuses[i].if_not_defined &&
-               get_variable(vars, statuses[i].if_not_defined))
-             continue;
-
-            if (!statuses[i].repeat_match)
-             add_stringf(errors, "EXPECTED: STATUS %s (got %s)",
-                         ippErrorString(statuses[i].status),
-                         ippErrorString(cupsLastError()));
-         }
+#ifdef WIN32
+ /*
+  * Read a key...
+  */
 
-         if ((attrptr = ippFindAttribute(response, "status-message",
-                                         IPP_TAG_TEXT)) != NULL)
-           add_stringf(errors, "status-message=\"%s\"",
-                       attrptr->values[0].string.text);
-        }
+  ReadFile(tty, &key, 1, &bytes, NULL);
 
-       for (i = num_expects, expect = expects; i > 0; i --, expect ++)
-       {
-         if (expect->if_defined && !get_variable(vars, expect->if_defined))
-           continue;
+ /*
+  * Cleanup...
+  */
 
-         if (expect->if_not_defined &&
-             get_variable(vars, expect->if_not_defined))
-           continue;
+  SetConsoleMode(tty, mode);
 
-          found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO);
+#else
+ /*
+  * Read a key...
+  */
 
-          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));
-               }
-             }
+  read(tty, &key, 1);
 
-             if (expect->repeat_no_match &&
-                 repeat_count < expect->repeat_limit)
-               repeat_test = 1;
+ /*
+  * Cleanup...
+  */
 
-             break;
-           }
+  tcsetattr(tty, TCSAFLUSH, &original);
+  close(tty);
+#endif /* WIN32 */
 
-           if (found)
-             ippAttributeString(found, buffer, sizeof(buffer));
+ /*
+  * Erase the "press any key" prompt...
+  */
 
-            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)
-             {
-               add_stringf(errors, "EXPECTED: %s WITH-VALUES-FROM %s", expect->name, expect->with_value_from);
+  cupsFilePuts(cupsFileStdout(), "\r                       \r");
+}
 
-               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;
+/*
+ * 'print_attr()' - Print an attribute on the screen.
+ */
 
-             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)
-             {
-               if (expect->with_flags & _CUPS_WITH_REGEX)
-                 add_stringf(errors, "EXPECTED: %s %s /%s/",
-                             expect->name,
-                             (expect->with_flags & _CUPS_WITH_ALL) ?
-                                 "WITH-ALL-VALUES" : "WITH-VALUE",
-                             expect->with_value);
-               else
-                 add_stringf(errors, "EXPECTED: %s %s \"%s\"",
-                             expect->name,
-                             (expect->with_flags & _CUPS_WITH_ALL) ?
-                                 "WITH-ALL-VALUES" : "WITH-VALUE",
-                             expect->with_value);
-
-               with_value(outfile, errors, expect->with_value, expect->with_flags, found,
-                          buffer, sizeof(buffer));
-             }
+static void
+print_attr(cups_file_t     *outfile,   /* I  - Output file */
+           int             output,     /* I  - Output format */
+           ipp_attribute_t *attr,      /* I  - Attribute to print */
+           ipp_tag_t       *group)     /* IO - Current group */
+{
+  int                  i,              /* Looping var */
+                       count;          /* Number of values */
+  ipp_attribute_t      *colattr;       /* Collection attribute */
 
-             if (expect->repeat_no_match &&
-                 repeat_count < expect->repeat_limit)
-               repeat_test = 1;
 
-             break;
-           }
+  if (output == _CUPS_OUTPUT_PLIST)
+  {
+    if (!ippGetName(attr) || (group && *group != ippGetGroupTag(attr)))
+    {
+      if (ippGetGroupTag(attr) != IPP_TAG_ZERO)
+      {
+       cupsFilePuts(outfile, "</dict>\n");
+       cupsFilePuts(outfile, "<dict>\n");
+      }
 
-           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 (group)
+        *group = ippGetGroupTag(attr);
+    }
 
-             if (expect->repeat_no_match &&
-                 repeat_count < expect->repeat_limit)
-               repeat_test = 1;
+    if (!ippGetName(attr))
+      return;
 
-             break;
-           }
+    print_xml_string(outfile, "key", ippGetName(attr));
+    if ((count = ippGetCount(attr)) > 1)
+      cupsFilePuts(outfile, "<array>\n");
 
-           if (found && expect->same_count_as)
-           {
-             attrptr = ippFindAttribute(response, expect->same_count_as,
-                                        IPP_TAG_ZERO);
+    switch (ippGetValueTag(attr))
+    {
+      case IPP_TAG_INTEGER :
+      case IPP_TAG_ENUM :
+         for (i = 0; i < count; i ++)
+           cupsFilePrintf(outfile, "<integer>%d</integer>\n", ippGetInteger(attr, i));
+         break;
 
-             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);
-               }
+      case IPP_TAG_BOOLEAN :
+         for (i = 0; i < count; i ++)
+           cupsFilePuts(outfile, ippGetBoolean(attr, i) ? "<true />\n" : "<false />\n");
+         break;
 
-               if (expect->repeat_no_match &&
-                   repeat_count < expect->repeat_limit)
-                 repeat_test = 1;
+      case IPP_TAG_RANGE :
+         for (i = 0; i < count; i ++)
+         {
+           int lower, upper;           /* Lower and upper ranges */
 
-               break;
-             }
-           }
+           lower = ippGetRange(attr, i, &upper);
+           cupsFilePrintf(outfile, "<dict><key>lower</key><integer>%d</integer><key>upper</key><integer>%d</integer></dict>\n", lower, upper);
+         }
+         break;
 
-           if (found && expect->define_match)
-             set_variable(outfile, vars, expect->define_match, "1");
+      case IPP_TAG_RESOLUTION :
+         for (i = 0; i < count; i ++)
+         {
+           int         xres, yres;     /* Resolution values */
+           ipp_res_t   units;          /* Resolution units */
 
-           if (found && expect->define_value)
-           {
-             if (!expect->with_value)
-             {
-               int last = ippGetCount(found) - 1;
-                                         /* Last element in attribute */
+            xres = ippGetResolution(attr, i, &yres, &units);
+           cupsFilePrintf(outfile, "<dict><key>xres</key><integer>%d</integer><key>yres</key><integer>%d</integer><key>units</key><string>%s</string></dict>\n", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+         }
+         break;
 
-               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;
-               }
-             }
+      case IPP_TAG_DATE :
+         for (i = 0; i < count; i ++)
+           cupsFilePrintf(outfile, "<date>%s</date>\n", iso_date(ippGetDate(attr, i)));
+         break;
 
-             set_variable(outfile, vars, expect->define_value, buffer);
-           }
+      case IPP_TAG_STRING :
+          for (i = 0; i < count; i ++)
+          {
+            int                datalen;        /* Length of data */
+            void       *data = ippGetOctetString(attr, i, &datalen);
+                                       /* Data */
+           char        buffer[IPP_MAX_LENGTH * 5 / 4 + 1];
+                                       /* Base64 output buffer */
 
-           if (found && expect->repeat_match &&
-               repeat_count < expect->repeat_limit)
-             repeat_test = 1;
+           cupsFilePrintf(outfile, "<data>%s</data>\n", httpEncode64_2(buffer, sizeof(buffer), data, datalen));
           }
-          while (expect->expect_all && (found = ippFindNextAttribute(response, expect->name, IPP_TAG_ZERO)) != NULL);
-       }
-      }
-
-     /*
-      * If we are going to repeat this test, sleep 1 second so we don't flood
-      * the printer with requests...
-      */
+          break;
 
-      if (repeat_test)
-      {
-       if (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != stdout))
-        {
-          printf("%04d]\n", repeat_count);
-          fflush(stdout);
+      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 :
+         for (i = 0; i < count; i ++)
+           print_xml_string(outfile, "string", ippGetString(attr, i, NULL));
+         break;
 
-         if (num_displayed > 0)
+      case IPP_TAG_TEXTLANG :
+      case IPP_TAG_NAMELANG :
+         for (i = 0; i < count; i ++)
          {
-           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(stdout, _CUPS_OUTPUT_TEST, attrptr, NULL);
-                   break;
-                 }
-               }
-             }
-           }
+           const char *s,              /* String */
+                       *lang;          /* Language */
+
+            s = ippGetString(attr, i, &lang);
+           cupsFilePuts(outfile, "<dict><key>language</key><string>");
+           print_xml_string(outfile, NULL, lang);
+           cupsFilePuts(outfile, "</string><key>string</key><string>");
+           print_xml_string(outfile, NULL, s);
+           cupsFilePuts(outfile, "</string></dict>\n");
          }
-        }
-
-        sleep((unsigned)repeat_interval);
-        repeat_interval = _cupsNextDelay(repeat_interval, &repeat_prev);
-
-       if (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != stdout))
-       {
-         printf("    %-68.68s [", name);
-         fflush(stdout);
-       }
-      }
-    }
-    while (repeat_test);
+         break;
 
-    ippDelete(request);
+      case IPP_TAG_BEGIN_COLLECTION :
+         for (i = 0; i < count; i ++)
+         {
+           ipp_t *col = ippGetCollection(attr, i);
+                                       /* Collection value */
 
-    request = NULL;
+           cupsFilePuts(outfile, "<dict>\n");
+           for (colattr = ippFirstAttribute(col); colattr; colattr = ippNextAttribute(col))
+             print_attr(outfile, output, colattr, NULL);
+           cupsFilePuts(outfile, "</dict>\n");
+         }
+         break;
 
-    if (cupsArrayCount(errors) > 0)
-      prev_pass = pass = 0;
+      default :
+         cupsFilePrintf(outfile, "<string>&lt;&lt;%s&gt;&gt;</string>\n", ippTagString(ippGetValueTag(attr)));
+         break;
+    }
 
-    if (prev_pass)
-      PassCount ++;
-    else
-      FailCount ++;
+    if (count > 1)
+      cupsFilePuts(outfile, "</array>\n");
+  }
+  else
+  {
+    char       buffer[131072];         /* Value buffer */
 
-    if (Output == _CUPS_OUTPUT_PLIST)
+    if (output == _CUPS_OUTPUT_TEST)
     {
-      fputs("<key>Successful</key>\n", outfile);
-      fputs(prev_pass ? "<true />\n" : "<false />\n", outfile);
-      fputs("<key>StatusCode</key>\n", outfile);
-      print_xml_string(outfile, "string", ippErrorString(cupsLastError()));
-      fputs("<key>ResponseAttributes</key>\n", outfile);
-      fputs("<array>\n", outfile);
-      fputs("<dict>\n", outfile);
-      for (attrptr = response ? response->attrs : NULL,
-               group = attrptr ? attrptr->group_tag : IPP_TAG_ZERO;
-          attrptr;
-          attrptr = attrptr->next)
-       print_attr(outfile, Output, attrptr, &group);
-      fputs("</dict>\n", outfile);
-      fputs("</array>\n", outfile);
+      if (!ippGetName(attr))
+      {
+        cupsFilePuts(outfile, "        -- separator --\n");
+        return;
+      }
+
+      cupsFilePrintf(outfile, "        %s (%s%s) = ", ippGetName(attr), ippGetCount(attr) > 1 ? "1setOf " : "", ippTagString(ippGetValueTag(attr)));
     }
 
-    if (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != stdout))
-    {
-      puts(prev_pass ? "PASS]" : "FAIL]");
+    ippAttributeString(attr, buffer, sizeof(buffer));
+    cupsFilePrintf(outfile, "%s\n", buffer);
+  }
+}
 
-      if (!prev_pass || (Verbosity && response))
-      {
-       printf("        RECEIVED: %lu bytes in response\n",
-              (unsigned long)ippLength(response));
-       printf("        status-code = %s (%s)\n", ippErrorString(cupsLastError()),
-              cupsLastErrorString());
 
-        if (Verbosity && response)
-        {
-         for (attrptr = response->attrs;
-              attrptr != NULL;
-              attrptr = attrptr->next)
-           print_attr(stdout, _CUPS_OUTPUT_TEST, attrptr, NULL);
-       }
-      }
-    }
-    else if (!prev_pass && Output != _CUPS_OUTPUT_QUIET)
-      fprintf(stderr, "%s\n", cupsLastErrorString());
+/*
+ * 'print_csv()' - Print a line of CSV text.
+ */
 
-    if (prev_pass && Output >= _CUPS_OUTPUT_LIST && !Verbosity &&
-        num_displayed > 0)
-    {
-      size_t   width;                  /* Length of value */
+static void
+print_csv(
+    _cups_testdata_t *data,            /* I - Test data */
+    ipp_t            *ipp,             /* I - Response message */
+    ipp_attribute_t  *attr,            /* I - First attribute for line */
+    int              num_displayed,    /* I - Number of attributes to display */
+    char             **displayed,      /* I - Attributes to display */
+    size_t           *widths)          /* I - Column widths */
+{
+  int          i;                      /* Looping var */
+  size_t       maxlength;              /* Max length of all columns */
+  char         *buffer,                /* String buffer */
+               *bufptr;                /* Pointer into buffer */
+  ipp_attribute_t *current;            /* Current attribute */
 
-      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 = ippAttributeString(attrptr, NULL, 0);
-         if (width > widths[i])
-           widths[i] = width;
-       }
-      }
+ /*
+  * Get the maximum string length we have to show and allocate...
+  */
 
-      if (Output == _CUPS_OUTPUT_CSV)
-       print_csv(outfile, NULL, num_displayed, displayed, widths);
-      else
-       print_line(outfile, NULL, num_displayed, displayed, widths);
+  for (i = 1, maxlength = widths[0]; i < num_displayed; i ++)
+    if (widths[i] > maxlength)
+      maxlength = widths[i];
 
-      attrptr = response->attrs;
+  maxlength += 2;
 
-      while (attrptr)
-      {
-       while (attrptr && attrptr->group_tag <= IPP_TAG_OPERATION)
-         attrptr = attrptr->next;
+  if ((buffer = malloc(maxlength)) == NULL)
+    return;
 
-       if (attrptr)
-       {
-         if (Output == _CUPS_OUTPUT_CSV)
-           print_csv(outfile, attrptr, num_displayed, displayed, widths);
-         else
-           print_line(outfile, attrptr, num_displayed, displayed, widths);
+ /*
+  * Loop through the attributes to display...
+  */
 
-         while (attrptr && attrptr->group_tag > IPP_TAG_OPERATION)
-           attrptr = attrptr->next;
-       }
-      }
-    }
-    else if (!prev_pass)
+  if (attr)
+  {
+    for (i = 0; i < num_displayed; i ++)
     {
-      if (Output == _CUPS_OUTPUT_PLIST)
-      {
-       fputs("<key>Errors</key>\n", outfile);
-       fputs("<array>\n", outfile);
+      if (i)
+        cupsFilePutChar(data->outfile, ',');
 
-       for (error = (char *)cupsArrayFirst(errors);
-            error;
-            error = (char *)cupsArrayNext(errors))
-         print_xml_string(outfile, "string", error);
+      buffer[0] = '\0';
 
-       fputs("</array>\n", outfile);
+      for (current = attr; current; current = ippNextAttribute(ipp))
+      {
+        if (!ippGetName(current))
+          break;
+        else if (!strcmp(ippGetName(current), displayed[i]))
+        {
+          ippAttributeString(current, buffer, maxlength);
+          break;
+        }
       }
 
-      if (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != stdout))
+      if (strchr(buffer, ',') != NULL || strchr(buffer, '\"') != NULL ||
+         strchr(buffer, '\\') != NULL)
       {
-       for (error = (char *)cupsArrayFirst(errors);
-            error;
-            error = (char *)cupsArrayNext(errors))
-         printf("        %s\n", error);
+        cupsFilePutChar(cupsFileStdout(), '\"');
+        for (bufptr = buffer; *bufptr; bufptr ++)
+        {
+          if (*bufptr == '\\' || *bufptr == '\"')
+            cupsFilePutChar(cupsFileStdout(), '\\');
+          cupsFilePutChar(cupsFileStdout(), *bufptr);
+        }
+        cupsFilePutChar(cupsFileStdout(), '\"');
       }
+      else
+        cupsFilePuts(data->outfile, buffer);
     }
-
-    if (num_displayed > 0 && !Verbosity && response && (Output == _CUPS_OUTPUT_TEST || (Output == _CUPS_OUTPUT_PLIST && outfile != stdout)))
+    cupsFilePutChar(cupsFileStdout(), '\n');
+  }
+  else
+  {
+    for (i = 0; i < num_displayed; i ++)
     {
-      for (attrptr = response->attrs;
-          attrptr != NULL;
-          attrptr = attrptr->next)
-      {
-       if (attrptr->name)
-       {
-         for (i = 0; i < num_displayed; i ++)
-         {
-           if (!strcmp(displayed[i], attrptr->name))
-           {
-             print_attr(outfile, Output, attrptr, NULL);
-             break;
-           }
-         }
-       }
-      }
+      if (i)
+        cupsFilePutChar(cupsFileStdout(), ',');
+
+      cupsFilePuts(data->outfile, displayed[i]);
     }
+    cupsFilePutChar(cupsFileStdout(), '\n');
+  }
 
-    skip_error:
+  free(buffer);
+}
 
-    if (Output == _CUPS_OUTPUT_PLIST)
-      fputs("</dict>\n", outfile);
 
-    fflush(stdout);
+/*
+ * 'print_fatal_error()' - Print a fatal error message.
+ */
 
-    ippDelete(response);
-    response = NULL;
+static void
+print_fatal_error(
+    _cups_testdata_t *data,            /* I - Test data */
+    const char       *s,               /* I - Printf-style format string */
+    ...)                               /* I - Additional arguments as needed */
+{
+  char         buffer[10240];          /* Format buffer */
+  va_list      ap;                     /* Pointer to arguments */
 
-    for (i = 0; i < num_statuses; i ++)
-    {
-      if (statuses[i].if_defined)
-        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;
 
-    for (i = num_expects, expect = expects; i > 0; i --, expect ++)
-    {
-      free(expect->name);
-      if (expect->of_type)
-        free(expect->of_type);
-      if (expect->same_count_as)
-        free(expect->same_count_as);
-      if (expect->if_defined)
-        free(expect->if_defined);
-      if (expect->if_not_defined)
-        free(expect->if_not_defined);
-      if (expect->with_value)
-        free(expect->with_value);
-      if (expect->define_match)
-        free(expect->define_match);
-      if (expect->define_no_match)
-        free(expect->define_no_match);
-      if (expect->define_value)
-        free(expect->define_value);
-    }
-    num_expects = 0;
+ /*
+  * Format the error message...
+  */
 
-    for (i = 0; i < num_displayed; i ++)
-      free(displayed[i]);
-    num_displayed = 0;
+  va_start(ap, s);
+  vsnprintf(buffer, sizeof(buffer), s, ap);
+  va_end(ap);
+
+ /*
+  * Then output it...
+  */
 
-    if (!ignore_errors && !prev_pass)
-      break;
+  if (data->output == _CUPS_OUTPUT_PLIST)
+  {
+    print_xml_header(data);
+    print_xml_trailer(data, 0, buffer);
   }
 
-  test_exit:
+  _cupsLangPrintf(stderr, "ipptool: %s", buffer);
+}
+
 
-  cupsArrayDelete(errors);
+/*
+ * 'print_ippserver_attr()' - Print a attribute suitable for use by ippserver.
+ */
 
-  if (fp)
-    fclose(fp);
+static void
+print_ippserver_attr(
+    _cups_testdata_t *data,            /* I - Test data */
+    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 */
 
-  httpClose(http);
-  ippDelete(request);
-  ippDelete(response);
 
-  for (i = 0; i < num_statuses; i ++)
-  {
-    if (statuses[i].if_defined)
-      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);
-  }
+  if (indent == 0)
+    cupsFilePrintf(data->outfile, "ATTR %s %s", ippTagString(ippGetValueTag(attr)), ippGetName(attr));
+  else
+    cupsFilePrintf(data->outfile, "%*sMEMBER %s %s", indent, "", ippTagString(ippGetValueTag(attr)), ippGetName(attr));
 
-  for (i = num_expects, expect = expects; i > 0; i --, expect ++)
+  switch (ippGetValueTag(attr))
   {
-    free(expect->name);
-    if (expect->of_type)
-      free(expect->of_type);
-    if (expect->same_count_as)
-      free(expect->same_count_as);
-    if (expect->if_defined)
-      free(expect->if_defined);
-    if (expect->if_not_defined)
-      free(expect->if_not_defined);
-    if (expect->with_value)
-      free(expect->with_value);
-    if (expect->define_match)
-      free(expect->define_match);
-    if (expect->define_no_match)
-      free(expect->define_no_match);
-    if (expect->define_value)
-      free(expect->define_value);
-  }
+    case IPP_TAG_INTEGER :
+    case IPP_TAG_ENUM :
+       for (i = 0; i < count; i ++)
+         cupsFilePrintf(data->outfile, "%s%d", i ? "," : " ", ippGetInteger(attr, i));
+       break;
 
-  for (i = 0; i < num_displayed; i ++)
-    free(displayed[i]);
+    case IPP_TAG_BOOLEAN :
+       cupsFilePuts(data->outfile, ippGetBoolean(attr, 0) ? " true" : " false");
 
-  return (pass);
-}
+       for (i = 1; i < count; i ++)
+         cupsFilePuts(data->outfile, ippGetBoolean(attr, 1) ? ",true" : ",false");
+       break;
 
+    case IPP_TAG_RANGE :
+       for (i = 0; i < count; i ++)
+       {
+         int upper, lower = ippGetRange(attr, i, &upper);
 
-/*
- * 'expand_variables()' - Expand variables in a string.
- */
+         cupsFilePrintf(data->outfile, "%s%d-%d", i ? "," : " ", lower, upper);
+       }
+       break;
 
-static void
-expand_variables(_cups_vars_t *vars,   /* I - Variables */
-                 char         *dst,    /* I - Destination string buffer */
-                const char   *src,     /* I - Source string */
-                size_t       dstsize)  /* I - Size of destination buffer */
-{
-  char         *dstptr,                /* Pointer into destination */
-               *dstend,                /* End of destination */
-               temp[256],              /* Temporary string */
-               *tempptr;               /* Pointer into temporary string */
-  const char   *value;                 /* Value to substitute */
+    case IPP_TAG_RESOLUTION :
+       for (i = 0; i < count; i ++)
+       {
+         ipp_res_t units;
+         int yres, xres = ippGetResolution(attr, i, &yres, &units);
 
+         cupsFilePrintf(data->outfile, "%s%dx%d%s", i ? "," : " ", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
+       }
+       break;
 
-  dstptr = dst;
-  dstend = dst + dstsize - 1;
+    case IPP_TAG_DATE :
+       for (i = 0; i < count; i ++)
+         cupsFilePrintf(data->outfile, "%s%s", i ? "," : " ", iso_date(ippGetDate(attr, i)));
+       break;
 
-  while (*src && dstptr < dstend)
-  {
-    if (*src == '$')
-    {
-     /*
-      * Substitute a string/number...
-      */
+    case IPP_TAG_STRING :
+       for (i = 0; i < count; i ++)
+       {
+         int len;
+         const char *s = (const char *)ippGetOctetString(attr, i, &len);
 
-      if (!strncmp(src, "$$", 2))
-      {
-        value = "$";
-       src   += 2;
-      }
-      else if (!strncmp(src, "$ENV[", 5))
-      {
-       strlcpy(temp, src + 5, sizeof(temp));
+         cupsFilePuts(data->outfile, i ? "," : " ");
+         print_ippserver_string(data, s, (size_t)len);
+       }
+       break;
 
-       for (tempptr = temp; *tempptr; tempptr ++)
-         if (*tempptr == ']')
-           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);
 
-        if (*tempptr)
-         *tempptr++ = '\0';
+         cupsFilePuts(data->outfile, i ? "," : " ");
+         print_ippserver_string(data, s, strlen(s));
+       }
+       break;
 
-       value = getenv(temp);
-        src   += tempptr - temp + 5;
-      }
-      else
-      {
-        if (src[1] == '{')
+    case IPP_TAG_BEGIN_COLLECTION :
+       for (i = 0; i < count; i ++)
        {
-         src += 2;
-         strlcpy(temp, src, sizeof(temp));
-         if ((tempptr = strchr(temp, '}')) != NULL)
-           *tempptr = '\0';
-         else
-           tempptr = temp + strlen(temp);
+         ipp_t *col = ippGetCollection(attr, i);
+
+         cupsFilePuts(data->outfile, i ? ",{\n" : " {\n");
+         for (colattr = ippFirstAttribute(col); colattr; colattr = ippNextAttribute(col))
+           print_ippserver_attr(data, colattr, indent + 4);
+         cupsFilePrintf(data->outfile, "%*s}", indent, "");
        }
-       else
-       {
-         strlcpy(temp, src + 1, sizeof(temp));
+       break;
 
-         for (tempptr = temp; *tempptr; tempptr ++)
-           if (!isalnum(*tempptr & 255) && *tempptr != '-' && *tempptr != '_')
-             break;
+    default :
+        /* Out-of-band value */
+       break;
+  }
 
-         if (*tempptr)
-           *tempptr = '\0';
-        }
+  cupsFilePuts(data->outfile, "\n");
+}
 
-       if (!strcmp(temp, "uri"))
-         value = vars->uri;
-       else if (!strcmp(temp, "filename"))
-         value = vars->filename;
-       else if (!strcmp(temp, "scheme") || !strcmp(temp, "method"))
-         value = vars->scheme;
-       else if (!strcmp(temp, "username"))
-         value = vars->userpass;
-       else if (!strcmp(temp, "hostname"))
-         value = vars->hostname;
-       else if (!strcmp(temp, "port"))
-       {
-         snprintf(temp, sizeof(temp), "%d", vars->port);
-         value = temp;
-       }
-       else if (!strcmp(temp, "resource"))
-         value = vars->resource;
-       else if (!strcmp(temp, "user"))
-         value = cupsUser();
-       else
-         value = get_variable(vars, temp);
 
-        src += tempptr - temp + 1;
-      }
+/*
+ * 'print_ippserver_string()' - Print a string suitable for use by ippserver.
+ */
 
-      if (value)
-      {
-        strlcpy(dstptr, value, (size_t)(dstend - dstptr + 1));
-       dstptr += strlen(dstptr);
-      }
-    }
-    else
-      *dstptr++ = *src++;
-  }
+static void
+print_ippserver_string(
+    _cups_testdata_t *data,            /* I - Test data */
+    const char       *s,               /* I - String to print */
+    size_t           len)              /* I - Length of string */
+{
+  cupsFilePutChar(data->outfile, '\"');
+  while (len > 0)
+  {
+    if (*s == '\"')
+      cupsFilePutChar(data->outfile, '\\');
+    cupsFilePutChar(data->outfile, *s);
 
-  *dstptr = '\0';
+    s ++;
+    len --;
+  }
+  cupsFilePutChar(data->outfile, '\"');
 }
 
 
 /*
- * 'expect_matches()' - Return true if the tag matches the specification.
+ * 'print_line()' - Print a line of formatted or CSV text.
  */
 
-static int                             /* O - 1 if matches, 0 otherwise */
-expect_matches(
-    _cups_expect_t *expect,            /* I - Expected attribute */
-    ipp_tag_t      value_tag)          /* I - Value tag for attribute */
+static void
+print_line(
+    _cups_testdata_t *data,            /* I - Test data */
+    ipp_t            *ipp,             /* I - Response message */
+    ipp_attribute_t  *attr,            /* I - First attribute for line */
+    int              num_displayed,    /* I - Number of attributes to display */
+    char             **displayed,      /* I - Attributes to display */
+    size_t           *widths)          /* I - Column widths */
 {
-  int  match;                          /* Match? */
-  char *of_type,                       /* Type name to match */
-       *next,                          /* Next name to match */
-       sep;                            /* Separator character */
+  int          i;                      /* Looping var */
+  size_t       maxlength;              /* Max length of all columns */
+  char         *buffer;                /* String buffer */
+  ipp_attribute_t *current;            /* Current attribute */
 
 
  /*
-  * If we don't expect a particular type, return immediately...
+  * Get the maximum string length we have to show and allocate...
   */
 
-  if (!expect->of_type)
-    return (1);
+  for (i = 1, maxlength = widths[0]; i < num_displayed; i ++)
+    if (widths[i] > maxlength)
+      maxlength = widths[i];
+
+  maxlength += 2;
+
+  if ((buffer = malloc(maxlength)) == NULL)
+    return;
 
  /*
-  * Parse the "of_type" value since the string can contain multiple attribute
-  * types separated by "," or "|"...
+  * Loop through the attributes to display...
   */
 
-  for (of_type = expect->of_type, match = 0; !match && *of_type; of_type = next)
+  if (attr)
   {
-   /*
-    * Find the next separator, and set it (temporarily) to nul if present.
-    */
+    for (i = 0; i < num_displayed; i ++)
+    {
+      if (i)
+        cupsFilePutChar(cupsFileStdout(), ' ');
 
-    for (next = of_type; *next && *next != '|' && *next != ','; next ++);
+      buffer[0] = '\0';
 
-    if ((sep = *next) != '\0')
-      *next = '\0';
+      for (current = attr; current; current = ippNextAttribute(ipp))
+      {
+        if (!ippGetName(current))
+          break;
+        else if (!strcmp(ippGetName(current), displayed[i]))
+        {
+          ippAttributeString(current, buffer, maxlength);
+          break;
+        }
+      }
 
-   /*
-    * Support some meta-types to make it easier to write the test file.
-    */
+      cupsFilePrintf(data->outfile, "%*s", (int)-widths[i], buffer);
+    }
+    cupsFilePutChar(cupsFileStdout(), '\n');
+  }
+  else
+  {
+    for (i = 0; i < num_displayed; i ++)
+    {
+      if (i)
+        cupsFilePutChar(cupsFileStdout(), ' ');
 
-    if (!strcmp(of_type, "text"))
-      match = value_tag == IPP_TAG_TEXTLANG || value_tag == IPP_TAG_TEXT;
-    else if (!strcmp(of_type, "name"))
-      match = value_tag == IPP_TAG_NAMELANG || value_tag == IPP_TAG_NAME;
-    else if (!strcmp(of_type, "collection"))
-      match = value_tag == IPP_TAG_BEGIN_COLLECTION;
-    else
-      match = value_tag == ippTagValue(of_type);
+      cupsFilePrintf(data->outfile, "%*s", (int)-widths[i], displayed[i]);
+    }
+    cupsFilePutChar(cupsFileStdout(), '\n');
 
-   /*
-    * Restore the separator if we have one...
-    */
+    for (i = 0; i < num_displayed; i ++)
+    {
+      if (i)
+       cupsFilePutChar(cupsFileStdout(), ' ');
 
-    if (sep)
-      *next++ = sep;
+      memset(buffer, '-', widths[i]);
+      buffer[widths[i]] = '\0';
+      cupsFilePuts(data->outfile, buffer);
+    }
+    cupsFilePutChar(cupsFileStdout(), '\n');
   }
 
-  return (match);
+  free(buffer);
 }
 
 
 /*
- * 'get_collection()' - Get a collection value from the current test file.
+ * 'print_xml_header()' - Print a standard XML plist header.
  */
 
-static ipp_t *                         /* O  - Collection value */
-get_collection(FILE         *outfile,  /* I  - Output file */
-               _cups_vars_t *vars,     /* I  - Variables */
-               FILE         *fp,       /* I  - File to read from */
-              int          *linenum)   /* IO - Line number */
+static void
+print_xml_header(_cups_testdata_t *data)/* I - Test data */
 {
-  char         token[1024],            /* Token from file */
-               temp[1024],             /* Temporary string */
-               attr[128];              /* Attribute name */
-  ipp_tag_t    value;                  /* Current value type */
-  ipp_t                *col = ippNew();        /* Collection value */
-  ipp_attribute_t *lastcol = NULL;     /* Last collection attribute */
+  if (!data->xml_header)
+  {
+    cupsFilePuts(data->outfile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+    cupsFilePuts(data->outfile, "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n");
+    cupsFilePuts(data->outfile, "<plist version=\"1.0\">\n");
+    cupsFilePuts(data->outfile, "<dict>\n");
+    cupsFilePuts(data->outfile, "<key>ipptoolVersion</key>\n");
+    cupsFilePuts(data->outfile, "<string>" CUPS_SVERSION "</string>\n");
+    cupsFilePuts(data->outfile, "<key>Transfer</key>\n");
+    cupsFilePrintf(data->outfile, "<string>%s</string>\n", data->transfer == _CUPS_TRANSFER_AUTO ? "auto" : data->transfer == _CUPS_TRANSFER_CHUNKED ? "chunked" : "length");
+    cupsFilePuts(data->outfile, "<key>Tests</key>\n");
+    cupsFilePuts(data->outfile, "<array>\n");
+
+    data->xml_header = 1;
+  }
+}
+
+
+/*
+ * 'print_xml_string()' - Print an XML string with escaping.
+ */
 
+static void
+print_xml_string(cups_file_t *outfile, /* I - Test data */
+                const char  *element,  /* I - Element name or NULL */
+                const char  *s)        /* I - String to print */
+{
+  if (element)
+    cupsFilePrintf(outfile, "<%s>", element);
 
-  while (get_token(fp, token, sizeof(token), linenum) != NULL)
+  while (*s)
   {
-    if (!strcmp(token, "}"))
-      break;
-    else if (!strcmp(token, "{") && lastcol)
+    if (*s == '&')
+      cupsFilePuts(outfile, "&amp;");
+    else if (*s == '<')
+      cupsFilePuts(outfile, "&lt;");
+    else if (*s == '>')
+      cupsFilePuts(outfile, "&gt;");
+    else if ((*s & 0xe0) == 0xc0)
     {
      /*
-      * Another collection value
+      * Validate UTF-8 two-byte sequence...
       */
 
-      ipp_t    *subcol = get_collection(outfile, vars, fp, linenum);
-                                       /* Collection value */
-
-      if (subcol)
-        ippSetCollection(col, &lastcol, ippGetCount(lastcol), subcol);
+      if ((s[1] & 0xc0) != 0x80)
+      {
+        cupsFilePutChar(outfile, '?');
+        s ++;
+      }
       else
-       goto col_error;
+      {
+        cupsFilePutChar(outfile, *s++);
+        cupsFilePutChar(outfile, *s);
+      }
     }
-    else if (!_cups_strcasecmp(token, "MEMBER"))
+    else if ((*s & 0xf0) == 0xe0)
     {
      /*
-      * Attribute...
+      * Validate UTF-8 three-byte sequence...
       */
 
-      lastcol = NULL;
-
-      if (!get_token(fp, token, sizeof(token), linenum))
+      if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80)
       {
-       print_fatal_error(outfile, "Missing MEMBER value tag on line %d.", *linenum);
-       goto col_error;
+        cupsFilePutChar(outfile, '?');
+        s += 2;
       }
-
-      if ((value = ippTagValue(token)) == IPP_TAG_ZERO)
+      else
       {
-       print_fatal_error(outfile, "Bad MEMBER value tag \"%s\" on line %d.", token,
-                         *linenum);
-       goto col_error;
+        cupsFilePutChar(outfile, *s++);
+        cupsFilePutChar(outfile, *s++);
+        cupsFilePutChar(outfile, *s);
       }
+    }
+    else if ((*s & 0xf8) == 0xf0)
+    {
+     /*
+      * Validate UTF-8 four-byte sequence...
+      */
 
-      if (!get_token(fp, attr, sizeof(attr), linenum))
+      if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
+          (s[3] & 0xc0) != 0x80)
       {
-       print_fatal_error(outfile, "Missing MEMBER name on line %d.", *linenum);
-       goto col_error;
+        cupsFilePutChar(outfile, '?');
+        s += 3;
       }
-
-      if (!get_token(fp, temp, sizeof(temp), linenum))
+      else
       {
-       print_fatal_error(outfile, "Missing MEMBER value on line %d.", *linenum);
-       goto col_error;
+        cupsFilePutChar(outfile, *s++);
+        cupsFilePutChar(outfile, *s++);
+        cupsFilePutChar(outfile, *s++);
+        cupsFilePutChar(outfile, *s);
       }
+    }
+    else if ((*s & 0x80) || (*s < ' ' && !isspace(*s & 255)))
+    {
+     /*
+      * Invalid control character...
+      */
 
-      expand_variables(vars, token, temp, sizeof(token));
-
-      switch (value)
-      {
-       case IPP_TAG_BOOLEAN :
-           if (!_cups_strcasecmp(token, "true"))
-             ippAddBoolean(col, IPP_TAG_ZERO, attr, 1);
-           else
-             ippAddBoolean(col, IPP_TAG_ZERO, attr, (char)atoi(token));
-           break;
-
-       case IPP_TAG_INTEGER :
-       case IPP_TAG_ENUM :
-           ippAddInteger(col, IPP_TAG_ZERO, value, attr, atoi(token));
-           break;
-
-       case IPP_TAG_RESOLUTION :
-           {
-             int       xres,           /* X resolution */
-                       yres;           /* Y resolution */
-             char      units[6];       /* Units */
-
-             if (sscanf(token, "%dx%d%5s", &xres, &yres, units) != 3 ||
-                 (_cups_strcasecmp(units, "dpi") &&
-                  _cups_strcasecmp(units, "dpc") &&
-                  _cups_strcasecmp(units, "dpcm") &&
-                  _cups_strcasecmp(units, "other")))
-             {
-               print_fatal_error(outfile, "Bad resolution value \"%s\" on line %d.",
-                                 token, *linenum);
-               goto col_error;
-             }
+      cupsFilePutChar(outfile, '?');
+    }
+    else
+      cupsFilePutChar(outfile, *s);
 
-             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, (ipp_res_t)0, xres, yres);
-           }
-           break;
+    s ++;
+  }
 
-       case IPP_TAG_RANGE :
-           {
-             int       lowers[4],      /* Lower value */
-                       uppers[4],      /* Upper values */
-                       num_vals;       /* Number of values */
+  if (element)
+    cupsFilePrintf(outfile, "</%s>\n", element);
+}
 
 
-             num_vals = sscanf(token, "%d-%d,%d-%d,%d-%d,%d-%d",
-                               lowers + 0, uppers + 0,
-                               lowers + 1, uppers + 1,
-                               lowers + 2, uppers + 2,
-                               lowers + 3, uppers + 3);
+/*
+ * 'print_xml_trailer()' - Print the XML trailer with success/fail value.
+ */
 
-             if ((num_vals & 1) || num_vals == 0)
-             {
-               print_fatal_error(outfile, "Bad rangeOfInteger value \"%s\" on line %d.",
-                                 token, *linenum);
-               goto col_error;
-             }
-
-             ippAddRanges(col, IPP_TAG_ZERO, attr, num_vals / 2, lowers,
-                          uppers);
-           }
-           break;
-
-       case IPP_TAG_BEGIN_COLLECTION :
-           if (!strcmp(token, "{"))
-           {
-             ipp_t     *subcol = get_collection(outfile, vars, fp, linenum);
-                                     /* Collection value */
-
-             if (subcol)
-             {
-               lastcol = ippAddCollection(col, IPP_TAG_ZERO, attr, subcol);
-               ippDelete(subcol);
-             }
-             else
-               goto col_error;
-           }
-           else
-           {
-             print_fatal_error(outfile, "Bad collection value on line %d.", *linenum);
-             goto col_error;
-           }
-           break;
-       case IPP_TAG_STRING :
-           ippAddOctetString(col, IPP_TAG_ZERO, attr, token, (int)strlen(token));
-           break;
-
-       default :
-           if (!strchr(token, ','))
-             ippAddString(col, IPP_TAG_ZERO, value, attr, NULL, token);
-           else
-           {
-            /*
-             * Multiple string values...
-             */
-
-             int       num_values;     /* Number of values */
-             char      *values[100],   /* Values */
-                       *ptr;           /* Pointer to next value */
-
-
-             values[0]  = token;
-             num_values = 1;
-
-             for (ptr = strchr(token, ','); ptr; ptr = strchr(ptr, ','))
-             {
-               *ptr++ = '\0';
-               values[num_values] = ptr;
-               num_values ++;
-             }
-
-             ippAddStrings(col, IPP_TAG_ZERO, value, attr, num_values,
-                           NULL, (const char **)values);
-           }
-           break;
-      }
+static void
+print_xml_trailer(
+    _cups_testdata_t *data,            /* I - Test data */
+    int              success,          /* I - 1 on success, 0 on failure */
+    const char       *message)         /* I - Error message or NULL */
+{
+  if (data->xml_header)
+  {
+    cupsFilePuts(data->outfile, "</array>\n");
+    cupsFilePuts(data->outfile, "<key>Successful</key>\n");
+    cupsFilePuts(data->outfile, success ? "<true />\n" : "<false />\n");
+    if (message)
+    {
+      cupsFilePuts(data->outfile, "<key>ErrorMessage</key>\n");
+      print_xml_string(data->outfile, "string", message);
     }
-  }
-
-  return (col);
-
- /*
-  * If we get here there was a parse error; free memory and return.
-  */
-
-  col_error:
-
-  ippDelete(col);
+    cupsFilePuts(data->outfile, "</dict>\n");
+    cupsFilePuts(data->outfile, "</plist>\n");
 
-  return (NULL);
+    data->xml_header = 0;
+  }
 }
 
 
+#ifndef WIN32
 /*
- * 'get_filename()' - Get a filename based on the current test file.
+ * 'sigterm_handler()' - Handle SIGINT and SIGTERM.
  */
 
-static char *                          /* O - Filename */
-get_filename(const char *testfile,     /* I - Current test file */
-             char       *dst,          /* I - Destination filename */
-            const char *src,           /* I - Source filename */
-             size_t     dstsize)       /* I - Size of destination buffer */
+static void
+sigterm_handler(int sig)               /* I - Signal number (unused) */
 {
-  char                 *dstptr;        /* Pointer into destination */
-  _cups_globals_t      *cg = _cupsGlobals();
-                                       /* Global data */
-
-
-  if (*src == '<' && src[strlen(src) - 1] == '>')
-  {
-   /*
-    * Map <filename> to CUPS_DATADIR/ipptool/filename...
-    */
-
-    snprintf(dst, dstsize, "%s/ipptool/%s", cg->cups_datadir, src + 1);
-    dstptr = dst + strlen(dst) - 1;
-    if (*dstptr == '>')
-      *dstptr = '\0';
-  }
-  else if (*src == '/' || !strchr(testfile, '/')
-#ifdef WIN32
-           || (isalpha(*src & 255) && src[1] == ':')
-#endif /* WIN32 */
-           )
-  {
-   /*
-    * Use the path as-is...
-    */
-
-    strlcpy(dst, src, dstsize);
-  }
-  else
-  {
-   /*
-    * Make path relative to testfile...
-    */
-
-    strlcpy(dst, testfile, dstsize);
-    if ((dstptr = strrchr(dst, '/')) != NULL)
-      dstptr ++;
-    else
-      dstptr = dst; /* Should never happen */
+  (void)sig;
 
-    strlcpy(dstptr, src, dstsize - (size_t)(dstptr - dst));
-  }
+  Cancel = 1;
 
-  return (dst);
+  signal(SIGINT, SIG_DFL);
+  signal(SIGTERM, SIG_DFL);
 }
+#endif /* !WIN32 */
 
 
 /*
- * 'get_string()' - Get a pointer to a string value or the portion of interest.
+ * 'timeout_cb()' - Handle HTTP timeouts.
  */
 
-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 */
+static int                             /* O - 1 to continue, 0 to cancel */
+timeout_cb(http_t *http,               /* I - Connection to server */
+           void   *user_data)          /* I - User data (unused) */
 {
-  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 "." */
+  int          buffered = 0;           /* Bytes buffered but not yet sent */
 
-    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';
+  (void)user_data;
 
-    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 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...
+  */
 
-    if (status < HTTP_URI_STATUS_OK)
-    {
-     /*
-      * Bad URI...
-      */
+#ifdef SO_NWRITE                       /* macOS and some versions of Linux */
+  socklen_t len = sizeof(buffered);    /* Size of return value */
 
-      buffer[0] = '\0';
-    }
-    else
-    {
-     /*
-      * Normalize URI with no trailing dot...
-      */
+  if (getsockopt(httpGetFd(http), SOL_SOCKET, SO_NWRITE, &buffered, &len))
+    buffered = 0;
 
-      if ((ptr = hostname + strlen(hostname) - 1) >= hostname && *ptr == '.')
-       *ptr = '\0';
+#elif defined(SIOCOUTQ)                        /* Others except Windows */
+  if (ioctl(httpGetFd(http), SIOCOUTQ, &buffered))
+    buffered = 0;
 
-      httpAssembleURI(HTTP_URI_CODING_ALL, buffer, (int)bufsize, scheme, userpass, hostname, port, resource);
-    }
+#else                                  /* Windows (not possible) */
+  (void)http;
+#endif /* SO_NWRITE */
 
-    return (buffer);
-  }
-  else
-    return (value);
+  return (buffered > 0);
 }
 
 
 /*
- * 'get_token()' - Get a token from a file.
+ * 'token_cb()' - Parse test file-specific tokens and run tests.
  */
 
-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 */
+static int                             /* O - 1 to continue, 0 to stop */
+token_cb(_ipp_file_t      *f,          /* I - IPP file data */
+         _ipp_vars_t      *vars,       /* I - IPP variables */
+         _cups_testdata_t *data,       /* I - Test data */
+         const char       *token)      /* I - Current token */
 {
-  int  ch,                             /* Character from file */
-       quote;                          /* Quoting character */
-  char *bufptr,                        /* Pointer into buffer */
-       *bufend;                        /* End of buffer */
+  char name[1024],                     /* Name string */
+       temp[1024],                     /* Temporary string */
+       value[1024],                    /* Value string */
+       *ptr;                           /* Pointer into value */
 
 
-  for (;;)
+  if (!token)
   {
    /*
-    * Skip whitespace...
+    * Initialize state as needed (nothing for now...)
     */
 
-    while (isspace(ch = getc(fp)))
-    {
-      if (ch == '\n')
-        (*linenum) ++;
-    }
-
+    return (1);
+  }
+  else if (f->attrs)
+  {
    /*
-    * Read a token...
+    * Parse until we see a close brace...
     */
 
-    if (ch == EOF)
-      return (NULL);
-    else if (ch == '\'' || ch == '\"')
+    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"))
+      data->last_expect = NULL;
+
+    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"))
+      data->last_status = NULL;
+
+    if (!strcmp(token, "}"))
+    {
+      return (do_test(f, vars, data));
+    }
+    else if (!strcmp(token, "COMPRESSION"))
     {
      /*
-      * Quoted text or regular expression...
+      * COMPRESSION none
+      * COMPRESSION deflate
+      * COMPRESSION gzip
       */
 
-      quote  = ch;
-      bufptr = buf;
-      bufend = buf + buflen - 1;
-
-      while ((ch = getc(fp)) != EOF)
+      if (_ippFileReadToken(f, temp, sizeof(temp)))
       {
-        if (ch == '\\')
+       _ippVarsExpand(vars, data->compression, temp, sizeof(data->compression));
+#ifdef HAVE_LIBZ
+       if (strcmp(data->compression, "none") && strcmp(data->compression, "deflate") &&
+           strcmp(data->compression, "gzip"))
+#else
+       if (strcmp(data->compression, "none"))
+#endif /* HAVE_LIBZ */
        {
-        /*
-         * Escape next character...
-         */
-
-         if (bufptr < bufend)
-           *bufptr++ = (char)ch;
-
-         if ((ch = getc(fp)) != EOF && bufptr < bufend)
-           *bufptr++ = (char)ch;
+         print_fatal_error(data, "Unsupported COMPRESSION value \"%s\" on line %d of \"%s\".", data->compression, f->linenum, f->filename);
+         return (0);
        }
-       else if (ch == quote)
-          break;
-       else if (bufptr < bufend)
-          *bufptr++ = (char)ch;
-      }
 
-      *bufptr = '\0';
-
-      return (buf);
+       if (!strcmp(data->compression, "none"))
+         data->compression[0] = '\0';
+      }
+      else
+      {
+       print_fatal_error(data, "Missing COMPRESSION value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
     }
-    else if (ch == '#')
+    else if (!strcmp(token, "DEFINE"))
     {
      /*
-      * Comment...
+      * DEFINE name value
       */
 
-      while ((ch = getc(fp)) != EOF)
-       if (ch == '\n')
-          break;
-
-      (*linenum) ++;
+      if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       _ippVarsExpand(vars, value, temp, sizeof(value));
+       _ippVarsSet(vars, name, value);
+      }
+      else
+      {
+       print_fatal_error(data, "Missing DEFINE name and/or value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
     }
-    else if (ch == '{' || ch == '}' || ch == ',')
+    else if (!strcmp(token, "IGNORE-ERRORS"))
     {
-      buf[0] = (char)ch;
-      buf[1] = '\0';
+     /*
+      * IGNORE-ERRORS yes
+      * IGNORE-ERRORS no
+      */
 
-      return (buf);
+      if (_ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
+      {
+       data->ignore_errors = !_cups_strcasecmp(temp, "yes");
+      }
+      else
+      {
+       print_fatal_error(data, "Missing IGNORE-ERRORS value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
     }
-    else
+    else if (!_cups_strcasecmp(token, "NAME"))
     {
      /*
-      * Whitespace delimited text...
+      * Name of test...
       */
 
-      ungetc(ch, fp);
-
-      bufptr = buf;
-      bufend = buf + buflen - 1;
-
-      while ((ch = getc(fp)) != EOF)
-       if (isspace(ch) || ch == '#')
-          break;
-       else if (bufptr < bufend)
-          *bufptr++ = (char)ch;
-
-      if (ch == '#')
-        ungetc(ch, fp);
-      else if (ch == '\n')
-        (*linenum) ++;
-
-      *bufptr = '\0';
-
-      return (buf);
+      _ippFileReadToken(f, temp, sizeof(temp));
+      _ippVarsExpand(vars, data->name, temp, sizeof(data->name));
     }
-  }
-}
-
-
-/*
- * 'get_variable()' - Get the value of a variable.
- */
-
-static char *                          /* O - Value or NULL */
-get_variable(_cups_vars_t *vars,       /* I - Variables */
-             const char   *name)       /* I - Variable name */
-{
-  _cups_var_t  key,                    /* Search key */
-               *match;                 /* Matching variable, if any */
+    else if (!_cups_strcasecmp(token, "PAUSE"))
+    {
+     /*
+      * Pause with a message...
+      */
 
+      if (_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+        pause_message(temp);
+      }
+      else
+      {
+       print_fatal_error(data, "Missing PAUSE message on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!strcmp(token, "REQUEST-ID"))
+    {
+     /*
+      * REQUEST-ID #
+      * REQUEST-ID random
+      */
 
-  key.name = (char *)name;
-  match    = cupsArrayFind(vars->vars, &key);
-
-  return (match ? match->value : NULL);
-}
-
-
-/*
- * 'iso_date()' - Return an ISO 8601 date/time string for the given IPP dateTime
- *                value.
- */
-
-static char *                          /* O - ISO 8601 date/time string */
-iso_date(ipp_uchar_t *date)            /* I - IPP (RFC 1903) date/time value */
-{
-  time_t       utctime;                /* UTC time since 1970 */
-  struct tm    *utcdate;               /* UTC date/time */
-  static char  buffer[255];            /* String buffer */
-
-
-  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);
-}
-
-
-/*
- * 'password_cb()' - Password callback for authenticated tests.
- */
-
-static const char *                    /* O - Password */
-password_cb(const char *prompt)                /* I - Prompt (unused) */
-{
-  (void)prompt;
-
-  if (PasswordTries < 3)
-  {
-    PasswordTries ++;
-
-    cupsSetUser(Username);
-
-    return (Password);
-  }
-  else
-    return (NULL);
-}
-
-
-/*
- * 'pause_message()' - Display the message and pause until the user presses a key.
- */
-
-static void
-pause_message(const char *message)     /* I - Message */
-{
-#ifdef WIN32
-  HANDLE       tty;                    /* Console handle */
-  DWORD                mode;                   /* Console mode */
-  char         key;                    /* Key press */
-  DWORD                bytes;                  /* Bytes read for key press */
-
-
- /*
-  * Disable input echo and set raw input...
-  */
-
-  if ((tty = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE)
-    return;
-
-  if (!GetConsoleMode(tty, &mode))
-    return;
-
-  if (!SetConsoleMode(tty, 0))
-    return;
-
-#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 */
-
-
- /*
-  * Disable input echo and set raw input...
-  */
-
-  if ((tty = open("/dev/tty", O_RDONLY)) < 0)
-    return;
-
-  if (tcgetattr(tty, &original))
-  {
-    close(tty);
-    return;
-  }
-
-  noecho = original;
-  noecho.c_lflag &= (tcflag_t)~(ICANON | ECHO | ECHOE | ISIG);
-
-  if (tcsetattr(tty, TCSAFLUSH, &noecho))
-  {
-    close(tty);
-    return;
-  }
-#endif /* WIN32 */
-
- /*
-  * Display the prompt...
-  */
-
-  printf("%s\n---- PRESS ANY KEY ----", message);
-  fflush(stdout);
-
-#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...
-  */
-
-  fputs("\r                       \r", stdout);
-  fflush(stdout);
-}
-
-
-/*
- * 'print_attr()' - Print an attribute on the screen.
- */
-
-static void
-print_attr(FILE            *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      *colattr;       /* Collection attribute */
-
-
-  if (format == _CUPS_OUTPUT_PLIST)
-  {
-    if (!attr->name || (group && *group != attr->group_tag))
-    {
-      if (attr->group_tag != IPP_TAG_ZERO)
+      if (_ippFileReadToken(f, temp, sizeof(temp)))
       {
-       fputs("</dict>\n", outfile);
-       fputs("<dict>\n", outfile);
-      }
-
-      if (group)
-        *group = attr->group_tag;
-    }
-
-    if (!attr->name)
-      return;
-
-    print_xml_string(outfile, "key", attr->name);
-    if (attr->num_values > 1)
-      fputs("<array>\n", outfile);
-
-    switch (attr->value_tag)
-    {
-      case IPP_TAG_INTEGER :
-      case IPP_TAG_ENUM :
-         for (i = 0; i < attr->num_values; i ++)
-           fprintf(outfile, "<integer>%d</integer>\n", attr->values[i].integer);
-         break;
-
-      case IPP_TAG_BOOLEAN :
-         for (i = 0; i < attr->num_values; i ++)
-           fputs(attr->values[i].boolean ? "<true />\n" : "<false />\n", outfile);
-         break;
-
-      case IPP_TAG_RANGE :
-         for (i = 0; i < attr->num_values; i ++)
-           fprintf(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 ++)
-           fprintf(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" : "dpcm");
-         break;
-
-      case IPP_TAG_DATE :
-         for (i = 0; i < attr->num_values; i ++)
-           fprintf(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 */
-
-           fprintf(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_LANGUAGE :
-         for (i = 0; i < attr->num_values; i ++)
-           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 ++)
-         {
-           fputs("<dict><key>language</key><string>", outfile);
-           print_xml_string(outfile, NULL, attr->values[i].string.language);
-           fputs("</string><key>string</key><string>", outfile);
-           print_xml_string(outfile, NULL, attr->values[i].string.text);
-           fputs("</string></dict>\n", outfile);
-         }
-         break;
-
-      case IPP_TAG_BEGIN_COLLECTION :
-         for (i = 0; i < attr->num_values; i ++)
-         {
-           fputs("<dict>\n", outfile);
-           for (colattr = attr->values[i].collection->attrs;
-                colattr;
-                colattr = colattr->next)
-             print_attr(outfile, format, colattr, NULL);
-           fputs("</dict>\n", outfile);
-         }
-         break;
-
-      default :
-         fprintf(outfile, "<string>&lt;&lt;%s&gt;&gt;</string>\n", ippTagString(attr->value_tag));
-         break;
-    }
-
-    if (attr->num_values > 1)
-      fputs("</array>\n", outfile);
-  }
-  else
-  {
-    char       buffer[8192];           /* Value buffer */
-
-    if (format == _CUPS_OUTPUT_TEST)
-    {
-      if (!attr->name)
-      {
-        fputs("        -- separator --\n", outfile);
-        return;
-      }
-
-      fprintf(outfile, "        %s (%s%s) = ", attr->name, attr->num_values > 1 ? "1setOf " : "", ippTagString(attr->value_tag));
-    }
-
-    ippAttributeString(attr, buffer, sizeof(buffer));
-    fprintf(outfile, "%s\n", buffer);
-  }
-}
-
-
-/*
- * 'print_csv()' - Print a line of CSV text.
- */
-
-static void
-print_csv(
-    FILE            *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 */
-    size_t          *widths)           /* I - Column widths */
-{
-  int          i;                      /* Looping var */
-  size_t       maxlength;              /* Max length of all columns */
-  char         *buffer,                /* String buffer */
-               *bufptr;                /* Pointer into buffer */
-  ipp_attribute_t *current;            /* Current attribute */
-
-
- /*
-  * Get the maximum string length we have to show and allocate...
-  */
-
-  for (i = 1, maxlength = widths[0]; i < num_displayed; i ++)
-    if (widths[i] > maxlength)
-      maxlength = widths[i];
-
-  maxlength += 2;
-
-  if ((buffer = malloc(maxlength)) == NULL)
-    return;
-
- /*
-  * Loop through the attributes to display...
-  */
-
-  if (attr)
-  {
-    for (i = 0; i < num_displayed; i ++)
-    {
-      if (i)
-        fputc(',', outfile);
-
-      buffer[0] = '\0';
-
-      for (current = attr; current; current = current->next)
-      {
-        if (!current->name)
-          break;
-        else if (!strcmp(current->name, displayed[i]))
-        {
-          ippAttributeString(current, buffer, maxlength);
-          break;
-        }
+       if (isdigit(temp[0] & 255))
+       {
+         data->request_id = atoi(temp);
+       }
+       else if (!_cups_strcasecmp(temp, "random"))
+       {
+         data->request_id = (CUPS_RAND() % 1000) * 137 + 1;
+       }
+       else
+       {
+         print_fatal_error(data, "Bad REQUEST-ID value \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+         return (0);
+       }
       }
-
-      if (strchr(buffer, ',') != NULL || strchr(buffer, '\"') != NULL ||
-         strchr(buffer, '\\') != NULL)
+      else
       {
-        putc('\"', outfile);
-        for (bufptr = buffer; *bufptr; bufptr ++)
-        {
-          if (*bufptr == '\\' || *bufptr == '\"')
-            putc('\\', outfile);
-          putc(*bufptr, outfile);
-        }
-        putc('\"', outfile);
+       print_fatal_error(data, "Missing REQUEST-ID value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
       }
-      else
-        fputs(buffer, outfile);
-    }
-    putc('\n', outfile);
-  }
-  else
-  {
-    for (i = 0; i < num_displayed; i ++)
-    {
-      if (i)
-        putc(',', outfile);
-
-      fputs(displayed[i], outfile);
     }
-    putc('\n', outfile);
-  }
-
-  free(buffer);
-}
-
-
-/*
- * 'print_fatal_error()' - Print a fatal error message.
- */
-
-static void
-print_fatal_error(FILE       *outfile, /* I - Output file */
-                 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_header(outfile);
-    print_xml_trailer(outfile, 0, buffer);
-  }
-
-  _cupsLangPrintf(stderr, "ipptool: %s", buffer);
-}
-
-
-/*
- * 'print_line()' - Print a line of formatted or CSV text.
- */
-
-static void
-print_line(
-    FILE            *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 */
-    size_t          *widths)           /* I - Column widths */
-{
-  int          i;                      /* Looping var */
-  size_t       maxlength;              /* Max length of all columns */
-  char         *buffer;                /* String buffer */
-  ipp_attribute_t *current;            /* Current attribute */
-
-
- /*
-  * Get the maximum string length we have to show and allocate...
-  */
-
-  for (i = 1, maxlength = widths[0]; i < num_displayed; i ++)
-    if (widths[i] > maxlength)
-      maxlength = widths[i];
-
-  maxlength += 2;
-
-  if ((buffer = malloc(maxlength)) == NULL)
-    return;
-
- /*
-  * Loop through the attributes to display...
-  */
-
-  if (attr)
-  {
-    for (i = 0; i < num_displayed; i ++)
+    else if (!strcmp(token, "SKIP-IF-DEFINED"))
     {
-      if (i)
-        putc(' ', outfile);
-
-      buffer[0] = '\0';
+     /*
+      * SKIP-IF-DEFINED variable
+      */
 
-      for (current = attr; current; current = current->next)
+      if (_ippFileReadToken(f, name, sizeof(name)))
       {
-        if (!current->name)
-          break;
-        else if (!strcmp(current->name, displayed[i]))
-        {
-          ippAttributeString(current, buffer, maxlength);
-          break;
-        }
+       if (_ippVarsGet(vars, name))
+         data->skip_test = 1;
+      }
+      else
+      {
+       print_fatal_error(data, "Missing SKIP-IF-DEFINED value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
       }
-
-      fprintf(outfile, "%*s", (int)-widths[i], buffer);
     }
-    putc('\n', outfile);
-  }
-  else
-  {
-    for (i = 0; i < num_displayed; i ++)
+    else if (!strcmp(token, "SKIP-IF-MISSING"))
     {
-      if (i)
-        putc(' ', outfile);
+     /*
+      * SKIP-IF-MISSING filename
+      */
 
-      fprintf(outfile, "%*s", (int)-widths[i], displayed[i]);
-    }
-    putc('\n', outfile);
+      if (_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+        char filename[1024];           /* Filename */
 
-    for (i = 0; i < num_displayed; i ++)
-    {
-      if (i)
-       putc(' ', outfile);
+       _ippVarsExpand(vars, value, temp, sizeof(value));
+       get_filename(f->filename, filename, temp, sizeof(filename));
 
-      memset(buffer, '-', widths[i]);
-      buffer[widths[i]] = '\0';
-      fputs(buffer, outfile);
+       if (access(filename, R_OK))
+         data->skip_test = 1;
+      }
+      else
+      {
+       print_fatal_error(data, "Missing SKIP-IF-MISSING filename on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
     }
-    putc('\n', outfile);
-  }
-
-  free(buffer);
-}
-
-
-/*
- * 'print_xml_header()' - Print a standard XML plist header.
- */
-
-static void
-print_xml_header(FILE *outfile)                /* I - Output file */
-{
-  if (!XMLHeader)
-  {
-    fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", outfile);
-    fputs("<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" "
-         "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n", outfile);
-    fputs("<plist version=\"1.0\">\n", outfile);
-    fputs("<dict>\n", outfile);
-    fputs("<key>ipptoolVersion</key>\n", outfile);
-    fputs("<string>" CUPS_SVERSION "</string>\n", outfile);
-    fputs("<key>Transfer</key>\n", outfile);
-    fprintf(outfile, "<string>%s</string>\n",
-           Transfer == _CUPS_TRANSFER_AUTO ? "auto" :
-               Transfer == _CUPS_TRANSFER_CHUNKED ? "chunked" : "length");
-    fputs("<key>Tests</key>\n", outfile);
-    fputs("<array>\n", outfile);
-
-    XMLHeader = 1;
-  }
-}
-
-
-/*
- * 'print_xml_string()' - Print an XML string with escaping.
- */
-
-static void
-print_xml_string(FILE       *outfile,  /* I - Output file */
-                 const char *element,  /* I - Element name or NULL */
-                const char *s)         /* I - String to print */
-{
-  if (element)
-    fprintf(outfile, "<%s>", element);
-
-  while (*s)
-  {
-    if (*s == '&')
-      fputs("&amp;", outfile);
-    else if (*s == '<')
-      fputs("&lt;", outfile);
-    else if (*s == '>')
-      fputs("&gt;", outfile);
-    else if ((*s & 0xe0) == 0xc0)
+    else if (!strcmp(token, "SKIP-IF-NOT-DEFINED"))
     {
      /*
-      * Validate UTF-8 two-byte sequence...
+      * SKIP-IF-NOT-DEFINED variable
       */
 
-      if ((s[1] & 0xc0) != 0x80)
+      if (_ippFileReadToken(f, name, sizeof(name)))
       {
-        putc('?', outfile);
-        s ++;
+       if (!_ippVarsGet(vars, name))
+         data->skip_test = 1;
       }
       else
       {
-        putc(*s++, outfile);
-        putc(*s, outfile);
+       print_fatal_error(data, "Missing SKIP-IF-NOT-DEFINED value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
       }
     }
-    else if ((*s & 0xf0) == 0xe0)
+    else if (!strcmp(token, "SKIP-PREVIOUS-ERROR"))
     {
      /*
-      * Validate UTF-8 three-byte sequence...
+      * SKIP-PREVIOUS-ERROR yes
+      * SKIP-PREVIOUS-ERROR no
       */
 
-      if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80)
+      if (_ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
       {
-        putc('?', outfile);
-        s += 2;
+       data->skip_previous = !_cups_strcasecmp(temp, "yes");
       }
       else
       {
-        putc(*s++, outfile);
-        putc(*s++, outfile);
-        putc(*s, outfile);
+       print_fatal_error(data, "Missing SKIP-PREVIOUS-ERROR value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
       }
     }
-    else if ((*s & 0xf8) == 0xf0)
+    else if (!strcmp(token, "TEST-ID"))
     {
      /*
-      * Validate UTF-8 four-byte sequence...
+      * TEST-ID "string"
       */
 
-      if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
-          (s[3] & 0xc0) != 0x80)
+      if (_ippFileReadToken(f, temp, sizeof(temp)))
       {
-        putc('?', outfile);
-        s += 3;
+       _ippVarsExpand(vars, data->test_id, temp, sizeof(data->test_id));
       }
       else
       {
-        putc(*s++, outfile);
-        putc(*s++, outfile);
-        putc(*s++, outfile);
-        putc(*s, outfile);
+       print_fatal_error(data, "Missing TEST-ID value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
       }
     }
-    else if ((*s & 0x80) || (*s < ' ' && !isspace(*s & 255)))
+    else if (!strcmp(token, "TRANSFER"))
     {
      /*
-      * Invalid control character...
+      * TRANSFER auto
+      * TRANSFER chunked
+      * TRANSFER length
       */
 
-      putc('?', outfile);
+      if (_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       if (!strcmp(temp, "auto"))
+       {
+         data->transfer = _CUPS_TRANSFER_AUTO;
+       }
+       else if (!strcmp(temp, "chunked"))
+       {
+         data->transfer = _CUPS_TRANSFER_CHUNKED;
+       }
+       else if (!strcmp(temp, "length"))
+       {
+         data->transfer = _CUPS_TRANSFER_LENGTH;
+       }
+       else
+       {
+         print_fatal_error(data, "Bad TRANSFER value \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+         return (0);
+       }
+      }
+      else
+      {
+       print_fatal_error(data, "Missing TRANSFER value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
     }
-    else
-      putc(*s, outfile);
-
-    s ++;
-  }
-
-  if (element)
-    fprintf(outfile, "</%s>\n", element);
-}
-
-
-/*
- * 'print_xml_trailer()' - Print the XML trailer with success/fail value.
- */
-
-static void
-print_xml_trailer(FILE       *outfile, /* I - Output file */
-                  int        success,  /* I - 1 on success, 0 on failure */
-                  const char *message) /* I - Error message or NULL */
-{
-  if (XMLHeader)
-  {
-    fputs("</array>\n", outfile);
-    fputs("<key>Successful</key>\n", outfile);
-    fputs(success ? "<true />\n" : "<false />\n", outfile);
-    if (message)
+    else if (!_cups_strcasecmp(token, "VERSION"))
     {
-      fputs("<key>ErrorMessage</key>\n", outfile);
-      print_xml_string(outfile, "string", message);
-    }
-    fputs("</dict>\n", outfile);
-    fputs("</plist>\n", outfile);
-
-    XMLHeader = 0;
-  }
-}
-
-
-/*
- * 'set_variable()' - Set a variable value.
- */
-
-static void
-set_variable(FILE         *outfile,    /* I - Output file */
-             _cups_vars_t *vars,       /* I - Variables */
-             const char   *name,       /* I - Variable name */
-             const char   *value)      /* I - Value string */
-{
-  _cups_var_t  key,                    /* Search key */
-               *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)
-  {
-    free(var->value);
-    var->value = strdup(value);
-  }
-  else if ((var = malloc(sizeof(_cups_var_t))) == NULL)
-  {
-    print_fatal_error(outfile, "Unable to allocate memory for variable \"%s\".", name);
-    exit(1);
-  }
-  else
-  {
-    var->name  = strdup(name);
-    var->value = strdup(value);
-
-    cupsArrayAdd(vars->vars, var);
-  }
-}
-
-
-#ifndef WIN32
-/*
- * 'sigterm_handler()' - Handle SIGINT and SIGTERM.
- */
-
-static void
-sigterm_handler(int sig)               /* I - Signal number (unused) */
-{
-  (void)sig;
+      if (_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       if (!strcmp(temp, "0.0"))
+       {
+         data->version = 0;
+       }
+       else if (!strcmp(temp, "1.0"))
+       {
+         data->version = 10;
+       }
+       else if (!strcmp(temp, "1.1"))
+       {
+         data->version = 11;
+       }
+       else if (!strcmp(temp, "2.0"))
+       {
+         data->version = 20;
+       }
+       else if (!strcmp(temp, "2.1"))
+       {
+         data->version = 21;
+       }
+       else if (!strcmp(temp, "2.2"))
+       {
+         data->version = 22;
+       }
+       else
+       {
+         print_fatal_error(data, "Bad VERSION \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+         return (0);
+       }
+      }
+      else
+      {
+       print_fatal_error(data, "Missing VERSION number on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "RESOURCE"))
+    {
+     /*
+      * Resource name...
+      */
 
-  Cancel = 1;
+      if (!_ippFileReadToken(f, data->resource, sizeof(data->resource)))
+      {
+       print_fatal_error(data, "Missing RESOURCE path on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "OPERATION"))
+    {
+     /*
+      * Operation...
+      */
 
-  signal(SIGINT, SIG_DFL);
-  signal(SIGTERM, SIG_DFL);
-}
-#endif /* !WIN32 */
+      ipp_op_t op;                     /* Operation code */
 
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing OPERATION code on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-/*
- * 'timeout_cb()' - Handle HTTP timeouts.
- */
+      _ippVarsExpand(vars, value, temp, sizeof(value));
 
-static int                             /* O - 1 to continue, 0 to cancel */
-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 */
+      if ((op = ippOpValue(value)) == (ipp_op_t)-1 && (op = (ipp_op_t)strtol(value, NULL, 0)) == 0)
+      {
+       print_fatal_error(data, "Bad OPERATION code \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+       return (0);
+      }
 
+      ippSetOperation(f->attrs, op);
+    }
+    else if (!_cups_strcasecmp(token, "GROUP"))
+    {
+     /*
+      * Attribute group...
+      */
 
-  (void)user_data;
+      ipp_tag_t        group_tag;              /* Group tag */
 
-  /*
-  * 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...
-  */
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing GROUP tag on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-#ifdef SO_NWRITE                       /* OS X and some versions of Linux */
-  socklen_t len = sizeof(buffered);    /* Size of return value */
+      if ((group_tag = ippTagValue(temp)) == IPP_TAG_ZERO || group_tag >= IPP_TAG_UNSUPPORTED_VALUE)
+      {
+       print_fatal_error(data, "Bad GROUP tag \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+       return (0);
+      }
 
-  if (getsockopt(httpGetFd(http), SOL_SOCKET, SO_NWRITE, &buffered, &len))
-    buffered = 0;
+      if (group_tag == f->group_tag)
+       ippAddSeparator(f->attrs);
 
-#elif defined(SIOCOUTQ)                        /* Others except Windows */
-  if (ioctl(httpGetFd(http), SIOCOUTQ, &buffered))
-    buffered = 0;
+      f->group_tag = group_tag;
+    }
+    else if (!_cups_strcasecmp(token, "DELAY"))
+    {
+     /*
+      * Delay before operation...
+      */
 
-#else                                  /* Windows (not possible) */
-  (void)http;
-#endif /* SO_NWRITE */
+      double dval;                    /* Delay value */
 
-  return (buffered > 0);
-}
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing DELAY value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
+      _ippVarsExpand(vars, value, temp, sizeof(value));
 
-/*
- * 'usage()' - Show program usage.
- */
+      if ((dval = _cupsStrScand(value, &ptr, localeconv())) < 0.0 || (*ptr && *ptr != ','))
+      {
+       print_fatal_error(data, "Bad DELAY value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
+       return (0);
+      }
 
-static void
-usage(void)
-{
-  _cupsLangPuts(stderr, _("Usage: ipptool [options] URI filename [ ... "
-                         "filenameN ]"));
-  _cupsLangPuts(stderr, _("Options:"));
-  _cupsLangPuts(stderr, _("  --help                  Show help."));
-  _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(stdout, _("  -E                      Test with 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 SSL "
-                         "encryption."));
-  _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, _("  -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."));
+      data->delay = (useconds_t)(1000000.0 * dval);
 
-  exit(1);
-}
+      if (*ptr == ',')
+      {
+       if ((dval = _cupsStrScand(ptr + 1, &ptr, localeconv())) <= 0.0 || *ptr)
+       {
+         print_fatal_error(data, "Bad DELAY value \"%s\" on line %d of \"%s\".", value, f->linenum, f->filename);
+         return (0);
+       }
 
+       data->repeat_interval = (useconds_t)(1000000.0 * dval);
+      }
+      else
+       data->repeat_interval = data->delay;
+    }
+    else if (!_cups_strcasecmp(token, "FILE"))
+    {
+     /*
+      * File...
+      */
 
-/*
- * 'validate_attr()' - Determine whether an attribute is valid.
- */
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing FILE filename on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-static int                             /* O - 1 if valid, 0 otherwise */
-validate_attr(FILE            *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 */
-               userpass[256],          /* Username/password from URI */
-               hostname[256],          /* Hostname from URI */
-               resource[1024];         /* Resource from URI */
-  int          port,                   /* Port number from URI */
-               uri_status,             /* URI separation status */
-               valid = 1;              /* Is the attribute valid? */
-  const char   *ptr;                   /* Pointer into string */
-  ipp_attribute_t *colattr;            /* Collection attribute */
-  regex_t      re;                     /* Regular expression */
-  ipp_uchar_t  *date;                  /* Current date value */
+      _ippVarsExpand(vars, value, temp, sizeof(value));
+      get_filename(f->filename, data->file, value, sizeof(data->file));
 
+      if (access(data->file, R_OK))
+      {
+       print_fatal_error(data, "Filename \"%s\" (mapped to \"%s\") on line %d of \"%s\" cannot be read.", value, data->file, f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "STATUS"))
+    {
+     /*
+      * Status...
+      */
 
- /*
-  * Skip separators.
-  */
+      if (data->num_statuses >= (int)(sizeof(data->statuses) / sizeof(data->statuses[0])))
+      {
+       print_fatal_error(data, "Too many STATUS's on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-  if (!attr->name)
-    return (1);
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing STATUS code on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
- /*
-  * Validate the attribute name.
-  */
+      if ((data->statuses[data->num_statuses].status = ippErrorValue(temp)) == (ipp_status_t)-1 && (data->statuses[data->num_statuses].status = (ipp_status_t)strtol(temp, NULL, 0)) == 0)
+      {
+       print_fatal_error(data, "Bad STATUS code \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+       return (0);
+      }
 
-  for (ptr = attr->name; *ptr; ptr ++)
-    if (!isalnum(*ptr & 255) && *ptr != '-' && *ptr != '.' && *ptr != '_')
-      break;
+      data->last_status = data->statuses + data->num_statuses;
+      data->num_statuses ++;
 
-  if (*ptr || ptr == attr->name)
-  {
-    valid = 0;
+      data->last_status->define_match    = NULL;
+      data->last_status->define_no_match = NULL;
+      data->last_status->if_defined      = NULL;
+      data->last_status->if_not_defined  = NULL;
+      data->last_status->repeat_limit    = 1000;
+      data->last_status->repeat_match    = 0;
+      data->last_status->repeat_no_match = 0;
+    }
+    else if (!_cups_strcasecmp(token, "EXPECT") || !_cups_strcasecmp(token, "EXPECT-ALL"))
+    {
+     /*
+      * Expected attributes...
+      */
 
-    add_stringf(errors,
-               "\"%s\": Bad attribute name - invalid character "
-               "(RFC 2911 section 4.1.3).", attr->name);
-  }
+      int expect_all = !_cups_strcasecmp(token, "EXPECT-ALL");
 
-  if ((ptr - attr->name) > 255)
-  {
-    valid = 0;
+      if (data->num_expects >= (int)(sizeof(data->expects) / sizeof(data->expects[0])))
+      {
+       print_fatal_error(data, "Too many EXPECT's on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-    add_stringf(errors,
-               "\"%s\": Bad attribute name - bad length "
-               "(RFC 2911 section 4.1.3).", attr->name);
-  }
+      if (!_ippFileReadToken(f, name, sizeof(name)))
+      {
+       print_fatal_error(data, "Missing EXPECT name on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-  switch (attr->value_tag)
-  {
-    case IPP_TAG_INTEGER :
-        break;
+      data->last_expect = data->expects + data->num_expects;
+      data->num_expects ++;
 
-    case IPP_TAG_BOOLEAN :
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         if (attr->values[i].boolean != 0 &&
-             attr->values[i].boolean != 1)
-         {
-           valid = 0;
+      memset(data->last_expect, 0, sizeof(_cups_expect_t));
+      data->last_expect->repeat_limit = 1000;
+      data->last_expect->expect_all   = expect_all;
 
-           add_stringf(errors,
-                       "\"%s\": Bad boolen value %d "
-                       "(RFC 2911 section 4.1.11).", attr->name,
-                       attr->values[i].boolean);
-         }
-       }
-        break;
+      if (name[0] == '!')
+      {
+       data->last_expect->not_expect = 1;
+       data->last_expect->name       = strdup(name + 1);
+      }
+      else if (name[0] == '?')
+      {
+       data->last_expect->optional = 1;
+       data->last_expect->name     = strdup(name + 1);
+      }
+      else
+       data->last_expect->name = strdup(name);
+    }
+    else if (!_cups_strcasecmp(token, "COUNT"))
+    {
+      int      count;                  /* Count value */
 
-    case IPP_TAG_ENUM :
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         if (attr->values[i].integer < 1)
-         {
-           valid = 0;
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing COUNT number on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-           add_stringf(errors,
-                       "\"%s\": Bad enum value %d - out of range "
-                       "(RFC 2911 section 4.1.4).", attr->name,
-                       attr->values[i].integer);
-         }
-       }
-        break;
+      if ((count = atoi(temp)) <= 0)
+      {
+       print_fatal_error(data, "Bad COUNT \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+       return (0);
+      }
 
-    case IPP_TAG_STRING :
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         if (attr->values[i].unknown.length > IPP_MAX_OCTETSTRING)
-         {
-           valid = 0;
+      if (data->last_expect)
+      {
+       data->last_expect->count = count;
+      }
+      else
+      {
+       print_fatal_error(data, "COUNT without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "DEFINE-MATCH"))
+    {
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing DEFINE-MATCH variable on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-           add_stringf(errors,
-                       "\"%s\": Bad octetString value - bad length %d "
-                       "(RFC 2911 section 4.1.10).", attr->name,
-                       attr->values[i].unknown.length);
-         }
-       }
-        break;
+      if (data->last_expect)
+      {
+       data->last_expect->define_match = strdup(temp);
+      }
+      else if (data->last_status)
+      {
+       data->last_status->define_match = strdup(temp);
+      }
+      else
+      {
+       print_fatal_error(data, "DEFINE-MATCH without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "DEFINE-NO-MATCH"))
+    {
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing DEFINE-NO-MATCH variable on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-    case IPP_TAG_DATE :
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         date = attr->values[i].date;
+      if (data->last_expect)
+      {
+       data->last_expect->define_no_match = strdup(temp);
+      }
+      else if (data->last_status)
+      {
+       data->last_status->define_no_match = strdup(temp);
+      }
+      else
+      {
+       print_fatal_error(data, "DEFINE-NO-MATCH without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "DEFINE-VALUE"))
+    {
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing DEFINE-VALUE variable on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-          if (date[2] < 1 || date[2] > 12)
-         {
-           valid = 0;
+      if (data->last_expect)
+      {
+       data->last_expect->define_value = strdup(temp);
+      }
+      else
+      {
+       print_fatal_error(data, "DEFINE-VALUE without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "OF-TYPE"))
+    {
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing OF-TYPE value tag(s) on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-           add_stringf(errors,
-                       "\"%s\": Bad dateTime month %u "
-                       "(RFC 2911 section 4.1.14).", attr->name, date[2]);
-         }
+      if (data->last_expect)
+      {
+       data->last_expect->of_type = strdup(temp);
+      }
+      else
+      {
+       print_fatal_error(data, "OF-TYPE without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "IN-GROUP"))
+    {
+      ipp_tag_t        in_group;               /* IN-GROUP value */
 
-          if (date[3] < 1 || date[3] > 31)
-         {
-           valid = 0;
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing IN-GROUP group tag on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-           add_stringf(errors,
-                       "\"%s\": Bad dateTime day %u "
-                       "(RFC 2911 section 4.1.14).", attr->name, date[3]);
-         }
+      if ((in_group = ippTagValue(temp)) == IPP_TAG_ZERO || in_group >= IPP_TAG_UNSUPPORTED_VALUE)
+      {
+       print_fatal_error(data, "Bad IN-GROUP group tag \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+       return (0);
+      }
+      else if (data->last_expect)
+      {
+       data->last_expect->in_group = in_group;
+      }
+      else
+      {
+       print_fatal_error(data, "IN-GROUP without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "REPEAT-LIMIT"))
+    {
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing REPEAT-LIMIT value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+      else if (atoi(temp) <= 0)
+      {
+       print_fatal_error(data, "Bad REPEAT-LIMIT value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-          if (date[4] > 23)
-         {
-           valid = 0;
+      if (data->last_status)
+      {
+       data->last_status->repeat_limit = atoi(temp);
+      }
+      else if (data->last_expect)
+      {
+       data->last_expect->repeat_limit = atoi(temp);
+      }
+      else
+      {
+       print_fatal_error(data, "REPEAT-LIMIT without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "REPEAT-MATCH"))
+    {
+      if (data->last_status)
+      {
+       data->last_status->repeat_match = 1;
+      }
+      else if (data->last_expect)
+      {
+       data->last_expect->repeat_match = 1;
+      }
+      else
+      {
+       print_fatal_error(data, "REPEAT-MATCH without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "REPEAT-NO-MATCH"))
+    {
+      if (data->last_status)
+      {
+       data->last_status->repeat_no_match = 1;
+      }
+      else if (data->last_expect)
+      {
+       data->last_expect->repeat_no_match = 1;
+      }
+      else
+      {
+       print_fatal_error(data, "REPEAT-NO-MATCH without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "SAME-COUNT-AS"))
+    {
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing SAME-COUNT-AS name on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-           add_stringf(errors,
-                       "\"%s\": Bad dateTime hours %u "
-                       "(RFC 2911 section 4.1.14).", attr->name, date[4]);
-         }
+      if (data->last_expect)
+      {
+       data->last_expect->same_count_as = strdup(temp);
+      }
+      else
+      {
+       print_fatal_error(data, "SAME-COUNT-AS without a preceding EXPECT on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "IF-DEFINED"))
+    {
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing IF-DEFINED name on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-          if (date[5] > 59)
-         {
-           valid = 0;
+      if (data->last_expect)
+      {
+       data->last_expect->if_defined = strdup(temp);
+      }
+      else if (data->last_status)
+      {
+       data->last_status->if_defined = strdup(temp);
+      }
+      else
+      {
+       print_fatal_error(data, "IF-DEFINED without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "IF-NOT-DEFINED"))
+    {
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing IF-NOT-DEFINED name on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-           add_stringf(errors,
-                       "\"%s\": Bad dateTime minutes %u "
-                       "(RFC 2911 section 4.1.14).", attr->name, date[5]);
-         }
+      if (data->last_expect)
+      {
+       data->last_expect->if_not_defined = strdup(temp);
+      }
+      else if (data->last_status)
+      {
+       data->last_status->if_not_defined = strdup(temp);
+      }
+      else
+      {
+       print_fatal_error(data, "IF-NOT-DEFINED without a preceding EXPECT or STATUS on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    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"))
+    {
+      off_t    lastpos;                /* Last file position */
 
-          if (date[6] > 60)
-         {
-           valid = 0;
+      if (data->last_expect)
+      {
+       if (!_cups_strcasecmp(token, "WITH-ALL-HOSTNAMES") || !_cups_strcasecmp(token, "WITH-HOSTNAME"))
+         data->last_expect->with_flags = _CUPS_WITH_HOSTNAME;
+       else if (!_cups_strcasecmp(token, "WITH-ALL-RESOURCES") || !_cups_strcasecmp(token, "WITH-RESOURCE"))
+         data->last_expect->with_flags = _CUPS_WITH_RESOURCE;
+       else if (!_cups_strcasecmp(token, "WITH-ALL-SCHEMES") || !_cups_strcasecmp(token, "WITH-SCHEME"))
+         data->last_expect->with_flags = _CUPS_WITH_SCHEME;
+
+       if (!_cups_strncasecmp(token, "WITH-ALL-", 9))
+         data->last_expect->with_flags |= _CUPS_WITH_ALL;
+      }
 
-           add_stringf(errors,
-                       "\"%s\": Bad dateTime seconds %u "
-                       "(RFC 2911 section 4.1.14).", attr->name, date[6]);
-         }
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing %s value on line %d of \"%s\".", token, f->linenum, f->filename);
+       return (0);
+      }
 
-          if (date[7] > 9)
-         {
-           valid = 0;
+     /*
+      * Read additional comma-delimited values - needed since legacy test files
+      * will have unquoted WITH-VALUE values with commas...
+      */
 
-           add_stringf(errors,
-                       "\"%s\": Bad dateTime deciseconds %u "
-                       "(RFC 2911 section 4.1.14).", attr->name, date[7]);
-         }
+      ptr = temp + strlen(temp);
 
-          if (date[8] != '-' && date[8] != '+')
-         {
-           valid = 0;
+      for (;;)
+      {
+        lastpos = cupsFileTell(f->fp);
+        ptr     += strlen(ptr);
 
-           add_stringf(errors,
-                       "\"%s\": Bad dateTime UTC sign '%c' "
-                       "(RFC 2911 section 4.1.14).", attr->name, date[8]);
-         }
+       if (!_ippFileReadToken(f, ptr, (sizeof(temp) - (size_t)(ptr - temp))))
+         break;
 
-          if (date[9] > 11)
-         {
-           valid = 0;
+        if (!strcmp(ptr, ","))
+        {
+         /*
+          * Append a value...
+          */
 
-           add_stringf(errors,
-                       "\"%s\": Bad dateTime UTC hours %u "
-                       "(RFC 2911 section 4.1.14).", attr->name, date[9]);
-         }
+         ptr += strlen(ptr);
 
-          if (date[10] > 59)
-         {
-           valid = 0;
+         if (!_ippFileReadToken(f, ptr, (sizeof(temp) - (size_t)(ptr - temp))))
+           break;
+        }
+        else
+        {
+         /*
+          * Not another value, stop here...
+          */
 
-           add_stringf(errors,
-                       "\"%s\": Bad dateTime UTC minutes %u "
-                       "(RFC 2911 section 4.1.14).", attr->name, date[10]);
-         }
+          cupsFileSeek(f->fp, lastpos);
+          *ptr = '\0';
+          break;
        }
-        break;
+      }
 
-    case IPP_TAG_RESOLUTION :
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         if (attr->values[i].resolution.xres <= 0)
-         {
-           valid = 0;
-
-           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 (data->last_expect)
+      {
+       /*
+       * Expand any variables in the value and then save it.
+       */
 
-         if (attr->values[i].resolution.yres <= 0)
-         {
-           valid = 0;
-
-           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");
-         }
+       _ippVarsExpand(vars, value, temp, sizeof(value));
 
-         if (attr->values[i].resolution.units != IPP_RES_PER_INCH &&
-             attr->values[i].resolution.units != IPP_RES_PER_CM)
-         {
-           valid = 0;
-
-           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;
+       ptr = value + strlen(value) - 1;
 
-    case IPP_TAG_RANGE :
-        for (i = 0; i < attr->num_values; i ++)
+       if (value[0] == '/' && ptr > value && *ptr == '/')
        {
-         if (attr->values[i].range.lower > attr->values[i].range.upper)
-         {
-           valid = 0;
+        /*
+         * WITH-VALUE is a POSIX extended regular expression.
+         */
 
-           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;
+         data->last_expect->with_value = calloc(1, (size_t)(ptr - value));
+         data->last_expect->with_flags |= _CUPS_WITH_REGEX;
 
-    case IPP_TAG_BEGIN_COLLECTION :
-        for (i = 0; i < attr->num_values; i ++)
+         if (data->last_expect->with_value)
+           memcpy(data->last_expect->with_value, value + 1, (size_t)(ptr - value - 1));
+       }
+       else
        {
-         for (colattr = attr->values[i].collection->attrs;
-              colattr;
-              colattr = colattr->next)
-         {
-           if (!validate_attr(outfile, NULL, colattr))
-           {
-             valid = 0;
-             break;
-           }
-         }
+        /*
+         * WITH-VALUE is a literal value...
+         */
 
-         if (colattr && errors)
+         for (ptr = value; *ptr; ptr ++)
          {
-            add_stringf(errors, "\"%s\": Bad collection value.", attr->name);
-
-           while (colattr)
+           if (*ptr == '\\' && ptr[1])
            {
-             validate_attr(outfile, errors, colattr);
-             colattr = colattr->next;
-           }
-         }
-       }
-        break;
+            /*
+             * Remove \ from \foo...
+             */
 
-    case IPP_TAG_TEXT :
-    case IPP_TAG_TEXTLANG :
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         for (ptr = attr->values[i].string.text; *ptr; ptr ++)
-         {
-           if ((*ptr & 0xe0) == 0xc0)
-           {
-             ptr ++;
-             if ((*ptr & 0xc0) != 0x80)
-               break;
-           }
-           else if ((*ptr & 0xf0) == 0xe0)
-           {
-             ptr ++;
-             if ((*ptr & 0xc0) != 0x80)
-               break;
-             ptr ++;
-             if ((*ptr & 0xc0) != 0x80)
-               break;
-           }
-           else if ((*ptr & 0xf8) == 0xf0)
-           {
-             ptr ++;
-             if ((*ptr & 0xc0) != 0x80)
-               break;
-             ptr ++;
-             if ((*ptr & 0xc0) != 0x80)
-               break;
-             ptr ++;
-             if ((*ptr & 0xc0) != 0x80)
-               break;
+             _cups_strcpy(ptr, ptr + 1);
            }
-           else if (*ptr & 0x80)
-             break;
-         }
-
-         if (*ptr)
-         {
-           valid = 0;
-
-           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) > (IPP_MAX_TEXT - 1))
-         {
-           valid = 0;
-
-           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));
-         }
+         data->last_expect->with_value = strdup(value);
+         data->last_expect->with_flags |= _CUPS_WITH_LITERAL;
        }
-        break;
-
-    case IPP_TAG_NAME :
-    case IPP_TAG_NAMELANG :
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         for (ptr = attr->values[i].string.text; *ptr; ptr ++)
-         {
-           if ((*ptr & 0xe0) == 0xc0)
-           {
-             ptr ++;
-             if ((*ptr & 0xc0) != 0x80)
-               break;
-           }
-           else if ((*ptr & 0xf0) == 0xe0)
-           {
-             ptr ++;
-             if ((*ptr & 0xc0) != 0x80)
-               break;
-             ptr ++;
-             if ((*ptr & 0xc0) != 0x80)
-               break;
-           }
-           else if ((*ptr & 0xf8) == 0xf0)
-           {
-             ptr ++;
-             if ((*ptr & 0xc0) != 0x80)
-               break;
-             ptr ++;
-             if ((*ptr & 0xc0) != 0x80)
-               break;
-             ptr ++;
-             if ((*ptr & 0xc0) != 0x80)
-               break;
-           }
-           else if (*ptr & 0x80)
-             break;
-         }
+      }
+      else
+      {
+       print_fatal_error(data, "%s without a preceding EXPECT on line %d of \"%s\".", token, f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "WITH-VALUE-FROM"))
+    {
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing %s value on line %d of \"%s\".", token, f->linenum, f->filename);
+       return (0);
+      }
 
-         if (*ptr)
-         {
-           valid = 0;
+      if (data->last_expect)
+      {
+       /*
+       * Expand any variables in the value and then save it.
+       */
 
-           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);
-         }
+       _ippVarsExpand(vars, value, temp, sizeof(value));
 
-         if ((ptr - attr->values[i].string.text) > (IPP_MAX_NAME - 1))
-         {
-           valid = 0;
+       data->last_expect->with_value_from = strdup(value);
+       data->last_expect->with_flags      = _CUPS_WITH_LITERAL;
+      }
+      else
+      {
+       print_fatal_error(data, "%s without a preceding EXPECT on line %d of \"%s\".", token, f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!_cups_strcasecmp(token, "DISPLAY"))
+    {
+     /*
+      * Display attributes...
+      */
 
-           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;
+      if (data->num_displayed >= (int)(sizeof(data->displayed) / sizeof(data->displayed[0])))
+      {
+       print_fatal_error(data, "Too many DISPLAY's on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-    case IPP_TAG_KEYWORD :
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         for (ptr = attr->values[i].string.text; *ptr; ptr ++)
-           if (!isalnum(*ptr & 255) && *ptr != '-' && *ptr != '.' &&
-               *ptr != '_')
-             break;
+      if (!_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       print_fatal_error(data, "Missing DISPLAY name on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
 
-         if (*ptr || ptr == attr->values[i].string.text)
-         {
-           valid = 0;
+      data->displayed[data->num_displayed] = strdup(temp);
+      data->num_displayed ++;
+    }
+    else
+    {
+      print_fatal_error(data, "Unexpected token %s seen on line %d of \"%s\".", token, f->linenum, f->filename);
+      return (0);
+    }
+  }
+  else
+  {
+   /*
+    * Scan for the start of a test (open brace)...
+    */
 
-           add_stringf(errors,
-                       "\"%s\": Bad keyword value \"%s\" - invalid "
-                       "character (RFC 2911 section 4.1.3).",
-                       attr->name, attr->values[i].string.text);
-         }
+    if (!strcmp(token, "{"))
+    {
+     /*
+      * Start new test...
+      */
 
-         if ((ptr - attr->values[i].string.text) > (IPP_MAX_KEYWORD - 1))
-         {
-           valid = 0;
+      if (data->show_header)
+      {
+       if (data->output == _CUPS_OUTPUT_PLIST)
+         print_xml_header(data);
 
-           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;
+       if (data->output == _CUPS_OUTPUT_TEST || (data->output == _CUPS_OUTPUT_PLIST && data->outfile != cupsFileStdout()))
+         cupsFilePrintf(cupsFileStdout(), "\"%s\":\n", f->filename);
 
-    case IPP_TAG_URI :
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         uri_status = httpSeparateURI(HTTP_URI_CODING_ALL,
-                                      attr->values[i].string.text,
-                                      scheme, sizeof(scheme),
-                                      userpass, sizeof(userpass),
-                                      hostname, sizeof(hostname),
-                                      &port, resource, sizeof(resource));
-
-         if (uri_status < HTTP_URI_OK)
-         {
-           valid = 0;
+       data->show_header = 0;
+      }
 
-           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));
-         }
+      data->compression[0] = '\0';
+      data->delay          = 0;
+      data->num_expects    = 0;
+      data->last_expect    = NULL;
+      data->file[0]        = '\0';
+      data->ignore_errors  = data->def_ignore_errors;
+      strlcpy(data->name, f->filename, sizeof(data->name));
+      if ((ptr = strrchr(data->name, '.')) != NULL)
+        *ptr = '\0';
+      data->repeat_interval = 5000000;
+      data->request_id ++;
+      strlcpy(data->resource, vars->resource, sizeof(data->resource));
+      data->skip_previous = 0;
+      data->skip_test     = 0;
+      data->num_statuses  = 0;
+      data->last_status   = NULL;
+      data->test_id[0]    = '\0';
+      data->transfer      = data->def_transfer;
+      data->version       = data->def_version;
+
+      f->attrs     = ippNew();
+      f->group_tag = IPP_TAG_ZERO;
+    }
+    else if (!strcmp(token, "DEFINE"))
+    {
+     /*
+      * DEFINE name value
+      */
 
-         if (strlen(attr->values[i].string.text) > (IPP_MAX_URI - 1))
-         {
-           valid = 0;
+      if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
+      {
+        _ippVarsExpand(vars, value, temp, sizeof(value));
+       _ippVarsSet(vars, name, value);
+      }
+      else
+      {
+        print_fatal_error(data, "Missing DEFINE name and/or value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!strcmp(token, "DEFINE-DEFAULT"))
+    {
+     /*
+      * DEFINE-DEFAULT name value
+      */
 
-           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));
-         }
+      if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
+      {
+        if (!_ippVarsGet(vars, name))
+        {
+         _ippVarsExpand(vars, value, temp, sizeof(value));
+         _ippVarsSet(vars, name, value);
        }
-        break;
+      }
+      else
+      {
+        print_fatal_error(data, "Missing DEFINE-DEFAULT name and/or value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!strcmp(token, "FILE-ID"))
+    {
+     /*
+      * FILE-ID "string"
+      */
 
-    case IPP_TAG_URISCHEME :
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         ptr = attr->values[i].string.text;
-         if (islower(*ptr & 255))
-         {
-           for (ptr ++; *ptr; ptr ++)
-             if (!islower(*ptr & 255) && !isdigit(*ptr & 255) &&
-                 *ptr != '+' && *ptr != '-' && *ptr != '.')
-                break;
-         }
+      if (_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+        _ippVarsExpand(vars, data->file_id, temp, sizeof(data->file_id));
+      }
+      else
+      {
+        print_fatal_error(data, "Missing FILE-ID value on line %d of \"%s\".", f->linenum, f->filename);
+        return (0);
+      }
+    }
+    else if (!strcmp(token, "IGNORE-ERRORS"))
+    {
+     /*
+      * IGNORE-ERRORS yes
+      * IGNORE-ERRORS no
+      */
+
+      if (_ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
+      {
+        data->def_ignore_errors = !_cups_strcasecmp(temp, "yes");
+      }
+      else
+      {
+        print_fatal_error(data, "Missing IGNORE-ERRORS value on line %d of \"%s\".", f->linenum, f->filename);
+        return (0);
+      }
+    }
+    else if (!strcmp(token, "INCLUDE"))
+    {
+     /*
+      * INCLUDE "filename"
+      * INCLUDE <filename>
+      */
 
-         if (*ptr || ptr == attr->values[i].string.text)
-         {
-           valid = 0;
+      if (_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       /*
+        * Map the filename to and then run the tests...
+       */
 
-           add_stringf(errors,
-                       "\"%s\": Bad uriScheme value \"%s\" - bad "
-                       "characters (RFC 2911 section 4.1.6).",
-                       attr->name, attr->values[i].string.text);
-         }
+        _cups_testdata_t inc_data;     /* Data for included file */
+        char           filename[1024]; /* Mapped filename */
 
-         if ((ptr - attr->values[i].string.text) > (IPP_MAX_URISCHEME - 1))
-         {
-           valid = 0;
+        memcpy(&inc_data, data, sizeof(inc_data));
+        inc_data.http        = NULL;
+       inc_data.pass        = 1;
+       inc_data.prev_pass   = 1;
+       inc_data.show_header = 1;
 
-           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));
-         }
+        if (!do_tests(get_filename(f->filename, filename, temp, sizeof(filename)), vars, &inc_data) && data->stop_after_include_error)
+        {
+          data->pass = data->prev_pass = 0;
+          return (0);
        }
-        break;
+      }
+      else
+      {
+        print_fatal_error(data, "Missing INCLUDE filename on line %d of \"%s\".", f->linenum, f->filename);
+        return (0);
+      }
 
-    case IPP_TAG_CHARSET :
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         for (ptr = attr->values[i].string.text; *ptr; ptr ++)
-           if (!isprint(*ptr & 255) || isupper(*ptr & 255) ||
-               isspace(*ptr & 255))
-             break;
+      data->show_header = 1;
+    }
+    else if (!strcmp(token, "INCLUDE-IF-DEFINED"))
+    {
+     /*
+      * INCLUDE-IF-DEFINED name "filename"
+      * INCLUDE-IF-DEFINED name <filename>
+      */
 
-         if (*ptr || ptr == attr->values[i].string.text)
-         {
-           valid = 0;
+      if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
+      {
+       /*
+        * Map the filename to and then run the tests...
+       */
 
-           add_stringf(errors,
-                       "\"%s\": Bad charset value \"%s\" - bad "
-                       "characters (RFC 2911 section 4.1.7).",
-                       attr->name, attr->values[i].string.text);
-         }
+        _cups_testdata_t inc_data;     /* Data for included file */
+        char           filename[1024]; /* Mapped filename */
 
-         if ((ptr - attr->values[i].string.text) > (IPP_MAX_CHARSET - 1))
-         {
-           valid = 0;
+        memcpy(&inc_data, data, sizeof(inc_data));
+        inc_data.http        = NULL;
+       inc_data.pass        = 1;
+       inc_data.prev_pass   = 1;
+       inc_data.show_header = 1;
 
-           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));
-         }
+        if (!do_tests(get_filename(f->filename, filename, temp, sizeof(filename)), vars, &inc_data) && data->stop_after_include_error)
+        {
+          data->pass = data->prev_pass = 0;
+          return (0);
        }
-        break;
+      }
+      else
+      {
+        print_fatal_error(data, "Missing INCLUDE-IF-DEFINED name or filename on line %d of \"%s\".", f->linenum, f->filename);
+        return (0);
+      }
 
-    case IPP_TAG_LANGUAGE :
+      data->show_header = 1;
+    }
+    else if (!strcmp(token, "INCLUDE-IF-NOT-DEFINED"))
+    {
+     /*
+      * INCLUDE-IF-NOT-DEFINED name "filename"
+      * INCLUDE-IF-NOT-DEFINED name <filename>
+      */
+
+      if (_ippFileReadToken(f, name, sizeof(name)) && _ippFileReadToken(f, temp, sizeof(temp)))
+      {
        /*
-        * The following regular expression is derived from the ABNF for
-       * language tags in RFC 4646.  All I can say is that this is the
-       * easiest way to check the values...
+        * Map the filename to and then run the tests...
        */
 
-        if ((i = regcomp(&re,
-                        "^("
-                        "(([a-z]{2,3}(-[a-z][a-z][a-z]){0,3})|[a-z]{4,8})"
-                                                               /* language */
-                        "(-[a-z][a-z][a-z][a-z]){0,1}"         /* script */
-                        "(-([a-z][a-z]|[0-9][0-9][0-9])){0,1}" /* region */
-                        "(-([a-z]{5,8}|[0-9][0-9][0-9]))*"     /* variant */
-                        "(-[a-wy-z](-[a-z0-9]{2,8})+)*"        /* extension */
-                        "(-x(-[a-z0-9]{1,8})+)*"               /* privateuse */
-                        "|"
-                        "x(-[a-z0-9]{1,8})+"                   /* privateuse */
-                        "|"
-                        "[a-z]{1,3}(-[a-z][0-9]{2,8}){1,2}"    /* grandfathered */
-                        ")$",
-                        REG_NOSUB | REG_EXTENDED)) != 0)
-        {
-          char temp[256];              /* Temporary error string */
+        _cups_testdata_t inc_data;     /* Data for included file */
+        char           filename[1024]; /* Mapped filename */
 
-          regerror(i, &re, temp, sizeof(temp));
-         print_fatal_error(outfile, "Unable to compile naturalLanguage regular "
-                           "expression: %s.", temp);
-         break;
-        }
+        memcpy(&inc_data, data, sizeof(inc_data));
+        inc_data.http        = NULL;
+       inc_data.pass        = 1;
+       inc_data.prev_pass   = 1;
+       inc_data.show_header = 1;
 
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         if (regexec(&re, attr->values[i].string.text, 0, NULL, 0))
-         {
-           valid = 0;
+        if (!do_tests(get_filename(f->filename, filename, temp, sizeof(filename)), vars, &inc_data) && data->stop_after_include_error)
+        {
+          data->pass = data->prev_pass = 0;
+          return (0);
+       }
+      }
+      else
+      {
+        print_fatal_error(data, "Missing INCLUDE-IF-NOT-DEFINED name or filename on line %d of \"%s\".", f->linenum, f->filename);
+        return (0);
+      }
 
-           add_stringf(errors,
-                       "\"%s\": Bad naturalLanguage value \"%s\" - bad "
-                       "characters (RFC 2911 section 4.1.8).",
-                       attr->name, attr->values[i].string.text);
-         }
+      data->show_header = 1;
+    }
+    else if (!strcmp(token, "SKIP-IF-DEFINED"))
+    {
+     /*
+      * SKIP-IF-DEFINED variable
+      */
 
-         if (strlen(attr->values[i].string.text) > (IPP_MAX_LANGUAGE - 1))
-         {
-           valid = 0;
+      if (_ippFileReadToken(f, name, sizeof(name)))
+      {
+        if (_ippVarsGet(vars, name))
+          data->skip_test = 1;
+      }
+      else
+      {
+        print_fatal_error(data, "Missing SKIP-IF-DEFINED variable on line %d of \"%s\".", f->linenum, f->filename);
+        return (0);
+      }
+    }
+    else if (!strcmp(token, "SKIP-IF-NOT-DEFINED"))
+    {
+     /*
+      * SKIP-IF-NOT-DEFINED variable
+      */
 
-           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));
-         }
-       }
+      if (_ippFileReadToken(f, name, sizeof(name)))
+      {
+        if (!_ippVarsGet(vars, name))
+          data->skip_test = 1;
+      }
+      else
+      {
+        print_fatal_error(data, "Missing SKIP-IF-NOT-DEFINED variable on line %d of \"%s\".", f->linenum, f->filename);
+        return (0);
+      }
+    }
+    else if (!strcmp(token, "STOP-AFTER-INCLUDE-ERROR"))
+    {
+     /*
+      * STOP-AFTER-INCLUDE-ERROR yes
+      * STOP-AFTER-INCLUDE-ERROR no
+      */
 
-       regfree(&re);
-        break;
+      if (_ippFileReadToken(f, temp, sizeof(temp)) && (!_cups_strcasecmp(temp, "yes") || !_cups_strcasecmp(temp, "no")))
+      {
+        data->stop_after_include_error = !_cups_strcasecmp(temp, "yes");
+      }
+      else
+      {
+        print_fatal_error(data, "Missing STOP-AFTER-INCLUDE-ERROR value on line %d of \"%s\".", f->linenum, f->filename);
+        return (0);
+      }
+    }
+    else if (!strcmp(token, "TRANSFER"))
+    {
+     /*
+      * TRANSFER auto
+      * TRANSFER chunked
+      * TRANSFER length
+      */
 
-    case IPP_TAG_MIMETYPE :
-       /*
-        * The following regular expression is derived from the ABNF for
-       * language tags in RFC 2045 and 4288.  All I can say is that this is
-       * the easiest way to check the values...
-       */
+      if (_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+        if (!strcmp(temp, "auto"))
+         data->def_transfer = _CUPS_TRANSFER_AUTO;
+       else if (!strcmp(temp, "chunked"))
+         data->def_transfer = _CUPS_TRANSFER_CHUNKED;
+       else if (!strcmp(temp, "length"))
+         data->def_transfer = _CUPS_TRANSFER_LENGTH;
+       else
+       {
+         print_fatal_error(data, "Bad TRANSFER value \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+         return (0);
+       }
+      }
+      else
+      {
+        print_fatal_error(data, "Missing TRANSFER value on line %d of \"%s\".", f->linenum, f->filename);
+       return (0);
+      }
+    }
+    else if (!strcmp(token, "VERSION"))
+    {
+      if (_ippFileReadToken(f, temp, sizeof(temp)))
+      {
+        if (!strcmp(temp, "1.0"))
+         data->def_version = 10;
+       else if (!strcmp(temp, "1.1"))
+         data->def_version = 11;
+       else if (!strcmp(temp, "2.0"))
+         data->def_version = 20;
+       else if (!strcmp(temp, "2.1"))
+         data->def_version = 21;
+       else if (!strcmp(temp, "2.2"))
+         data->def_version = 22;
+       else
+       {
+         print_fatal_error(data, "Bad VERSION \"%s\" on line %d of \"%s\".", temp, f->linenum, f->filename);
+         return (0);
+       }
+      }
+      else
+      {
+        print_fatal_error(data, "Missing VERSION number on line %d of \"%s\".", f->linenum, f->filename);
+        return (0);
+      }
+    }
+    else
+    {
+      print_fatal_error(data, "Unexpected token %s seen on line %d of \"%s\".", token, f->linenum, f->filename);
+      return (0);
+    }
+  }
 
-        if ((i = regcomp(&re,
-                        "^"
-                        "[-a-zA-Z0-9!#$&.+^_]{1,127}"          /* type-name */
-                        "/"
-                        "[-a-zA-Z0-9!#$&.+^_]{1,127}"          /* subtype-name */
-                        "(;[-a-zA-Z0-9!#$&.+^_]{1,127}="       /* parameter= */
-                        "([-a-zA-Z0-9!#$&.+^_]{1,127}|\"[^\"]*\"))*"
-                                                               /* value */
-                        "$",
-                        REG_NOSUB | REG_EXTENDED)) != 0)
-        {
-          char temp[256];              /* Temporary error string */
+  return (1);
+}
 
-          regerror(i, &re, temp, sizeof(temp));
-         print_fatal_error(outfile, "Unable to compile mimeMediaType regular "
-                           "expression: %s.", temp);
-         break;
-        }
 
-        for (i = 0; i < attr->num_values; i ++)
-       {
-         if (regexec(&re, attr->values[i].string.text, 0, NULL, 0))
-         {
-           valid = 0;
+/*
+ * 'usage()' - Show program usage.
+ */
 
-           add_stringf(errors,
-                       "\"%s\": Bad mimeMediaType value \"%s\" - bad "
-                       "characters (RFC 2911 section 4.1.9).",
-                       attr->name, attr->values[i].string.text);
-         }
+static void
+usage(void)
+{
+  _cupsLangPuts(stderr, _("Usage: ipptool [options] URI filename [ ... filenameN ]"));
+  _cupsLangPuts(stderr, _("Options:"));
+  _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."));
 
-         if (strlen(attr->values[i].string.text) > (IPP_MAX_MIMETYPE - 1))
-         {
-           valid = 0;
+  exit(1);
+}
 
-           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;
+/*
+ * 'with_flags_string()' - Return the "WITH-xxx" predicate that corresponds to
+                           the flags.
+ */
 
-    default :
-        break;
+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");
   }
-
-  return (valid);
+  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");
 }
 
 
@@ -5611,18 +4294,20 @@ validate_attr(FILE            *outfile, /* I - Output file */
  */
 
 static int                             /* O - 1 on match, 0 on non-match */
-with_value(FILE            *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 */
-          char            *matchbuf,   /* I - Buffer to hold matching value */
-          size_t          matchlen)    /* I - Length of match buffer */
+with_value(_cups_testdata_t *data,     /* I - Test data */
+           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 */
+          char             *matchbuf,  /* I - Buffer to hold matching value */
+          size_t           matchlen)   /* I - Length of match buffer */
 {
-  int  i,                              /* Looping var */
-       match;                          /* Match? */
-  char temp[1024],                     /* Temporary value string */
-       *valptr;                        /* Pointer into value */
+  int          i,                      /* Looping var */
+               count,                  /* Number of values */
+               match;                  /* Match? */
+  char         temp[1024],             /* Temporary value string */
+               *valptr;                /* Pointer into value */
+  const char   *name;                  /* Attribute name */
 
 
   *matchbuf = '\0';
@@ -5639,15 +4324,20 @@ with_value(FILE            *outfile,    /* I - Output file */
   * Compare the value string to the attribute value.
   */
 
-  switch (attr->value_tag)
+  name  = ippGetName(attr);
+  count = ippGetCount(attr);
+
+  switch (ippGetValueTag(attr))
   {
     case IPP_TAG_INTEGER :
     case IPP_TAG_ENUM :
-        for (i = 0; i < attr->num_values; i ++)
+        for (i = 0; i < count; i ++)
         {
          char  op,                     /* Comparison operator */
                *nextptr;               /* Next pointer */
          int   intvalue,               /* Integer value */
+               attrvalue = ippGetInteger(attr, i),
+                                       /* Attribute value */
                valmatch = 0;           /* Does the current value match? */
 
           valptr = value;
@@ -5672,12 +4362,12 @@ with_value(FILE            *outfile,    /* I - Output file */
              break;
            valptr = nextptr;
 
-            if ((op == '=' && attr->values[i].integer == intvalue) ||
-                (op == '<' && attr->values[i].integer < intvalue) ||
-                (op == '>' && attr->values[i].integer > intvalue))
+            if ((op == '=' && attrvalue == intvalue) ||
+                (op == '<' && attrvalue < intvalue) ||
+                (op == '>' && attrvalue > intvalue))
            {
              if (!matchbuf[0])
-               snprintf(matchbuf, matchlen, "%d", attr->values[i].integer);
+               snprintf(matchbuf, matchlen, "%d", attrvalue);
 
              valmatch = 1;
              break;
@@ -5701,20 +4391,22 @@ with_value(FILE            *outfile,    /* I - Output file */
 
         if (!match && errors)
        {
-         for (i = 0; i < attr->num_values; i ++)
-           add_stringf(errors, "GOT: %s=%d", attr->name,
-                       attr->values[i].integer);
+         for (i = 0; i < count; i ++)
+           add_stringf(data->errors, "GOT: %s=%d", name, ippGetInteger(attr, i));
        }
        break;
 
     case IPP_TAG_RANGE :
-        for (i = 0; i < attr->num_values; i ++)
+        for (i = 0; i < count; i ++)
         {
          char  op,                     /* Comparison operator */
                *nextptr;               /* Next pointer */
          int   intvalue,               /* Integer value */
+               lower,                  /* Lower range */
+               upper,                  /* Upper range */
                valmatch = 0;           /* Does the current value match? */
 
+         lower = ippGetRange(attr, i, &upper);
           valptr = value;
 
          while (isspace(*valptr & 255) || isdigit(*valptr & 255) ||
@@ -5737,15 +4429,12 @@ with_value(FILE            *outfile,    /* I - Output file */
              break;
            valptr = nextptr;
 
-            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))
+            if ((op == '=' && (lower == intvalue || upper == intvalue)) ||
+               (op == '<' && upper < intvalue) ||
+               (op == '>' && upper > intvalue))
            {
              if (!matchbuf[0])
-               snprintf(matchbuf, matchlen, "%d-%d",
-                        attr->values[0].range.lower,
-                        attr->values[0].range.upper);
+               snprintf(matchbuf, matchlen, "%d-%d", lower, upper);
 
              valmatch = 1;
              break;
@@ -5769,17 +4458,20 @@ with_value(FILE            *outfile,    /* I - Output file */
 
         if (!match && errors)
        {
-         for (i = 0; i < attr->num_values; i ++)
-           add_stringf(errors, "GOT: %s=%d-%d", attr->name,
-                            attr->values[i].range.lower,
-                            attr->values[i].range.upper);
+         for (i = 0; i < count; i ++)
+         {
+           int lower, upper;           /* Range values */
+
+           lower = ippGetRange(attr, i, &upper);
+           add_stringf(data->errors, "GOT: %s=%d-%d", name, lower, upper);
+         }
        }
        break;
 
     case IPP_TAG_BOOLEAN :
-       for (i = 0; i < attr->num_values; i ++)
+       for (i = 0; i < count; i ++)
        {
-          if (!strcmp(value, "true") == attr->values[i].boolean)
+          if ((!strcmp(value, "true")) == ippGetBoolean(attr, i))
           {
             if (!matchbuf[0])
              strlcpy(matchbuf, value, matchlen);
@@ -5799,27 +4491,22 @@ with_value(FILE            *outfile,    /* I - Output file */
 
        if (!match && errors)
        {
-         for (i = 0; i < attr->num_values; i ++)
-           add_stringf(errors, "GOT: %s=%s", attr->name,
-                            attr->values[i].boolean ? "true" : "false");
+         for (i = 0; i < count; i ++)
+           add_stringf(data->errors, "GOT: %s=%s", name, ippGetBoolean(attr, i) ? "true" : "false");
        }
        break;
 
     case IPP_TAG_RESOLUTION :
-       for (i = 0; i < attr->num_values; i ++)
+       for (i = 0; i < count; 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");
+         int           xres, yres;     /* Resolution values */
+         ipp_res_t     units;          /* Resolution units */
+
+         xres = ippGetResolution(attr, i, &yres, &units);
+         if (xres == yres)
+           snprintf(temp, sizeof(temp), "%d%s", xres, 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");
+           snprintf(temp, sizeof(temp), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
 
           if (!strcmp(value, temp))
           {
@@ -5841,23 +4528,19 @@ with_value(FILE            *outfile,    /* I - Output file */
 
        if (!match && errors)
        {
-         for (i = 0; i < attr->num_values; i ++)
+         for (i = 0; i < count; 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");
+           int         xres, yres;     /* Resolution values */
+           ipp_res_t   units;          /* Resolution units */
+
+           xres = ippGetResolution(attr, i, &yres, &units);
+           if (xres == yres)
+             snprintf(temp, sizeof(temp), "%d%s", xres, 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");
+             snprintf(temp, sizeof(temp), "%dx%d%s", xres, yres, units == IPP_RES_PER_INCH ? "dpi" : "dpcm");
 
             if (strcmp(value, temp))
-             add_stringf(errors, "GOT: %s=%s", attr->name, temp);
+             add_stringf(data->errors, "GOT: %s=%s", name, temp);
          }
        }
        break;
@@ -5888,8 +4571,7 @@ with_value(FILE            *outfile,      /* I - Output file */
          {
             regerror(i, &re, temp, sizeof(temp));
 
-           print_fatal_error(outfile, "Unable to compile WITH-VALUE regular expression "
-                             "\"%s\" - %s", value, temp);
+           print_fatal_error(data, "Unable to compile WITH-VALUE regular expression \"%s\" - %s", value, temp);
            return (0);
          }
 
@@ -5897,15 +4579,13 @@ with_value(FILE            *outfile,    /* I - Output file */
          * See if ALL of the values match the given regular expression.
          */
 
-         for (i = 0; i < attr->num_values; i ++)
+         for (i = 0; i < count; i ++)
          {
            if (!regexec(&re, get_string(attr, i, flags, temp, sizeof(temp)),
                         0, NULL, 0))
            {
              if (!matchbuf[0])
-               strlcpy(matchbuf,
-                       get_string(attr, i, flags, temp, sizeof(temp)),
-                       matchlen);
+               strlcpy(matchbuf, get_string(attr, i, flags, temp, sizeof(temp)), matchlen);
 
              if (!(flags & _CUPS_WITH_ALL))
              {
@@ -5922,41 +4602,18 @@ with_value(FILE            *outfile,    /* I - Output file */
 
          regfree(&re);
        }
-       else if (ippGetValueTag(attr) == IPP_TAG_URI)
+       else if (ippGetValueTag(attr) == IPP_TAG_URI && !(flags & (_CUPS_WITH_SCHEME | _CUPS_WITH_HOSTNAME | _CUPS_WITH_RESOURCE)))
        {
-          if (!strncmp(value, "ipp://", 6) || !strncmp(value, "http://", 7) || !strncmp(value, "ipps://", 7) || !strncmp(value, "https://", 8))
-          {
-           char        scheme[256],    /* URI scheme */
-                       userpass[256],  /* username:password, if any */
-                       hostname[256],  /* hostname */
-                       *hostptr,       /* Pointer into hostname */
-                       resource[1024]; /* Resource path */
-           int         port;           /* Port number */
-
-            if (httpSeparateURI(HTTP_URI_CODING_ALL, value, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource)) >= HTTP_URI_STATUS_OK && (hostptr = hostname + strlen(hostname) - 1) > hostname && *hostptr == '.')
-            {
-             /*
-              * Strip trailing "." in hostname of URI...
-              */
-
-              *hostptr = '\0';
-              httpAssembleURI(HTTP_URI_CODING_ALL, temp, sizeof(temp), scheme, userpass, hostname, port, resource);
-              value = temp;
-            }
-          }
-
         /*
          * Value is a literal URI string, see if the value(s) match...
          */
 
-         for (i = 0; i < attr->num_values; i ++)
+         for (i = 0; i < count; i ++)
          {
-           if (!strcmp(value, get_string(attr, i, flags, temp, sizeof(temp))))
+           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);
+               strlcpy(matchbuf, get_string(attr, i, flags, temp, sizeof(temp)), matchlen);
 
              if (!(flags & _CUPS_WITH_ALL))
              {
@@ -5977,14 +4634,51 @@ with_value(FILE            *outfile,    /* I - Output file */
          * Value is a literal string, see if the value(s) match...
          */
 
-         for (i = 0; i < attr->num_values; i ++)
+         for (i = 0; i < count; i ++)
          {
-           if (!strcmp(value, get_string(attr, i, flags, temp, sizeof(temp))))
+           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);
+               strlcpy(matchbuf, get_string(attr, i, flags, temp, sizeof(temp)), matchlen);
 
              if (!(flags & _CUPS_WITH_ALL))
              {
@@ -6002,9 +4696,8 @@ with_value(FILE            *outfile,      /* I - Output file */
 
         if (!match && errors)
         {
-         for (i = 0; i < attr->num_values; i ++)
-           add_stringf(errors, "GOT: %s=\"%s\"", attr->name,
-                       attr->values[i].string.text);
+         for (i = 0; i < count; i ++)
+           add_stringf(data->errors, "GOT: %s=\"%s\"", name, ippGetString(attr, i, NULL));
         }
        break;
 
@@ -6140,7 +4833,6 @@ with_value_from(
     case IPP_TAG_NAMELANG :
     case IPP_TAG_TEXT :
     case IPP_TAG_TEXTLANG :
-    case IPP_TAG_URI :
     case IPP_TAG_URISCHEME :
        for (i = 0; i < count; i ++)
        {
@@ -6160,6 +4852,31 @@ with_value_from(
        }
        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;
@@ -6174,8 +4891,3 @@ with_value_from(
 
   return (0);
 }
-
-
-/*
- * End of "$Id$".
- */