]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - test/ipptool.c
Merge changes from CUPS 1.6svn-r9968.
[thirdparty/cups.git] / test / ipptool.c
index 689da2416beaac709bcca93000ef4431a10f0557..ef3d86c76aeacbadd71f736a694e3b3627f0dbbc 100644 (file)
@@ -3,7 +3,7 @@
  *
  *   ipptool command for CUPS.
  *
- *   Copyright 2007-2010 by Apple Inc.
+ *   Copyright 2007-2011 by Apple Inc.
  *   Copyright 1997-2007 by Easy Software Products.
  *
  *   These coded instructions, statements, and computer programs are the
@@ -12,6 +12,8 @@
  *   which should have been included with this file.  If this file is
  *   file is missing or damaged, see the license at "http://www.cups.org/".
  *
+ *   This file is subject to the Apple OS-Developed Software exception.
+ *
  * Contents:
  *
  *   main()              - Parse options and do tests.
  *   get_variable()      - Get the value of a variable.
  *   iso_date()          - Return an ISO 8601 date/time string for the given IPP
  *                         dateTime value.
+ *   password_cb()       - Password callback for authenticated tests.
  *   print_attr()        - Print an attribute on the screen.
  *   print_col()         - Print a collection attribute on the screen.
  *   print_csv()         - Print a line of CSV text.
  *   print_fatal_error() - Print a fatal error message.
- *   print_line()        - Print a line of formatted text.
+ *   print_line()        - Print a line of formatted or CSV text.
  *   print_test_error()  - Print a test error message.
  *   print_xml_header()  - Print a standard XML plist header.
  *   print_xml_string()  - Print an XML string with escaping.
  *   print_xml_trailer() - Print the XML trailer with success/fail value.
  *   set_variable()      - Set a variable value.
+ *   sigterm_handler()   - Handle SIGINT and SIGTERM.
+ *   timeout_cb()        - Handle HTTP timeouts.
  *   usage()             - Show program usage.
  *   validate_attr()     - Determine whether an attribute is valid.
  *   with_value()        - Test a WITH-VALUE predicate.
@@ -47,6 +52,7 @@
 #include <cups/cups-private.h>
 #include <cups/file-private.h>
 #include <regex.h>
+#include <sys/stat.h>
 
 #ifndef O_BINARY
 #  define O_BINARY 0
@@ -81,8 +87,11 @@ typedef struct _cups_expect_s                /**** Expected attribute info ****/
                *of_type,               /* Type name */
                *same_count_as,         /* Parallel attribute name */
                *if_defined,            /* Only required if variable defined */
-               *if_undefined,          /* Only required if variable is not defined */
-               *with_value;            /* Attribute must include this value */
+               *if_not_defined,        /* Only required if variable is not defined */
+               *with_value,            /* Attribute must include this value */
+               *define_match,          /* Variable to define on match */
+               *define_no_match,       /* Variable to define on no-match */
+               *define_value;          /* Variable to define with value */
   int          with_regex,             /* WITH-VALUE is a regular expression */
                count;                  /* Expected count if > 0 */
   ipp_tag_t    in_group;               /* IN-GROUP value */
@@ -92,7 +101,7 @@ typedef struct _cups_status_s                /**** Status info ****/
 {
   ipp_status_t status;                 /* Expected status code */
   char         *if_defined,            /* Only if variable is defined */
-               *if_undefined;          /* Only if variable is not defined */
+               *if_not_defined;        /* Only if variable is not defined */
 } _cups_status_t;
 
 typedef struct _cups_var_s             /**** Variable ****/
@@ -111,6 +120,8 @@ typedef struct _cups_vars_s         /**** Set of variables ****/
                resource[1024];         /* Resource path from URI */
   int          port;                   /* Port number from URI */
   http_encryption_t encryption;                /* Encryption for connection? */
+  double       timeout;                /* Timeout for connection */
+  int          family;                 /* Address family */
   cups_array_t *vars;                  /* Array of variables */
 } _cups_vars_t;
 
@@ -123,9 +134,12 @@ _cups_transfer_t Transfer = _CUPS_TRANSFER_AUTO;
                                        /* How to transfer requests */
 _cups_output_t Output = _CUPS_OUTPUT_LIST;
                                        /* Output mode */
-int            Verbosity = 0,          /* Show all attributes? */
+int            Cancel = 0,             /* Cancel test? */
+               IgnoreErrors = 0,       /* Ignore errors? */
+               Verbosity = 0,          /* Show all attributes? */
                Version = 11,           /* Default IPP version */
                XMLHeader = 0;          /* 1 if header is written */
+char           *Password = NULL;       /* Password from URI */
 const char * const URIStatusStrings[] =        /* URI status strings */
 {
   "URI too large",
@@ -150,11 +164,7 @@ const char * const URIStatusStrings[] =    /* URI status strings */
 static int     compare_vars(_cups_var_t *a, _cups_var_t *b);
 static int     do_tests(_cups_vars_t *vars, const char *testfile);
 static void    expand_variables(_cups_vars_t *vars, char *dst, const char *src,
-                                size_t dstsize)
-#ifdef __GNUC__
-__attribute((nonnull(1,2,3)))
-#endif /* __GNUC__ */
-;
+                                size_t dstsize) __attribute((nonnull(1,2,3)));
 static int      expect_matches(_cups_expect_t *expect, ipp_tag_t value_tag);
 static ipp_t   *get_collection(_cups_vars_t *vars, FILE *fp, int *linenum);
 static char    *get_filename(const char *testfile, char *dst, const char *src,
@@ -163,30 +173,28 @@ 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 void    print_attr(ipp_attribute_t *attr);
+static const char *password_cb(const char *prompt);
+static void    print_attr(ipp_attribute_t *attr, ipp_tag_t *group);
 static void    print_col(ipp_t *col);
 static void    print_csv(ipp_attribute_t *attr, int num_displayed,
                          char **displayed, size_t *widths);
 static void    print_fatal_error(const char *s, ...)
-#ifdef __GNUC__
-__attribute__ ((__format__ (__printf__, 1, 2)))
-#endif /* __GNUC__ */
-;
+               __attribute__ ((__format__ (__printf__, 1, 2)));
 static void    print_line(ipp_attribute_t *attr, int num_displayed,
                           char **displayed, size_t *widths);
 static void    print_test_error(const char *s, ...)
-#ifdef __GNUC__
-__attribute__ ((__format__ (__printf__, 1, 2)))
-#endif /* __GNUC__ */
-;
+               __attribute__ ((__format__ (__printf__, 1, 2)));
 static void    print_xml_header(void);
 static void    print_xml_string(const char *element, const char *s);
 static void    print_xml_trailer(int success, const char *message);
 static void    set_variable(_cups_vars_t *vars, const char *name,
                             const char *value);
-static void    usage(void);
+static void    sigterm_handler(int sig);
+static int     timeout_cb(http_t *http, void *user_data);
+static void    usage(void) __attribute__((noreturn));
 static int     validate_attr(ipp_attribute_t *attr, int print);
-static int      with_value(char *value, int regex, ipp_attribute_t *attr);
+static int      with_value(char *value, int regex, ipp_attribute_t *attr,
+                          int report);
 
 
 /*
@@ -205,7 +213,7 @@ main(int  argc,                             /* I - Number of command-line args */
                        filename[1024], /* Real filename */
                        testname[1024]; /* Real test filename */
   const char           *testfile;      /* Test file to use */
-  int                  interval,       /* Test interval */
+  int                  interval,       /* Test interval in microseconds */
                        repeat;         /* Repeat count */
   _cups_vars_t         vars;           /* Variables */
   http_uri_status_t    uri_status;     /* URI separation status */
@@ -213,6 +221,12 @@ main(int  argc,                            /* I - Number of command-line args */
                                        /* Global data */
 
 
+ /*
+  * Catch SIGINT and SIGTERM...
+  */
+
+  signal(SIGINT, sigterm_handler);
+  signal(SIGTERM, sigterm_handler);
 
  /*
   * Initialize the locale and variables...
@@ -221,7 +235,8 @@ main(int  argc,                             /* I - Number of command-line args */
   _cupsSetLocale(argv);
 
   memset(&vars, 0, sizeof(vars));
-  vars.vars = cupsArrayNew((cups_array_func_t)compare_vars, NULL);
+  vars.family = AF_UNSPEC;
+  vars.vars   = cupsArrayNew((cups_array_func_t)compare_vars, NULL);
 
  /*
   * We need at least:
@@ -242,6 +257,16 @@ main(int  argc,                            /* I - Number of command-line args */
       {
         switch (*opt)
         {
+         case '4' : /* Connect using IPv4 only */
+             vars.family = AF_INET;
+             break;
+
+#ifdef AF_INET6
+         case '6' : /* Connect using IPv6 only */
+             vars.family = AF_INET6;
+             break;
+#endif /* AF_INET6 */
+
           case 'C' : /* Enable HTTP chunking */
               Transfer = _CUPS_TRANSFER_CHUNKED;
               break;
@@ -250,12 +275,15 @@ main(int  argc,                           /* I - Number of command-line args */
 #ifdef HAVE_SSL
              vars.encryption = HTTP_ENCRYPT_REQUIRED;
 #else
-             _cupsLangPrintf(stderr,
-                             _("%s: Sorry, no encryption support compiled in\n"),
+             _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."),
                              argv[0]);
 #endif /* HAVE_SSL */
              break;
 
+          case 'I' : /* Ignore errors */
+             IgnoreErrors = 1;
+             break;
+
           case 'L' : /* Disable HTTP chunking */
               Transfer = _CUPS_TRANSFER_LENGTH;
               break;
@@ -264,19 +292,31 @@ main(int  argc,                           /* I - Number of command-line args */
 #ifdef HAVE_SSL
              vars.encryption = HTTP_ENCRYPT_ALWAYS;
 #else
-             _cupsLangPrintf(stderr,
-                             _("%s: Sorry, no encryption support compiled in\n"),
+             _cupsLangPrintf(stderr, _("%s: Sorry, no encryption support."),
                              argv[0]);
 #endif /* HAVE_SSL */
              break;
 
+         case 'T' : /* Set timeout */
+             i ++;
+
+             if (i >= argc)
+             {
+               _cupsLangPuts(stderr,
+                             _("ipptool: Missing timeout for \"-T\"."));
+               usage();
+              }
+
+             vars.timeout = _cupsStrScand(argv[i], NULL, localeconv());
+             break;
+
          case 'V' : /* Set IPP version */
              i ++;
 
              if (i >= argc)
              {
                _cupsLangPuts(stderr,
-                             _("ipptool: Missing version for \"-V\".\n"));
+                             _("ipptool: Missing version for \"-V\"."));
                usage();
               }
 
@@ -293,7 +333,7 @@ main(int  argc,                             /* I - Number of command-line args */
              else
              {
                _cupsLangPrintf(stderr,
-                               _("ipptool: Bad version %s for \"-V\".\n"),
+                               _("ipptool: Bad version %s for \"-V\"."),
                                argv[i]);
                usage();
              }
@@ -305,7 +345,7 @@ main(int  argc,                             /* I - Number of command-line args */
               if (interval || repeat)
              {
                _cupsLangPuts(stderr, _("ipptool: \"-i\" and \"-n\" are "
-                                       "incompatible with -X\".\n"));
+                                       "incompatible with -X\"."));
                usage();
              }
              break;
@@ -320,7 +360,7 @@ main(int  argc,                             /* I - Number of command-line args */
              if (i >= argc)
              {
                _cupsLangPuts(stderr,
-                             _("ipptool: Missing name=value for \"-d\".\n"));
+                             _("ipptool: Missing name=value for \"-d\"."));
                usage();
               }
 
@@ -339,7 +379,7 @@ main(int  argc,                             /* I - Number of command-line args */
              if (i >= argc)
              {
                _cupsLangPuts(stderr,
-                             _("ipptool: Missing filename for \"-f\".\n"));
+                             _("ipptool: Missing filename for \"-f\"."));
                usage();
               }
 
@@ -362,16 +402,25 @@ main(int  argc,                           /* I - Number of command-line args */
              if (i >= argc)
              {
                _cupsLangPuts(stderr,
-                             _("ipptool: Missing seconds for \"-i\".\n"));
+                             _("ipptool: Missing seconds for \"-i\"."));
                usage();
               }
              else
-               interval = atoi(argv[i]);
+             {
+               interval = (int)(_cupsStrScand(argv[i], NULL, localeconv()) *
+                                1000000.0);
+               if (interval <= 0)
+               {
+                 _cupsLangPuts(stderr,
+                               _("ipptool: Invalid seconds for \"-i\"."));
+                 usage();
+               }
+              }
 
               if (Output == _CUPS_OUTPUT_PLIST && interval)
              {
                _cupsLangPuts(stderr, _("ipptool: \"-i\" is incompatible with "
-                                       "\"-X\".\n"));
+                                       "\"-X\"."));
                usage();
              }
              break;
@@ -386,7 +435,7 @@ main(int  argc,                             /* I - Number of command-line args */
              if (i >= argc)
              {
                _cupsLangPuts(stderr,
-                             _("ipptool: Missing count for \"-n\".\n"));
+                             _("ipptool: Missing count for \"-n\"."));
                usage();
               }
              else
@@ -395,7 +444,7 @@ main(int  argc,                             /* I - Number of command-line args */
               if (Output == _CUPS_OUTPUT_PLIST && repeat)
              {
                _cupsLangPuts(stderr, _("ipptool: \"-n\" is incompatible with "
-                                       "\"-X\".\n"));
+                                       "\"-X\"."));
                usage();
              }
              break;
@@ -413,16 +462,19 @@ main(int  argc,                           /* I - Number of command-line args */
              break;
 
          default :
-             _cupsLangPrintf(stderr, _("ipptool: Unknown option \"-%c\".\n"),
+             _cupsLangPrintf(stderr, _("ipptool: Unknown option \"-%c\"."),
                              *opt);
              usage();
              break;
        }
       }
     }
-    else if (!strncmp(argv[i], "ipp://", 6) ||
-             !strncmp(argv[i], "http://", 7) ||
-             !strncmp(argv[i], "https://", 8))
+    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)
+#endif /* HAVE_SSL */
+            )
     {
      /*
       * Set URI...
@@ -430,10 +482,15 @@ main(int  argc,                           /* I - Number of command-line args */
 
       if (vars.uri)
       {
-        _cupsLangPuts(stderr, _("ipptool: May only specify a single URI.\n"));
+        _cupsLangPuts(stderr, _("ipptool: May only specify a single URI."));
         usage();
       }
 
+#ifdef HAVE_SSL
+      if (!strncmp(argv[i], "ipps://", 7) || !strncmp(argv[i], "https://", 8))
+        vars.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),
@@ -444,17 +501,19 @@ main(int  argc,                           /* I - Number of command-line args */
 
       if (uri_status != HTTP_URI_OK)
       {
-        _cupsLangPrintf(stderr, _("ipptool: Bad URI - %s.\n"),
+        _cupsLangPrintf(stderr, _("ipptool: Bad URI - %s."),
                        URIStatusStrings[uri_status - HTTP_URI_OVERFLOW]);
         return (1);
       }
 
-      if (strcmp(vars.scheme, "http") && strcmp(vars.scheme, "https") &&
-          strcmp(vars.scheme, "ipp"))
+      if (vars.userpass[0])
       {
-        _cupsLangPuts(stderr, _("ipptool: Only http, https, and ipp URIs are "
-                               "supported."));
-       return (1);
+        if ((Password = strchr(vars.userpass, ':')) != NULL)
+         *Password++ = '\0';
+
+        cupsSetUser(vars.userpass);
+       cupsSetPasswordCB(password_cb);
+       set_variable(&vars, "uriuser", vars.userpass);
       }
     }
     else
@@ -495,20 +554,20 @@ main(int  argc,                           /* I - Number of command-line args */
 
   if (Output == _CUPS_OUTPUT_PLIST)
     print_xml_trailer(!status, NULL);
-  else if (interval && repeat > 0)
+  else if (interval > 0 && repeat > 0)
   {
     while (repeat > 1)
     {
-      sleep(interval);
+      usleep(interval);
       do_tests(&vars, testfile);
       repeat --;
     }
   }
-  else if (interval)
+  else if (interval > 0)
   {
     for (;;)
     {
-      sleep(interval);
+      usleep(interval);
       do_tests(&vars, testfile);
     }
   }
@@ -529,7 +588,7 @@ static int                          /* O - Result of comparison */
 compare_vars(_cups_var_t *a,           /* I - First variable */
              _cups_var_t *b)           /* I - Second variable */
 {
-  return (strcasecmp(a->name, b->name));
+  return (_cups_strcasecmp(a->name, b->name));
 }
 
 
@@ -544,16 +603,24 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
   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? */
+               show_header = 1,        /* Show the test header? */
+               ignore_errors,          /* Ignore test failures? */
+               skip_previous = 0;      /* Skip on previous test failure? */
   http_t       *http = NULL;           /* HTTP connection to server */
   FILE         *fp = NULL;             /* Test file */
   char         resource[512],          /* Resource for request */
                token[1024],            /* Token from file */
                *tokenptr,              /* Pointer into token */
-               temp[1024];             /* Temporary string */
-  ipp_t                *request = NULL;        /* IPP request */
-  ipp_t                *response = NULL;       /* IPP response */
+               temp[1024],             /* Temporary string */
+               buffer[8192];           /* Copy buffer */
+  ipp_t                *request = NULL,        /* IPP request */
+               *response = NULL;       /* IPP response */
+  size_t       length;                 /* Length of IPP request */
+  http_status_t        status;                 /* HTTP status */
+  int          fd;                     /* File to send */
+  ssize_t      bytes;                  /* Bytes read/written */
   char         attr[128];              /* Attribute name */
   ipp_op_t     op;                     /* Operation */
   ipp_tag_t    group;                  /* Current group */
@@ -564,7 +631,8 @@ do_tests(_cups_vars_t *vars,                /* I - Variables */
   char         name[1024];             /* Name of test */
   char         filename[1024];         /* Filename */
   _cups_transfer_t transfer;           /* To chunk or not to chunk */
-  int          version;                /* IPP version number to use */
+  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) */
@@ -585,21 +653,34 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
   {
     print_fatal_error("Unable to open test file %s - %s", testfile,
                       strerror(errno));
-    goto test_error;
+    pass = 0;
+    goto test_exit;
   }
 
  /*
   * Connect to the server...
   */
 
-  if ((http = httpConnectEncrypt(vars->hostname, vars->port,
-                                 vars->encryption)) == NULL)
+  if ((http = _httpCreate(vars->hostname, vars->port, NULL, vars->encryption,
+                         vars->family)) == NULL)
+  {
+    print_fatal_error("Unable to connect to %s on port %d - %s", vars->hostname,
+                      vars->port, cupsLastErrorString());
+    pass = 0;
+    goto test_exit;
+  }
+
+  if (httpReconnect(http))
   {
     print_fatal_error("Unable to connect to %s on port %d - %s", vars->hostname,
-                      vars->port, strerror(errno));
-    goto test_error;
+                      vars->port, cupsLastErrorString());
+    pass = 0;
+    goto test_exit;
   }
 
+  if (vars->timeout > 0.0)
+    httpSetTimeout(http, vars->timeout, timeout_cb, NULL);
+
  /*
   * Loop on tests...
   */
@@ -610,7 +691,7 @@ do_tests(_cups_vars_t *vars,                /* I - Variables */
   linenum    = 1;
   request_id = (CUPS_RAND() % 1000) * 137 + 1;
 
-  while (get_token(fp, token, sizeof(token), &linenum) != NULL)
+  while (!Cancel && get_token(fp, token, sizeof(token), &linenum) != NULL)
   {
    /*
     * Expect an open brace...
@@ -632,7 +713,52 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
       {
         print_fatal_error("Missing DEFINE name and/or value on line %d.",
                          linenum);
-        goto test_error;
+       pass = 0;
+       goto test_exit;
+      }
+
+      continue;
+    }
+    else if (!strcmp(token, "DEFINE-DEFAULT"))
+    {
+     /*
+      * DEFINE-DEFAULT name value
+      */
+
+      if (get_token(fp, attr, sizeof(attr), &linenum) &&
+          get_token(fp, temp, sizeof(temp), &linenum))
+      {
+        expand_variables(vars, token, temp, sizeof(token));
+       if (!get_variable(vars, attr))
+         set_variable(vars, attr, token);
+      }
+      else
+      {
+        print_fatal_error("Missing DEFINE-DEFAULT name and/or value on line "
+                         "%d.", linenum);
+       pass = 0;
+       goto test_exit;
+      }
+
+      continue;
+    }
+    else if (!strcmp(token, "IGNORE-ERRORS"))
+    {
+     /*
+      * IGNORE-ERRORS yes
+      * IGNORE-ERRORS no
+      */
+
+      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("Missing IGNORE-ERRORS value on line %d.", linenum);
+       pass = 0;
+       goto test_exit;
       }
 
       continue;
@@ -652,17 +778,131 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
 
         if (!do_tests(vars, get_filename(testfile, filename, temp,
                                         sizeof(filename))))
-         goto test_error;
+       {
+         pass = 0;
+
+         if (!IgnoreErrors)
+           goto test_exit;
+       }
       }
       else
       {
         print_fatal_error("Missing INCLUDE filename on line %d.", linenum);
-        goto test_error;
+       pass = 0;
+       goto test_exit;
       }
 
       show_header = 1;
       continue;
     }
+    else if (!strcmp(token, "INCLUDE-IF-DEFINED"))
+    {
+     /*
+      * INCLUDE-IF-DEFINED name "filename"
+      * INCLUDE-IF-DEFINED name <filename>
+      */
+
+      if (get_token(fp, attr, sizeof(attr), &linenum) &&
+          get_token(fp, temp, sizeof(temp), &linenum))
+      {
+       /*
+        * Map the filename to and then run the tests...
+       */
+
+        if (get_variable(vars, attr) &&
+           !do_tests(vars, get_filename(testfile, filename, temp,
+                                        sizeof(filename))))
+       {
+         pass = 0;
+
+         if (!IgnoreErrors)
+           goto test_exit;
+       }
+      }
+      else
+      {
+        print_fatal_error("Missing INCLUDE-IF-DEFINED name or filename on line "
+                         "%d.", linenum);
+       pass = 0;
+       goto test_exit;
+      }
+
+      show_header = 1;
+      continue;
+    }
+    else if (!strcmp(token, "INCLUDE-IF-NOT-DEFINED"))
+    {
+     /*
+      * INCLUDE-IF-NOT-DEFINED name "filename"
+      * INCLUDE-IF-NOT-DEFINED name <filename>
+      */
+
+      if (get_token(fp, attr, sizeof(attr), &linenum) &&
+          get_token(fp, temp, sizeof(temp), &linenum))
+      {
+       /*
+        * Map the filename to and then run the tests...
+       */
+
+        if (!get_variable(vars, attr) &&
+           !do_tests(vars, get_filename(testfile, filename, temp,
+                                        sizeof(filename))))
+       {
+         pass = 0;
+
+         if (!IgnoreErrors)
+           goto test_exit;
+       }
+      }
+      else
+      {
+        print_fatal_error("Missing INCLUDE-IF-NOT-DEFINED name or filename on "
+                         "line %d.", linenum);
+       pass = 0;
+       goto test_exit;
+      }
+
+      show_header = 1;
+      continue;
+    }
+    else if (!strcmp(token, "SKIP-IF-DEFINED"))
+    {
+     /*
+      * SKIP-IF-DEFINED variable
+      */
+
+      if (get_token(fp, temp, sizeof(temp), &linenum))
+      {
+        if (get_variable(vars, temp))
+         goto test_exit;
+      }
+      else
+      {
+        print_fatal_error("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
+      {
+        print_fatal_error("Missing SKIP-IF-NOT-DEFINED variable on line %d.",
+                         linenum);
+       pass = 0;
+       goto test_exit;
+      }
+    }
     else if (!strcmp(token, "TRANSFER"))
     {
      /*
@@ -683,13 +923,15 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
        {
          print_fatal_error("Bad TRANSFER value \"%s\" on line %d.", temp,
                            linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
       }
       else
       {
         print_fatal_error("Missing TRANSFER value on line %d.", linenum);
-        goto test_error;
+       pass = 0;
+       goto test_exit;
       }
 
       continue;
@@ -711,13 +953,15 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
        else
        {
          print_fatal_error("Bad VERSION \"%s\" on line %d.", temp, linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
       }
       else
       {
         print_fatal_error("Missing VERSION number on line %d.", linenum);
-        goto test_error;
+       pass = 0;
+       goto test_exit;
       }
 
       continue;
@@ -725,7 +969,8 @@ do_tests(_cups_vars_t *vars,                /* I - Variables */
     else if (strcmp(token, "{"))
     {
       print_fatal_error("Unexpected token %s seen on line %d.", token, linenum);
-      goto test_error;
+      pass = 0;
+      goto test_exit;
     }
 
    /*
@@ -748,9 +993,11 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
     request       = ippNew();
     op            = (ipp_op_t)0;
     group         = IPP_TAG_ZERO;
+    ignore_errors = IgnoreErrors;
     last_expect   = NULL;
     last_status   = NULL;
     filename[0]   = '\0';
+    skip_test     = 0;
     version       = Version;
     transfer      = Transfer;
 
@@ -764,17 +1011,20 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
 
     while (get_token(fp, token, sizeof(token), &linenum) != NULL)
     {
-      if (strcasecmp(token, "COUNT") &&
-          strcasecmp(token, "IF-DEFINED") &&
-          strcasecmp(token, "IF-UNDEFINED") &&
-          strcasecmp(token, "IN-GROUP") &&
-          strcasecmp(token, "OF-TYPE") &&
-          strcasecmp(token, "SAME-COUNT-AS") &&
-          strcasecmp(token, "WITH-VALUE"))
+      if (_cups_strcasecmp(token, "COUNT") &&
+          _cups_strcasecmp(token, "DEFINE-MATCH") &&
+          _cups_strcasecmp(token, "DEFINE-NO-MATCH") &&
+          _cups_strcasecmp(token, "DEFINE-VALUE") &&
+          _cups_strcasecmp(token, "IF-DEFINED") &&
+          _cups_strcasecmp(token, "IF-NOT-DEFINED") &&
+          _cups_strcasecmp(token, "IN-GROUP") &&
+          _cups_strcasecmp(token, "OF-TYPE") &&
+          _cups_strcasecmp(token, "SAME-COUNT-AS") &&
+          _cups_strcasecmp(token, "WITH-VALUE"))
         last_expect = NULL;
 
-      if (strcasecmp(token, "IF-DEFINED") &&
-          strcasecmp(token, "IF-UNDEFINED"))
+      if (_cups_strcasecmp(token, "IF-DEFINED") &&
+          _cups_strcasecmp(token, "IF-NOT-DEFINED"))
         last_status = NULL;
 
       if (!strcmp(token, "}"))
@@ -802,7 +1052,8 @@ do_tests(_cups_vars_t *vars,               /* I - Variables */
                                          sizeof(ipp_value_t))) == NULL)
          {
            print_fatal_error("Unable to allocate memory on line %d.", linenum);
-           goto test_error;
+           pass = 0;
+           goto test_exit;
          }
 
          if (tempcol != lastcol)
@@ -823,7 +1074,10 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          lastcol->num_values ++;
        }
        else
-         goto test_error;
+       {
+         pass = 0;
+         goto test_exit;
+       }
       }
       else if (!strcmp(token, "DEFINE"))
       {
@@ -841,10 +1095,32 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
        {
          print_fatal_error("Missing DEFINE name and/or value on line %d.",
                            linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "NAME"))
+      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("Missing IGNORE-ERRORS value on line %d.", linenum);
+         pass = 0;
+         goto test_exit;
+       }
+
+       continue;
+      }
+      else if (!_cups_strcasecmp(token, "NAME"))
       {
        /*
         * Name of test...
@@ -863,21 +1139,82 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
        {
          if (isdigit(temp[0] & 255))
            request_id = atoi(temp);
-         else if (!strcasecmp(temp, "random"))
+         else if (!_cups_strcasecmp(temp, "random"))
            request_id = (CUPS_RAND() % 1000) * 137 + 1;
          else
          {
            print_fatal_error("Bad REQUEST-ID value \"%s\" on line %d.", temp,
                              linenum);
-           goto test_error;
+           pass = 0;
+           goto test_exit;
          }
        }
        else
        {
          print_fatal_error("Missing REQUEST-ID value on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
+       }
+      }
+      else if (!strcmp(token, "SKIP-IF-DEFINED"))
+      {
+       /*
+       * SKIP-IF-DEFINED variable
+       */
+
+       if (get_token(fp, temp, sizeof(temp), &linenum))
+       {
+         if (get_variable(vars, temp))
+           skip_test = 1;
+       }
+       else
+       {
+         print_fatal_error("Missing SKIP-IF-DEFINED value 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))
+           skip_test = 1;
+       }
+       else
+       {
+         print_fatal_error("Missing SKIP-IF-NOT-DEFINED value on line %d.",
+                           linenum);
+         pass = 0;
+         goto test_exit;
        }
       }
+      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
+       {
+         print_fatal_error("Missing SKIP-PREVIOUS-ERROR value on line %d.", linenum);
+         pass = 0;
+         goto test_exit;
+       }
+
+       continue;
+      }
       else if (!strcmp(token, "TRANSFER"))
       {
        /*
@@ -898,16 +1235,18 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
          {
            print_fatal_error("Bad TRANSFER value \"%s\" on line %d.", temp,
                              linenum);
-           goto test_error;
+           pass = 0;
+           goto test_exit;
          }
        }
        else
        {
          print_fatal_error("Missing TRANSFER value on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "VERSION"))
+      else if (!_cups_strcasecmp(token, "VERSION"))
       {
        if (get_token(fp, temp, sizeof(temp), &linenum))
        {
@@ -926,16 +1265,18 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
          else
          {
            print_fatal_error("Bad VERSION \"%s\" on line %d.", temp, linenum);
-           goto test_error;
+           pass = 0;
+           goto test_exit;
          }
        }
        else
        {
          print_fatal_error("Missing VERSION number on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "RESOURCE"))
+      else if (!_cups_strcasecmp(token, "RESOURCE"))
       {
        /*
         * Resource name...
@@ -944,10 +1285,11 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
        if (!get_token(fp, resource, sizeof(resource), &linenum))
        {
          print_fatal_error("Missing RESOURCE path on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "OPERATION"))
+      else if (!_cups_strcasecmp(token, "OPERATION"))
       {
        /*
         * Operation...
@@ -956,17 +1298,20 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
          print_fatal_error("Missing OPERATION code on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
-       if ((op = ippOpValue(token)) < 0 && (op = strtol(token, NULL, 0)) == 0)
+       if ((op = ippOpValue(token)) == (ipp_op_t)-1 &&
+           (op = strtol(token, NULL, 0)) == 0)
        {
          print_fatal_error("Bad OPERATION code \"%s\" on line %d.", token,
                            linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "GROUP"))
+      else if (!_cups_strcasecmp(token, "GROUP"))
       {
        /*
         * Attribute group...
@@ -975,13 +1320,15 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
          print_fatal_error("Missing GROUP tag on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
        if ((value = ippTagValue(token)) < 0)
        {
          print_fatal_error("Bad GROUP tag \"%s\" on line %d.", token, linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
        if (value == group)
@@ -989,30 +1336,37 @@ do_tests(_cups_vars_t *vars,             /* I - Variables */
 
         group = value;
       }
-      else if (!strcasecmp(token, "DELAY"))
+      else if (!_cups_strcasecmp(token, "DELAY"))
       {
        /*
         * Delay before operation...
        */
 
-        int delay;
+        double delay;
 
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
          print_fatal_error("Missing DELAY value on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
-       if ((delay = atoi(token)) <= 0)
+       if ((delay = _cupsStrScand(token, NULL, localeconv())) <= 0.0)
        {
          print_fatal_error("Bad DELAY value \"%s\" on line %d.", token,
                            linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
        else
-         sleep(delay);
+       {
+         if (Output == _CUPS_OUTPUT_TEST)
+           printf("    [%g second delay]\n", delay);
+
+         usleep((int)(1000000.0 * delay));
+       }
       }
-      else if (!strcasecmp(token, "ATTR"))
+      else if (!_cups_strcasecmp(token, "ATTR"))
       {
        /*
         * Attribute...
@@ -1021,26 +1375,30 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
          print_fatal_error("Missing ATTR value tag on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
        if ((value = ippTagValue(token)) == IPP_TAG_ZERO)
        {
          print_fatal_error("Bad ATTR value tag \"%s\" on line %d.", token,
                            linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
        if (!get_token(fp, attr, sizeof(attr), &linenum))
        {
          print_fatal_error("Missing ATTR name on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
        if (!get_token(fp, temp, sizeof(temp), &linenum))
        {
          print_fatal_error("Missing ATTR value on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
         expand_variables(vars, token, temp, sizeof(token));
@@ -1048,7 +1406,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
         switch (value)
        {
          case IPP_TAG_BOOLEAN :
-             if (!strcasecmp(token, "true"))
+             if (!_cups_strcasecmp(token, "true"))
                ippAddBoolean(request, group, attr, 1);
               else
                ippAddBoolean(request, group, attr, atoi(token));
@@ -1056,14 +1414,44 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
          case IPP_TAG_INTEGER :
          case IPP_TAG_ENUM :
-             ippAddInteger(request, group, value, attr, atoi(token));
+             if (!strchr(token, ','))
+               ippAddInteger(request, group, value, attr,
+                             strtol(token, &tokenptr, 0));
+             else
+             {
+               int     values[100],    /* Values */
+                       num_values = 1; /* Number of values */
+
+               values[0] = strtol(token, &tokenptr, 10);
+               while (tokenptr && *tokenptr &&
+                      num_values < (int)(sizeof(values) / sizeof(values[0])))
+               {
+                 if (*tokenptr == ',')
+                   tokenptr ++;
+                 else if (!isdigit(*tokenptr & 255) && *tokenptr != '-')
+                   break;
+
+                 values[num_values] = strtol(tokenptr, &tokenptr, 0);
+                 num_values ++;
+               }
+
+               ippAddIntegers(request, group, value, attr, num_values, values);
+             }
+
+             if (!tokenptr || *tokenptr)
+             {
+               print_fatal_error("Bad %s value \"%s\" on line %d.",
+                                 ippTagString(value), token, linenum);
+               pass = 0;
+               goto test_exit;
+             }
              break;
 
          case IPP_TAG_RESOLUTION :
              {
                int     xres,           /* X resolution */
-               yres;           /* Y resolution */
-               char *ptr;      /* Pointer into value */
+                       yres;           /* Y resolution */
+               char    *ptr;           /* Pointer into value */
 
                xres = yres = strtol(token, (char **)&ptr, 10);
                if (ptr > token && xres > 0)
@@ -1073,18 +1461,19 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
                }
 
                if (ptr <= token || xres <= 0 || yres <= 0 || !ptr ||
-               (strcasecmp(ptr, "dpi") && strcasecmp(ptr, "dpc") &&
-               strcasecmp(ptr, "other")))
+                   (_cups_strcasecmp(ptr, "dpi") && _cups_strcasecmp(ptr, "dpc") &&
+                    _cups_strcasecmp(ptr, "other")))
                {
                  print_fatal_error("Bad resolution value \"%s\" on line %d.",
                                    token, linenum);
-                 goto test_error;
+                 pass = 0;
+                 goto test_exit;
                }
 
-               if (!strcasecmp(ptr, "dpi"))
+               if (!_cups_strcasecmp(ptr, "dpi"))
                  ippAddResolution(request, group, attr, IPP_RES_PER_INCH,
                                   xres, yres);
-               else if (!strcasecmp(ptr, "dpc"))
+               else if (!_cups_strcasecmp(ptr, "dpc"))
                  ippAddResolution(request, group, attr, IPP_RES_PER_CM,
                                   xres, yres);
                else
@@ -1110,7 +1499,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
                {
                  print_fatal_error("Bad rangeOfInteger value \"%s\" on line "
                                    "%d.", token, linenum);
-                 goto test_error;
+                 pass = 0;
+                 goto test_exit;
                }
 
                ippAddRanges(request, group, attr, num_vals / 2, lowers,
@@ -1130,20 +1520,25 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
                  ippDelete(col);
                }
                else
-                 goto test_error;
+               {
+                 pass = 0;
+                 goto test_exit;
+               }
               }
              else
              {
                print_fatal_error("Bad ATTR collection value on line %d.",
                                  linenum);
-               goto test_error;
+               pass = 0;
+               goto test_exit;
              }
              break;
 
          default :
              print_fatal_error("Unsupported ATTR value tag %s on line %d.",
                                ippTagString(value), linenum);
-             goto test_error;
+             pass = 0;
+             goto test_exit;
 
          case IPP_TAG_TEXTLANG :
          case IPP_TAG_NAMELANG :
@@ -1184,7 +1579,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
              break;
        }
       }
-      else if (!strcasecmp(token, "FILE"))
+      else if (!_cups_strcasecmp(token, "FILE"))
       {
        /*
         * File...
@@ -1193,13 +1588,14 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
        if (!get_token(fp, temp, sizeof(temp), &linenum))
        {
          print_fatal_error("Missing FILE filename on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
         expand_variables(vars, token, temp, sizeof(token));
        get_filename(testfile, filename, token, sizeof(filename));
       }
-      else if (!strcasecmp(token, "STATUS"))
+      else if (!_cups_strcasecmp(token, "STATUS"))
       {
        /*
         * Status...
@@ -1208,29 +1604,34 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
         if (num_statuses >= (int)(sizeof(statuses) / sizeof(statuses[0])))
        {
          print_fatal_error("Too many STATUS's on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
          print_fatal_error("Missing STATUS code on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
-       if ((statuses[num_statuses].status = ippErrorValue(token)) < 0)
+       if ((statuses[num_statuses].status = ippErrorValue(token))
+               == (ipp_status_t)-1 &&
+           (statuses[num_statuses].status = strtol(token, NULL, 0)) == 0)
        {
          print_fatal_error("Bad STATUS code \"%s\" on line %d.", token,
                            linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
         last_status = statuses + num_statuses;
        num_statuses ++;
 
        last_status->if_defined   = NULL;
-       last_status->if_undefined = NULL;
+       last_status->if_not_defined = NULL;
       }
-      else if (!strcasecmp(token, "EXPECT"))
+      else if (!_cups_strcasecmp(token, "EXPECT"))
       {
        /*
         * Expected attributes...
@@ -1239,13 +1640,15 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
         if (num_expects >= (int)(sizeof(expects) / sizeof(expects[0])))
         {
          print_fatal_error("Too many EXPECT's on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
         }
 
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
          print_fatal_error("Missing EXPECT name on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
         last_expect = expects + num_expects;
@@ -1266,18 +1669,20 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
         else
          last_expect->name = strdup(token);
       }
-      else if (!strcasecmp(token, "COUNT"))
+      else if (!_cups_strcasecmp(token, "COUNT"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
          print_fatal_error("Missing COUNT number on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
         if ((i = atoi(token)) <= 0)
        {
          print_fatal_error("Bad COUNT \"%s\" on line %d.", token, linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
        if (last_expect)
@@ -1286,16 +1691,78 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
        {
          print_fatal_error("COUNT without a preceding EXPECT on line %d.",
                            linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
+       }
+      }
+      else if (!_cups_strcasecmp(token, "DEFINE-MATCH"))
+      {
+       if (!get_token(fp, token, sizeof(token), &linenum))
+       {
+         print_fatal_error("Missing DEFINE-MATCH variable on line %d.",
+                           linenum);
+         pass = 0;
+         goto test_exit;
+       }
+
+       if (last_expect)
+         last_expect->define_match = strdup(token);
+       else
+       {
+         print_fatal_error("DEFINE-MATCH without a preceding EXPECT 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("Missing DEFINE-NO-MATCH variable on line %d.",
+                           linenum);
+         pass = 0;
+         goto test_exit;
+       }
+
+       if (last_expect)
+         last_expect->define_no_match = strdup(token);
+       else
+       {
+         print_fatal_error("DEFINE-NO-MATCH without a preceding EXPECT on "
+                           "line %d.", linenum);
+         pass = 0;
+         goto test_exit;
+       }
+      }
+      else if (!_cups_strcasecmp(token, "DEFINE-VALUE"))
+      {
+       if (!get_token(fp, token, sizeof(token), &linenum))
+       {
+         print_fatal_error("Missing DEFINE-VALUE variable on line %d.",
+                           linenum);
+         pass = 0;
+         goto test_exit;
+       }
+
+       if (last_expect)
+         last_expect->define_value = strdup(token);
+       else
+       {
+         print_fatal_error("DEFINE-VALUE without a preceding EXPECT on line "
+                           "%d.", linenum);
+         pass = 0;
+         goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "OF-TYPE"))
+      else if (!_cups_strcasecmp(token, "OF-TYPE"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
          print_fatal_error("Missing OF-TYPE value tag(s) on line %d.",
                            linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
        if (last_expect)
@@ -1304,10 +1771,11 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
        {
          print_fatal_error("OF-TYPE without a preceding EXPECT on line %d.",
                            linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "IN-GROUP"))
+      else if (!_cups_strcasecmp(token, "IN-GROUP"))
       {
         ipp_tag_t      in_group;       /* IN-GROUP value */
 
@@ -1315,7 +1783,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
          print_fatal_error("Missing IN-GROUP group tag on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
         if ((in_group = ippTagValue(token)) == (ipp_tag_t)-1)
@@ -1327,15 +1796,17 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
        {
          print_fatal_error("IN-GROUP without a preceding EXPECT on line %d.",
                            linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "SAME-COUNT-AS"))
+      else if (!_cups_strcasecmp(token, "SAME-COUNT-AS"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
          print_fatal_error("Missing SAME-COUNT-AS name on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
        if (last_expect)
@@ -1344,15 +1815,17 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
        {
          print_fatal_error("SAME-COUNT-AS without a preceding EXPECT on line "
                            "%d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "IF-DEFINED"))
+      else if (!_cups_strcasecmp(token, "IF-DEFINED"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
          print_fatal_error("Missing IF-DEFINED name on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
        if (last_expect)
@@ -1363,39 +1836,50 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
        {
          print_fatal_error("IF-DEFINED without a preceding EXPECT or STATUS "
                            "on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "IF-UNDEFINED"))
+      else if (!_cups_strcasecmp(token, "IF-NOT-DEFINED"))
       {
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
-         print_fatal_error("Missing IF-UNDEFINED name on line %d.", linenum);
-         goto test_error;
+         print_fatal_error("Missing IF-NOT-DEFINED name on line %d.", linenum);
+         pass = 0;
+         goto test_exit;
        }
 
        if (last_expect)
-         last_expect->if_undefined = strdup(token);
+         last_expect->if_not_defined = strdup(token);
        else if (last_status)
-         last_status->if_undefined = strdup(token);
+         last_status->if_not_defined = strdup(token);
        else
        {
-         print_fatal_error("IF-UNDEFINED without a preceding EXPECT or STATUS "
+         print_fatal_error("IF-NOT-DEFINED without a preceding EXPECT or STATUS "
                            "on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "WITH-VALUE"))
+      else if (!_cups_strcasecmp(token, "WITH-VALUE"))
       {
-       if (!get_token(fp, token, sizeof(token), &linenum))
+       if (!get_token(fp, temp, sizeof(temp), &linenum))
        {
          print_fatal_error("Missing WITH-VALUE value on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
         if (last_expect)
        {
+        /*
+         * Expand any variables in the value and then save it.
+         */
+
+         expand_variables(vars, token, temp, sizeof(token));
+
          tokenptr = token + strlen(token) - 1;
+
          if (token[0] == '/' && tokenptr > token && *tokenptr == '/')
          {
           /*
@@ -1421,10 +1905,11 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
        {
          print_fatal_error("WITH-VALUE without a preceding EXPECT on line %d.",
                            linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
       }
-      else if (!strcasecmp(token, "DISPLAY"))
+      else if (!_cups_strcasecmp(token, "DISPLAY"))
       {
        /*
         * Display attributes...
@@ -1433,13 +1918,15 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
         if (num_displayed >= (int)(sizeof(displayed) / sizeof(displayed[0])))
        {
          print_fatal_error("Too many DISPLAY's on line %d", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
        if (!get_token(fp, token, sizeof(token), &linenum))
        {
          print_fatal_error("Missing DISPLAY name on line %d.", linenum);
-         goto test_error;
+         pass = 0;
+         goto test_exit;
        }
 
        displayed[num_displayed] = strdup(token);
@@ -1449,7 +1936,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
       {
        print_fatal_error("Unexpected token %s seen on line %d.", token,
                          linenum);
-       goto test_error;
+       pass = 0;
+       goto test_exit;
       }
     }
 
@@ -1470,10 +1958,17 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
       puts("<key>Operation</key>");
       print_xml_string("string", ippOpString(op));
       puts("<key>RequestAttributes</key>");
-      puts("<dict>");
-      for (attrptr = request->attrs; attrptr; attrptr = attrptr->next)
-       print_attr(attrptr);
-      puts("</dict>");
+      puts("<array>");
+      if (request->attrs)
+      {
+       puts("<dict>");
+       for (attrptr = request->attrs, group = attrptr->group_tag;
+            attrptr;
+            attrptr = attrptr->next)
+         print_attr(attrptr, &group);
+       puts("</dict>");
+      }
+      puts("</array>");
     }
     else if (Output == _CUPS_OUTPUT_TEST)
     {
@@ -1482,69 +1977,151 @@ do_tests(_cups_vars_t *vars,           /* I - Variables */
         printf("    %s:\n", ippOpString(op));
 
         for (attrptr = request->attrs; attrptr; attrptr = attrptr->next)
-          print_attr(attrptr);
+          print_attr(attrptr, NULL);
       }
 
       printf("    %-69.69s [", name);
       fflush(stdout);
     }
 
+    if ((skip_previous && !prev_pass) || skip_test)
+    {
+      ippDelete(request);
+      request = NULL;
+
+      if (Output == _CUPS_OUTPUT_PLIST)
+      {
+       puts("<key>Successful</key>");
+       puts("<true />");
+       puts("<key>StatusCode</key>");
+       print_xml_string("string", "skip");
+       puts("<key>ResponseAttributes</key>");
+       puts("<dict>");
+       puts("</dict>");
+      }
+      else if (Output == _CUPS_OUTPUT_TEST)
+       puts("SKIP]");
+
+      goto skip_error;
+    }
+
+    status = HTTP_OK;
+
     if (transfer == _CUPS_TRANSFER_CHUNKED ||
         (transfer == _CUPS_TRANSFER_AUTO && filename[0]))
     {
      /*
-      * Send request using chunking...
+      * Send request using chunking - a 0 length means "chunk".
       */
 
-      http_status_t status = cupsSendRequest(http, request, resource, 0);
+      length = 0;
+    }
+    else
+    {
+     /*
+      * Send request using content length...
+      */
+
+      length = ippLength(request);
 
-      if (status == HTTP_CONTINUE && filename[0])
+      if (filename[0])
       {
-        int    fd;                     /* File to send */
-        char   buffer[8192];           /* Copy buffer */
-        ssize_t        bytes;                  /* Bytes read/written */
+        struct stat    fileinfo;       /* File information */
 
-        if ((fd = open(filename, O_RDONLY | O_BINARY)) >= 0)
+        if (stat(filename, &fileinfo))
         {
-          while ((bytes = read(fd, buffer, sizeof(buffer))) > 0)
-            if ((status = cupsWriteRequestData(http, buffer,
-                                               bytes)) != HTTP_CONTINUE)
-              break;
-        }
-        else
-       {
          snprintf(buffer, sizeof(buffer), "%s: %s", filename, strerror(errno));
          _cupsSetError(IPP_INTERNAL_ERROR, buffer, 0);
 
           status = HTTP_ERROR;
-       }
+        }
+        else
+          length += fileinfo.st_size;
       }
+    }
 
-      ippDelete(request);
+   /*
+    * Send the request...
+    */
 
-      if (status == HTTP_CONTINUE)
-       response = cupsGetResponse(http, resource);
-      else
-       response = NULL;
+    response = NULL;
+
+    if (status != HTTP_ERROR)
+    {
+      while (!response && !Cancel)
+      {
+       status = cupsSendRequest(http, request, resource, length);
+
+       if (!Cancel && status == HTTP_CONTINUE && request->state == IPP_DATA &&
+           filename[0])
+       {
+         if ((fd = open(filename, O_RDONLY | O_BINARY)) >= 0)
+         {
+           while (!Cancel && (bytes = read(fd, buffer, sizeof(buffer))) > 0)
+             if ((status = cupsWriteRequestData(http, buffer,
+                                                bytes)) != HTTP_CONTINUE)
+               break;
+         }
+         else
+         {
+           snprintf(buffer, sizeof(buffer), "%s: %s", filename,
+                    strerror(errno));
+           _cupsSetError(IPP_INTERNAL_ERROR, buffer, 0);
+
+           status = HTTP_ERROR;
+         }
+       }
+
+       /*
+       * Get the server's response...
+       */
+
+       if (!Cancel && (status == HTTP_CONTINUE || status == HTTP_OK))
+       {
+         response = cupsGetResponse(http, resource);
+         status   = http->status;
+       }
+       else
+         httpFlush(http);
+
+       if ((status == HTTP_ERROR && cupsLastError() != IPP_INTERNAL_ERROR) ||
+           (status >= HTTP_BAD_REQUEST && status != HTTP_UNAUTHORIZED &&
+            status != HTTP_UPGRADE_REQUIRED))
+       {
+         _cupsSetHTTPError(status);
+         break;
+       }
+
+       if (http->state != HTTP_WAITING)
+       {
+        /*
+         * Flush any remaining data...
+         */
+
+         httpFlush(http);
+       }
+      }
     }
-    else if (filename[0])
-      response = cupsDoFileRequest(http, request, resource, filename);
-    else
-      response = cupsDoRequest(http, request, resource);
 
-    request = NULL;
+    ippDelete(request);
+
+    request   = NULL;
+    prev_pass = 1;
 
     if (!response)
-      pass = 0;
+      prev_pass = pass = 0;
     else
     {
       if (http->version != HTTP_1_1)
-        pass = 0;
+        prev_pass = pass = 0;
+
+      if (response->request.status.request_id != request_id)
+        prev_pass = pass = 0;
 
-      if (response->request.status.version[0] != (version / 10) ||
-         response->request.status.version[1] != (version % 10) ||
-         response->request.status.request_id != request_id)
-        pass = 0;
+      if (version &&
+          (response->request.status.version[0] != (version / 10) ||
+          response->request.status.version[1] != (version % 10)))
+        prev_pass = pass = 0;
 
       if ((attrptr = ippFindAttribute(response, "job-id",
                                       IPP_TAG_INTEGER)) != NULL)
@@ -1570,7 +2147,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
          attrptr->group_tag != IPP_TAG_OPERATION ||
          attrptr->num_values != 1 ||
           strcmp(attrptr->name, "attributes-charset"))
-        pass = 0;
+        prev_pass = pass = 0;
 
       if (attrptr)
       {
@@ -1580,7 +2157,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
            attrptr->group_tag != IPP_TAG_OPERATION ||
            attrptr->num_values != 1 ||
            strcmp(attrptr->name, "attributes-natural-language"))
-         pass = 0;
+         prev_pass = pass = 0;
       }
 
       if ((attrptr = ippFindAttribute(response, "status-message",
@@ -1590,7 +2167,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
           attrptr->num_values != 1 ||
           (attrptr->value_tag == IPP_TAG_TEXT &&
            strlen(attrptr->values[0].string.text) > 255)))
-       pass = 0;
+       prev_pass = pass = 0;
 
       if ((attrptr = ippFindAttribute(response, "detailed-status-message",
                                       IPP_TAG_ZERO)) != NULL &&
@@ -1599,7 +2176,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
           attrptr->num_values != 1 ||
           (attrptr->value_tag == IPP_TAG_TEXT &&
            strlen(attrptr->values[0].string.text) > 1023)))
-       pass = 0;
+       prev_pass = pass = 0;
 
       for (attrptr = response->attrs, group = attrptr->group_tag;
            attrptr;
@@ -1607,13 +2184,13 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
       {
         if (attrptr->group_tag < group && attrptr->group_tag != IPP_TAG_ZERO)
        {
-         pass = 0;
+         prev_pass = pass = 0;
          break;
        }
 
         if (!validate_attr(attrptr, 0))
        {
-         pass = 0;
+         prev_pass = pass = 0;
          break;
        }
       }
@@ -1624,8 +2201,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
            !get_variable(vars, statuses[i].if_defined))
          continue;
 
-        if (statuses[i].if_undefined &&
-           get_variable(vars, statuses[i].if_undefined))
+        if (statuses[i].if_not_defined &&
+           get_variable(vars, statuses[i].if_not_defined))
          continue;
 
         if (response->request.status.status_code == statuses[i].status)
@@ -1633,7 +2210,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
       }
 
       if (i == num_statuses && num_statuses > 0)
-       pass = 0;
+       prev_pass = pass = 0;
       else
       {
         for (i = num_expects, expect = expects; i > 0; i --, expect ++)
@@ -1641,7 +2218,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
           if (expect->if_defined && !get_variable(vars, expect->if_defined))
             continue;
 
-          if (expect->if_undefined && get_variable(vars, expect->if_undefined))
+          if (expect->if_not_defined &&
+             get_variable(vars, expect->if_not_defined))
             continue;
 
           found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO);
@@ -1652,21 +2230,33 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
              (found && expect->in_group &&
               found->group_tag != expect->in_group))
           {
-           pass = 0;
-           break;
+           if (expect->define_no_match)
+             set_variable(vars, expect->define_no_match, "1");
+           else if (!expect->define_match)
+             prev_pass = pass = 0;
+
+           continue;
           }
 
           if (found &&
-             !with_value(expect->with_value, expect->with_regex, found))
+             !with_value(expect->with_value, expect->with_regex, found, 0))
           {
-            pass = 0;
-            break;
+           if (expect->define_no_match)
+             set_variable(vars, expect->define_no_match, "1");
+           else if (!expect->define_match)
+             prev_pass = pass = 0;
+
+            continue;
           }
 
           if (found && expect->count > 0 && found->num_values != expect->count)
          {
-            pass = 0;
-            break;
+           if (expect->define_no_match)
+             set_variable(vars, expect->define_no_match, "1");
+           else if (!expect->define_match)
+             prev_pass = pass = 0;
+
+            continue;
          }
 
           if (found && expect->same_count_as)
@@ -1676,10 +2266,23 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
             if (!attrptr || attrptr->num_values != found->num_values)
             {
-              pass = 0;
-              break;
+             if (expect->define_no_match)
+               set_variable(vars, expect->define_no_match, "1");
+             else if (!expect->define_match)
+               prev_pass = pass = 0;
+
+              continue;
             }
           }
+
+         if (found && expect->define_match)
+           set_variable(vars, expect->define_match, "1");
+
+         if (found && expect->define_value)
+         {
+           _ippAttrString(found, token, sizeof(token));
+           set_variable(vars, expect->define_value, token);
+         }
         }
       }
     }
@@ -1687,20 +2290,23 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
     if (Output == _CUPS_OUTPUT_PLIST)
     {
       puts("<key>Successful</key>");
-      puts(pass ? "<true />" : "<false />");
+      puts(prev_pass ? "<true />" : "<false />");
       puts("<key>StatusCode</key>");
       print_xml_string("string", ippErrorString(cupsLastError()));
       puts("<key>ResponseAttributes</key>");
+      puts("<array>");
       puts("<dict>");
-      for (attrptr = response ? response->attrs : NULL;
+      for (attrptr = response ? response->attrs : NULL,
+               group = attrptr ? attrptr->group_tag : IPP_TAG_ZERO;
           attrptr;
           attrptr = attrptr->next)
-       print_attr(attrptr);
+       print_attr(attrptr, &group);
       puts("</dict>");
+      puts("</array>");
     }
     else if (Output == _CUPS_OUTPUT_TEST)
     {
-      puts(pass ? "PASS]" : "FAIL]");
+      puts(prev_pass ? "PASS]" : "FAIL]");
 
       if (Verbosity && response)
       {
@@ -1713,15 +2319,15 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
             attrptr != NULL;
             attrptr = attrptr->next)
        {
-         print_attr(attrptr);
+         print_attr(attrptr, NULL);
        }
       }
     }
-    else if (!pass)
+    else if (!prev_pass)
       fprintf(stderr, "%s\n", cupsLastErrorString());
 
-    if (pass && Output != _CUPS_OUTPUT_PLIST && Output != _CUPS_OUTPUT_QUIET &&
-        !Verbosity && num_displayed > 0)
+    if (prev_pass && Output != _CUPS_OUTPUT_PLIST &&
+        Output != _CUPS_OUTPUT_QUIET && !Verbosity && num_displayed > 0)
     {
       if (Output >= _CUPS_OUTPUT_LIST)
       {
@@ -1779,7 +2385,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
            {
              if (!strcmp(displayed[i], attrptr->name))
              {
-               print_attr(attrptr);
+               print_attr(attrptr, NULL);
                break;
              }
            }
@@ -1787,7 +2393,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
        }
       }
     }
-    else if (!pass)
+    else if (!prev_pass)
     {
       if (Output == _CUPS_OUTPUT_PLIST)
       {
@@ -1805,8 +2411,9 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
                         cupsLastErrorString());
       else
       {
-       if (response->request.status.version[0] != (version / 10) ||
-           response->request.status.version[1] != (version % 10))
+       if (version &&
+           (response->request.status.version[0] != (version / 10) ||
+            response->request.status.version[1] != (version % 10)))
           print_test_error("Bad version %d.%d in response - expected %d.%d "
                           "(RFC 2911 section 3.1.8).",
                           response->request.status.version[0],
@@ -1923,8 +2530,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
              !get_variable(vars, statuses[i].if_defined))
            continue;
 
-         if (statuses[i].if_undefined &&
-             get_variable(vars, statuses[i].if_undefined))
+         if (statuses[i].if_not_defined &&
+             get_variable(vars, statuses[i].if_not_defined))
            continue;
 
          if (response->request.status.status_code == statuses[i].status)
@@ -1933,17 +2540,37 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
 
        if (i == num_statuses && num_statuses > 0)
        {
-         print_test_error("Bad status-code (%s)",
-                          ippErrorString(cupsLastError()));
-         print_test_error("status-message=\"%s\"", cupsLastErrorString());
+         for (i = 0; i < num_statuses; i ++)
+         {
+           if (statuses[i].if_defined &&
+               !get_variable(vars, statuses[i].if_defined))
+             continue;
+
+           if (statuses[i].if_not_defined &&
+               get_variable(vars, statuses[i].if_not_defined))
+             continue;
+
+           print_test_error("EXPECTED: STATUS %s (got %s)",
+                            ippErrorString(statuses[i].status),
+                            ippErrorString(cupsLastError()));
+         }
+
+         if ((attrptr = ippFindAttribute(response, "status-message",
+                                         IPP_TAG_TEXT)) != NULL)
+           print_test_error("status-message=\"%s\"",
+                            attrptr->values[0].string.text);
         }
 
        for (i = num_expects, expect = expects; i > 0; i --, expect ++)
        {
+         if (expect->define_match || expect->define_no_match)
+           continue;
+
          if (expect->if_defined && !get_variable(vars, expect->if_defined))
            continue;
 
-         if (expect->if_undefined && get_variable(vars, expect->if_undefined))
+         if (expect->if_not_defined &&
+             get_variable(vars, expect->if_not_defined))
            continue;
 
          found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO);
@@ -1964,7 +2591,7 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
                               expect->name, ippTagString(expect->in_group),
                               ippTagString(found->group_tag));
 
-           if (!with_value(expect->with_value, expect->with_regex, found))
+           if (!with_value(expect->with_value, expect->with_regex, found, 0))
            {
              if (expect->with_regex)
                print_test_error("EXPECTED: %s WITH-VALUE /%s/",
@@ -1972,6 +2599,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
              else
                print_test_error("EXPECTED: %s WITH-VALUE \"%s\"",
                                 expect->name, expect->with_value);
+
+             with_value(expect->with_value, expect->with_regex, found, 1);
            }
 
            if (expect->count > 0 && found->num_values != expect->count)
@@ -2002,6 +2631,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
        puts("</array>");
     }
 
+    skip_error:
+
     if (Output == _CUPS_OUTPUT_PLIST)
       puts("</dict>");
 
@@ -2012,8 +2643,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
     {
       if (statuses[i].if_defined)
         free(statuses[i].if_defined);
-      if (statuses[i].if_undefined)
-        free(statuses[i].if_undefined);
+      if (statuses[i].if_not_defined)
+        free(statuses[i].if_not_defined);
     }
     num_statuses = 0;
 
@@ -2026,10 +2657,16 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
         free(expect->same_count_as);
       if (expect->if_defined)
         free(expect->if_defined);
-      if (expect->if_undefined)
-        free(expect->if_undefined);
+      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;
 
@@ -2037,20 +2674,11 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
       free(displayed[i]);
     num_displayed = 0;
 
-    if (!pass)
+    if (!ignore_errors && !prev_pass)
       break;
   }
 
-  fclose(fp);
-  httpClose(http);
-
-  return (pass);
-
- /*
-  * If we get here there was a fatal test error...
-  */
-
-  test_error:
+  test_exit:
 
   if (fp)
     fclose(fp);
@@ -2063,8 +2691,8 @@ do_tests(_cups_vars_t *vars,              /* I - Variables */
   {
     if (statuses[i].if_defined)
       free(statuses[i].if_defined);
-    if (statuses[i].if_undefined)
-      free(statuses[i].if_undefined);
+    if (statuses[i].if_not_defined)
+      free(statuses[i].if_not_defined);
   }
 
   for (i = num_expects, expect = expects; i > 0; i --, expect ++)
@@ -2076,16 +2704,22 @@ do_tests(_cups_vars_t *vars,            /* I - Variables */
       free(expect->same_count_as);
     if (expect->if_defined)
       free(expect->if_defined);
-    if (expect->if_undefined)
-      free(expect->if_undefined);
+    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);
   }
 
   for (i = 0; i < num_displayed; i ++)
     free(displayed[i]);
 
-  return (0);
+  return (pass);
 }
 
 
@@ -2321,7 +2955,7 @@ get_collection(_cups_vars_t *vars,        /* I  - Variables */
       else
        goto col_error;
     }
-    else if (!strcasecmp(token, "MEMBER"))
+    else if (!_cups_strcasecmp(token, "MEMBER"))
     {
      /*
       * Attribute...
@@ -2359,7 +2993,7 @@ get_collection(_cups_vars_t *vars,        /* I  - Variables */
       switch (value)
       {
        case IPP_TAG_BOOLEAN :
-           if (!strcasecmp(token, "true"))
+           if (!_cups_strcasecmp(token, "true"))
              ippAddBoolean(col, IPP_TAG_ZERO, attr, 1);
            else
              ippAddBoolean(col, IPP_TAG_ZERO, attr, atoi(token));
@@ -2377,18 +3011,18 @@ get_collection(_cups_vars_t *vars,      /* I  - Variables */
              char      units[6];       /* Units */
 
              if (sscanf(token, "%dx%d%5s", &xres, &yres, units) != 3 ||
-                 (strcasecmp(units, "dpi") && strcasecmp(units, "dpc") &&
-                  strcasecmp(units, "other")))
+                 (_cups_strcasecmp(units, "dpi") && _cups_strcasecmp(units, "dpc") &&
+                  _cups_strcasecmp(units, "other")))
              {
                print_fatal_error("Bad resolution value \"%s\" on line %d.",
                                  token, *linenum);
                goto col_error;
              }
 
-             if (!strcasecmp(units, "dpi"))
+             if (!_cups_strcasecmp(units, "dpi"))
                ippAddResolution(col, IPP_TAG_ZERO, attr, xres, yres,
                                 IPP_RES_PER_INCH);
-             else if (!strcasecmp(units, "dpc"))
+             else if (!_cups_strcasecmp(units, "dpc"))
                ippAddResolution(col, IPP_TAG_ZERO, attr, xres, yres,
                                 IPP_RES_PER_CM);
              else
@@ -2679,29 +3313,42 @@ get_variable(_cups_vars_t *vars,        /* I - Variables */
 static char *                          /* O - ISO 8601 date/time string */
 iso_date(ipp_uchar_t *date)            /* I - IPP (RFC 1903) date/time value */
 {
-  unsigned     year = (date[0] << 8) + date[1];
-                                       /* Year */
+  time_t       utctime;                /* UTC time since 1970 */
+  struct tm    *utcdate;               /* UTC date/time */
   static char  buffer[255];            /* String buffer */
 
 
-  if (date[9] == 0 && date[10] == 0)
-    snprintf(buffer, sizeof(buffer), "%04u-%02u-%02uT%02u:%02u:%02uZ",
-            year, date[2], date[3], date[4], date[5], date[6]);
-  else
-    snprintf(buffer, sizeof(buffer), "%04u-%02u-%02uT%02u:%02u:%02u%c%02u%02u",
-            year, date[2], date[3], date[4], date[5], date[6],
-            date[8], date[9], date[10]);
+  utctime = ippDateToTime(date);
+  utcdate = gmtime(&utctime);
+
+  snprintf(buffer, sizeof(buffer), "%04d-%02d-%02dT%02d:%02d:%02dZ",
+          utcdate->tm_year + 1900, utcdate->tm_mon + 1, utcdate->tm_mday,
+          utcdate->tm_hour, utcdate->tm_min, utcdate->tm_sec);
 
   return (buffer);
 }
 
 
+/*
+ * 'password_cb()' - Password callback for authenticated tests.
+ */
+
+static const char *                    /* O - Password */
+password_cb(const char *prompt)                /* I - Prompt (unused) */
+{
+  (void)prompt;
+
+  return (Password);
+}
+
+
 /*
  * 'print_attr()' - Print an attribute on the screen.
  */
 
 static void
-print_attr(ipp_attribute_t *attr)      /* I - Attribute to print */
+print_attr(ipp_attribute_t *attr,      /* I  - Attribute to print */
+           ipp_tag_t       *group)     /* IO - Current group */
 {
   int                  i;              /* Looping var */
   ipp_attribute_t      *colattr;       /* Collection attribute */
@@ -2709,12 +3356,18 @@ print_attr(ipp_attribute_t *attr)       /* I - Attribute to print */
 
   if (Output == _CUPS_OUTPUT_PLIST)
   {
-    if (!attr->name)
+    if (!attr->name || (group && *group != attr->group_tag))
     {
-      printf("<key>%s</key>\n<true />\n", ippTagString(attr->group_tag));
-      return;
+      puts("</dict>");
+      puts("<dict>");
+
+      if (group)
+        *group = attr->group_tag;
     }
 
+    if (!attr->name)
+      return;
+
     print_xml_string("key", attr->name);
     if (attr->num_values > 1)
       puts("<array>");
@@ -2816,7 +3469,7 @@ print_attr(ipp_attribute_t *attr) /* I - Attribute to print */
            puts("</string></dict>");
          }
          else
-           printf("\"%s\",%s ", attr->values[i].string.text,
+           printf("\"%s\"(%s) ", attr->values[i].string.text,
                   attr->values[i].string.charset);
        break;
 
@@ -2829,7 +3482,7 @@ print_attr(ipp_attribute_t *attr) /* I - Attribute to print */
            for (colattr = attr->values[i].collection->attrs;
                 colattr;
                 colattr = colattr->next)
-             print_attr(colattr);
+             print_attr(colattr, NULL);
            puts("</dict>");
          }
          else
@@ -3066,7 +3719,7 @@ print_fatal_error(const char *s,  /* I - Printf-style format string */
     print_xml_trailer(0, buffer);
   }
   else
-    _cupsLangPrintf(stderr, "ipptool: %s\n", buffer);
+    _cupsLangPrintf(stderr, "ipptool: %s", buffer);
 }
 
 
@@ -3231,6 +3884,69 @@ print_xml_string(const char *element,    /* I - Element name or NULL */
       fputs("&lt;", stdout);
     else if (*s == '>')
       fputs("&gt;", stdout);
+    else if ((*s & 0xe0) == 0xc0)
+    {
+     /*
+      * Validate UTF-8 two-byte sequence...
+      */
+
+      if ((s[1] & 0xc0) != 0x80)
+      {
+        putchar('?');
+        s ++;
+      }
+      else
+      {
+        putchar(*s++);
+        putchar(*s);
+      }
+    }
+    else if ((*s & 0xf0) == 0xe0)
+    {
+     /*
+      * Validate UTF-8 three-byte sequence...
+      */
+
+      if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80)
+      {
+        putchar('?');
+        s += 2;
+      }
+      else
+      {
+        putchar(*s++);
+        putchar(*s++);
+        putchar(*s);
+      }
+    }
+    else if ((*s & 0xf8) == 0xf0)
+    {
+     /*
+      * Validate UTF-8 four-byte sequence...
+      */
+
+      if ((s[1] & 0xc0) != 0x80 || (s[2] & 0xc0) != 0x80 ||
+          (s[3] & 0xc0) != 0x80)
+      {
+        putchar('?');
+        s += 3;
+      }
+      else
+      {
+        putchar(*s++);
+        putchar(*s++);
+        putchar(*s++);
+        putchar(*s);
+      }
+    }
+    else if ((*s & 0x80) || (*s < ' ' && !isspace(*s & 255)))
+    {
+     /*
+      * Invalid control character...
+      */
+
+      putchar('?');
+    }
     else
       putchar(*s);
 
@@ -3302,6 +4018,35 @@ set_variable(_cups_vars_t *vars, /* I - Variables */
 }
 
 
+/*
+ * 'sigterm_handler()' - Handle SIGINT and SIGTERM.
+ */
+
+static void
+sigterm_handler(int sig)               /* I - Signal number (unused) */
+{
+  (void)sig;
+
+  Cancel = 1;
+}
+
+
+/*
+ * 'timeout_cb()' - Handle HTTP timeouts.
+ */
+
+static int                             /* O - 1 to continue, 0 to cancel */
+timeout_cb(http_t *http,               /* I - Connection to server (unused) */
+           void   *user_data)          /* I - User data (unused) */
+{
+  (void)http;
+  (void)user_data;
+
+ /* Always cancel on timeout */
+  return (0);
+}
+
+
 /*
  * 'usage()' - Show program usage.
  */
@@ -3309,27 +4054,39 @@ set_variable(_cups_vars_t *vars,        /* I - Variables */
 static void
 usage(void)
 {
-  _cupsLangPuts(stderr,
-                _("Usage: ipptool [options] URI filename [ ... "
-                 "filenameN ]\n"
-                 "\n"
-                 "Options:\n"
-                 "\n"
-                 "-C             Send requests using chunking (default)\n"
-                 "-E             Test with TLS encryption.\n"
-                 "-L             Send requests using content-length\n"
-                 "-S             Test with SSL encryption.\n"
-                 "-V version     Set default IPP version.\n"
-                 "-X             Produce XML plist instead of plain text.\n"
-                 "-d name=value  Define variable.\n"
-                 "-f filename    Set default request filename.\n"
-                 "-i seconds     Repeat the last file with the given time "
-                 "interval.\n"
-                 "-n count       Repeat the last file the given number of "
-                 "times.\n"
-                 "-q             Be quiet - no output except errors.\n"
-                 "-t             Produce a test report.\n"
-                 "-v             Show all attributes sent and received.\n"));
+  _cupsLangPuts(stderr, _("Usage: ipptool [options] URI filename [ ... "
+                         "filenameN ]"));
+  _cupsLangPuts(stderr, _("Options:"));
+  _cupsLangPuts(stderr, _("  -4                      Connect using IPv4."));
+  _cupsLangPuts(stderr, _("  -6                      Connect using IPv6."));
+  _cupsLangPuts(stderr, _("  -C                      Send requests using "
+                          "chunking (default)."));
+  _cupsLangPuts(stderr, _("  -E                      Test with TLS "
+                          "encryption."));
+  _cupsLangPuts(stderr, _("  -I                      Ignore errors."));
+  _cupsLangPuts(stderr, _("  -L                      Send requests using "
+                          "content-length."));
+  _cupsLangPuts(stderr, _("  -S                      Test with SSL "
+                         "encryption."));
+  _cupsLangPuts(stderr, _("  -T                      Set the receive/send "
+                          "timeout in seconds."));
+  _cupsLangPuts(stderr, _("  -V version              Set default IPP "
+                          "version."));
+  _cupsLangPuts(stderr, _("  -X                      Produce XML plist instead "
+                          "of plain text."));
+  _cupsLangPuts(stderr, _("  -d name=value           Set named variable to "
+                          "value."));
+  _cupsLangPuts(stderr, _("  -f filename             Set default request "
+                          "filename."));
+  _cupsLangPuts(stderr, _("  -i seconds              Repeat the last file with "
+                          "the given time interval."));
+  _cupsLangPuts(stderr, _("  -n count                Repeat the last file the "
+                          "given number of times."));
+  _cupsLangPuts(stderr, _("  -q                      Be quiet - no output "
+                          "except errors."));
+  _cupsLangPuts(stderr, _("  -t                      Produce a test report."));
+  _cupsLangPuts(stderr, _("  -v                      Show all attributes sent "
+                          "and received."));
 
   exit(1);
 }
@@ -4062,7 +4819,8 @@ validate_attr(ipp_attribute_t *attr,      /* I - Attribute to validate */
 static int                             /* O - 1 on match, 0 on non-match */
 with_value(char            *value,     /* I - Value string */
            int             regex,      /* I - Value is a regular expression */
-           ipp_attribute_t *attr)      /* I - Attribute to compare */
+           ipp_attribute_t *attr,      /* I - Attribute to compare */
+          int             report)      /* I - 1 = report failures */
 {
   int  i;                              /* Looping var */
   char *valptr;                        /* Pointer into value */
@@ -4131,6 +4889,72 @@ with_value(char            *value,       /* I - Value string */
            }
          }
         }
+
+       if (report)
+       {
+         for (i = 0; i < attr->num_values; i ++)
+           print_test_error("GOT: %s=%d", attr->name, attr->values[i].integer);
+       }
+       break;
+
+    case IPP_TAG_RANGE :
+        for (i = 0; i < attr->num_values; i ++)
+        {
+         char  op,                     /* Comparison operator */
+               *nextptr;               /* Next pointer */
+         int   intvalue;               /* Integer value */
+
+
+          valptr = value;
+         if (!strncmp(valptr, "no-value,", 9))
+           valptr += 9;
+
+         while (isspace(*valptr & 255) || isdigit(*valptr & 255) ||
+                *valptr == '-' || *valptr == ',' || *valptr == '<' ||
+                *valptr == '=' || *valptr == '>')
+         {
+           op = '=';
+           while (*valptr && !isdigit(*valptr & 255) && *valptr != '-')
+           {
+             if (*valptr == '<' || *valptr == '>' || *valptr == '=')
+               op = *valptr;
+             valptr ++;
+           }
+
+            if (!*valptr)
+             break;
+
+           intvalue = strtol(valptr, &nextptr, 0);
+           if (nextptr == valptr)
+             break;
+           valptr = nextptr;
+
+           switch (op)
+           {
+             case '=' :
+                 if (attr->values[i].range.lower == intvalue ||
+                     attr->values[i].range.upper == intvalue)
+                   return (1);
+                 break;
+             case '<' :
+                 if (attr->values[i].range.upper < intvalue)
+                   return (1);
+                 break;
+             case '>' :
+                 if (attr->values[i].range.upper > intvalue)
+                   return (1);
+                 break;
+           }
+         }
+        }
+
+       if (report)
+       {
+         for (i = 0; i < attr->num_values; i ++)
+           print_test_error("GOT: %s=%d-%d", attr->name,
+                            attr->values[i].range.lower,
+                            attr->values[i].range.upper);
+       }
        break;
 
     case IPP_TAG_BOOLEAN :
@@ -4139,6 +4963,13 @@ with_value(char            *value,       /* I - Value string */
           if (!strcmp(value, "true") == attr->values[i].boolean)
            return (1);
        }
+
+       if (report)
+       {
+         for (i = 0; i < attr->num_values; i ++)
+           print_test_error("GOT: %s=%s", attr->name,
+                            attr->values[i].boolean ? "true" : "false");
+       }
        break;
 
     case IPP_TAG_NOVALUE :
@@ -4180,7 +5011,13 @@ with_value(char            *value,       /* I - Value string */
          for (i = 0; i < attr->num_values; i ++)
          {
            if (regexec(&re, attr->values[i].string.text, 0, NULL, 0))
-             break;
+           {
+             if (report)
+               print_test_error("GOT: %s=\"%s\"", attr->name,
+                                attr->values[i].string.text);
+             else
+               break;
+           }
          }
 
          regfree(&re);
@@ -4199,6 +5036,13 @@ with_value(char            *value,       /* I - Value string */
            if (!strcmp(value, attr->values[i].string.text))
              return (1);
          }
+
+         if (report)
+         {
+           for (i = 0; i < attr->num_values; i ++)
+             print_test_error("GOT: %s=\"%s\"", attr->name,
+                              attr->values[i].string.text);
+         }
        }
        break;