]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - test/ipptool.c
Fix test suite with shared libraries on macOS.
[thirdparty/cups.git] / test / ipptool.c
index a2d0ee609f087ada02313ba1613ecae057bfa9ac..9b5bc6395c2e8c7f0522cfc56cdf7c73cfa3d676 100644 (file)
@@ -12,7 +12,6 @@
  * Include necessary headers...
  */
 
-#define _IPP_PRIVATE_STRUCTURES 0      /* Disable private IPP stuff */
 #include <cups/cups-private.h>
 #include <cups/file-private.h>
 #include <regex.h>
@@ -166,8 +165,9 @@ static int  Cancel = 0;             /* Cancel test? */
  * Local functions...
  */
 
-static void    add_stringf(cups_array_t *a, const char *s, ...) __attribute__ ((__format__ (__printf__, 2, 3)));
+static void    add_stringf(cups_array_t *a, const char *s, ...) _CUPS_FORMAT(2, 3);
 static int      compare_uris(const char *a, const char *b);
+static void    copy_hex_string(char *buffer, unsigned char *data, int datalen, size_t bufsize);
 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);
@@ -179,7 +179,7 @@ static char *iso_date(const ipp_uchar_t *date);
 static void    pause_message(const char *message);
 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_fatal_error(_cups_testdata_t *data, const char *s, ...) _CUPS_FORMAT(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);
@@ -191,8 +191,7 @@ 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(_cups_testdata_t *data, cups_array_t *errors, ipp_attribute_t *attr);
+static void    usage(void) _CUPS_NORETURN;
 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);
@@ -240,7 +239,7 @@ main(int  argc,                             /* I - Number of command-line args */
 
   init_data(&data);
 
-  _ippVarsInit(&vars);
+  _ippVarsInit(&vars, NULL, (_ipp_ferror_cb_t)error_cb, (_ipp_ftoken_cb_t)token_cb);
 
  /*
   * We need at least:
@@ -818,6 +817,69 @@ compare_uris(const char *a,             /* I - First URI */
 }
 
 
+/*
+ * 'copy_hex_string()' - Copy an octetString to a C string and encode as hex if
+ *                       needed.
+ */
+
+static void
+copy_hex_string(char          *buffer, /* I - String buffer */
+               unsigned char *data,    /* I - octetString data */
+               int           datalen,  /* I - octetString length */
+               size_t        bufsize)  /* I - Size of string buffer */
+{
+  char         *bufptr,                /* Pointer into string buffer */
+               *bufend = buffer + bufsize - 2;
+                                       /* End of string buffer */
+  unsigned char        *dataptr,               /* Pointer into octetString data */
+               *dataend = data + datalen;
+                                       /* End of octetString data */
+  static const char *hexdigits = "0123456789ABCDEF";
+                                       /* Hex digits */
+
+
+ /*
+  * First see if there are any non-ASCII bytes in the octetString...
+  */
+
+  for (dataptr = data; dataptr < dataend; dataptr ++)
+    if (*dataptr < 0x20 || *dataptr >= 0x7f)
+      break;
+
+  if (*dataptr)
+  {
+   /*
+    * Yes, encode as hex...
+    */
+
+    *buffer = '<';
+
+    for (bufptr = buffer + 1, dataptr = data; bufptr < bufend && dataptr < dataend; dataptr ++)
+    {
+      *bufptr++ = hexdigits[*dataptr >> 4];
+      *bufptr++ = hexdigits[*dataptr & 15];
+    }
+
+    if (bufptr < bufend)
+      *bufptr++ = '>';
+
+    *bufptr = '\0';
+  }
+  else
+  {
+   /*
+    * No, copy as a string...
+    */
+
+    if ((size_t)datalen > bufsize)
+      datalen = (int)bufsize - 1;
+
+    memcpy(buffer, data, datalen);
+    buffer[datalen] = '\0';
+  }
+}
+
+
 /*
  * 'do_test()' - Do a single test from the test file.
  */
@@ -1266,11 +1328,12 @@ do_test(_ipp_file_t      *f,            /* I - IPP data file */
            group = ippGetGroupTag(attrptr);
        }
 
-       validate_attr(data, data->errors, attrptr);
+       if (!ippValidateAttribute(attrptr))
+         cupsArrayAdd(data->errors, (void *)cupsLastErrorString());
 
        if (ippGetName(attrptr))
        {
-         if (cupsArrayFind(a, (void *)ippGetName(attrptr)))
+         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));
 
@@ -1343,6 +1406,8 @@ do_test(_ipp_file_t      *f,              /* I - IPP data file */
 
       for (i = data->num_expects, expect = data->expects; i > 0; i --, expect ++)
       {
+       ipp_attribute_t *group_found;   /* Found parent attribute for group tests */
+
        if (expect->if_defined && !_ippVarsGet(vars, expect->if_defined))
          continue;
 
@@ -1350,15 +1415,33 @@ do_test(_ipp_file_t      *f,            /* I - IPP data file */
            _ippVarsGet(vars, expect->if_not_defined))
          continue;
 
-       found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO);
+       if ((found = ippFindAttribute(response, expect->name, IPP_TAG_ZERO)) != NULL && expect->in_group && expect->in_group != ippGetGroupTag(found))
+       {
+         while ((found = ippFindNextAttribute(response, expect->name, IPP_TAG_ZERO)) != NULL)
+           if (expect->in_group == ippGetGroupTag(found))
+             break;
+       }
 
        do
        {
+         group_found = found;
+
+          if (expect->in_group && strchr(expect->name, '/'))
+          {
+            char       group_name[256],/* Parent attribute name */
+                       *group_ptr;     /* Pointer into parent attribute name */
+
+           strlcpy(group_name, expect->name, sizeof(group_name));
+           if ((group_ptr = strchr(group_name, '/')) != NULL)
+             *group_ptr = '\0';
+
+           group_found = ippFindAttribute(response, group_name, IPP_TAG_ZERO);
+         }
+
          if ((found && expect->not_expect) ||
              (!found && !(expect->not_expect || expect->optional)) ||
              (found && !expect_matches(expect, ippGetValueTag(found))) ||
-             (found && expect->in_group &&
-              ippGetGroupTag(found) != expect->in_group))
+             (group_found && expect->in_group && ippGetGroupTag(group_found) != expect->in_group))
          {
            if (expect->define_no_match)
              _ippVarsSet(vars, expect->define_no_match, "1");
@@ -1375,10 +1458,10 @@ do_test(_ipp_file_t      *f,            /* I - IPP data file */
                              expect->name, expect->of_type,
                              ippTagString(ippGetValueTag(found)));
 
-               if (expect->in_group && ippGetGroupTag(found) != expect->in_group)
+               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(found)));
+                             ippTagString(ippGetGroupTag(group_found)));
              }
            }
 
@@ -1819,7 +1902,7 @@ do_tests(const char       *testfile,      /* I - Test file to use */
   * Run tests...
   */
 
-  _ippFileParse(vars, testfile, (_ipp_ftoken_cb_t)token_cb, (_ipp_ferror_cb_t)error_cb, (void *)data);
+  _ippFileParse(vars, testfile, (void *)data);
 
  /*
   * Close connection and return...
@@ -2579,7 +2662,7 @@ print_ippserver_attr(
        break;
 
     default :
-       cupsFilePuts(data->outfile, " \"\"");
+        /* Out-of-band value */
        break;
   }
 
@@ -4245,608 +4328,6 @@ usage(void)
 }
 
 
-/*
- * 'validate_attr()' - Determine whether an attribute is valid.
- */
-
-static int                             /* O - 1 if valid, 0 otherwise */
-validate_attr(_cups_testdata_t *data,  /* I - Test data */
-              cups_array_t     *errors,        /* I - Errors array */
-              ipp_attribute_t  *attr)  /* I - Attribute to validate */
-{
-  int          i,                      /* Looping var */
-               count;                  /* Number of values */
-  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   *name,                  /* Attribute name */
-               *ptr;                   /* Pointer into string */
-  ipp_attribute_t *colattr;            /* Collection attribute */
-  regex_t      re;                     /* Regular expression */
-
-
- /*
-  * Skip separators.
-  */
-
-  if ((name = ippGetName(attr)) == NULL)
-    return (1);
-
- /*
-  * Validate the attribute name.
-  */
-
-  for (ptr = name; *ptr; ptr ++)
-    if (!isalnum(*ptr & 255) && *ptr != '-' && *ptr != '.' && *ptr != '_')
-      break;
-
-  if (*ptr || ptr == name)
-  {
-    valid = 0;
-
-    add_stringf(data->errors,
-               "\"%s\": Bad attribute name - invalid character "
-               "(RFC 2911 section 4.1.3).", name);
-  }
-
-  if ((ptr - ippGetName(attr)) > 255)
-  {
-    valid = 0;
-
-    add_stringf(data->errors,
-               "\"%s\": Bad attribute name - bad length "
-               "(RFC 2911 section 4.1.3).", name);
-  }
-
-  count = ippGetCount(attr);
-
-  switch (ippGetValueTag(attr))
-  {
-    case IPP_TAG_INTEGER :
-        break;
-
-    case IPP_TAG_BOOLEAN :
-        for (i = 0; i < count; i ++)
-       {
-         int b = ippGetBoolean(attr, i);
-                                       /* Boolean value */
-
-         if (b != 0 && b != 1)
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad boolen value %d (RFC 2911 section 4.1.11).", name, b);
-         }
-       }
-        break;
-
-    case IPP_TAG_ENUM :
-        for (i = 0; i < count; i ++)
-       {
-         if (ippGetInteger(attr, i) < 1)
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad enum value %d - out of range (RFC 2911 section 4.1.4).", name, ippGetInteger(attr, i));
-         }
-       }
-        break;
-
-    case IPP_TAG_STRING :
-        for (i = 0; i < count; i ++)
-       {
-         int   datalen;                /* Length of string */
-
-         ippGetOctetString(attr, i, &datalen);
-         if (datalen > IPP_MAX_OCTETSTRING)
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad octetString value - bad length %d (RFC 2911 section 4.1.10).", name, datalen);
-         }
-       }
-        break;
-
-    case IPP_TAG_DATE :
-        for (i = 0; i < count; i ++)
-       {
-          const ipp_uchar_t    *date = ippGetDate(attr, i);
-                                       /* Current date value */
-
-          if (date[2] < 1 || date[2] > 12)
-         {
-           valid = 0;
-
-           add_stringf(data->errors,
-                       "\"%s\": Bad dateTime month %u "
-                       "(RFC 2911 section 4.1.14).", name, date[2]);
-         }
-
-          if (date[3] < 1 || date[3] > 31)
-         {
-           valid = 0;
-
-           add_stringf(data->errors,
-                       "\"%s\": Bad dateTime day %u "
-                       "(RFC 2911 section 4.1.14).", name, date[3]);
-         }
-
-          if (date[4] > 23)
-         {
-           valid = 0;
-
-           add_stringf(data->errors,
-                       "\"%s\": Bad dateTime hours %u "
-                       "(RFC 2911 section 4.1.14).", name, date[4]);
-         }
-
-          if (date[5] > 59)
-         {
-           valid = 0;
-
-           add_stringf(data->errors,
-                       "\"%s\": Bad dateTime minutes %u "
-                       "(RFC 2911 section 4.1.14).", name, date[5]);
-         }
-
-          if (date[6] > 60)
-         {
-           valid = 0;
-
-           add_stringf(data->errors,
-                       "\"%s\": Bad dateTime seconds %u "
-                       "(RFC 2911 section 4.1.14).", name, date[6]);
-         }
-
-          if (date[7] > 9)
-         {
-           valid = 0;
-
-           add_stringf(data->errors,
-                       "\"%s\": Bad dateTime deciseconds %u "
-                       "(RFC 2911 section 4.1.14).", name, date[7]);
-         }
-
-          if (date[8] != '-' && date[8] != '+')
-         {
-           valid = 0;
-
-           add_stringf(data->errors,
-                       "\"%s\": Bad dateTime UTC sign '%c' "
-                       "(RFC 2911 section 4.1.14).", name, date[8]);
-         }
-
-          if (date[9] > 11)
-         {
-           valid = 0;
-
-           add_stringf(data->errors,
-                       "\"%s\": Bad dateTime UTC hours %u "
-                       "(RFC 2911 section 4.1.14).", name, date[9]);
-         }
-
-          if (date[10] > 59)
-         {
-           valid = 0;
-
-           add_stringf(data->errors,
-                       "\"%s\": Bad dateTime UTC minutes %u "
-                       "(RFC 2911 section 4.1.14).", name, date[10]);
-         }
-       }
-        break;
-
-    case IPP_TAG_RESOLUTION :
-        for (i = 0; i < count; i ++)
-       {
-         int           xres, yres;     /* Resolution values */
-         ipp_res_t     units;          /* Resolution units */
-
-         xres = ippGetResolution(attr, i, &yres, &units);
-
-         if (xres <= 0)
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad resolution value %dx%d%s - cross feed resolution must be positive (RFC 2911 section 4.1.15).", name, xres, yres, units == IPP_RES_PER_INCH ? "dpi" : units == IPP_RES_PER_CM ? "dpcm" : "unknown");
-         }
-
-         if (yres <= 0)
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad resolution value %dx%d%s - feed resolution must be positive (RFC 2911 section 4.1.15).", name, xres, yres, units == IPP_RES_PER_INCH ? "dpi" : units == IPP_RES_PER_CM ? "dpcm" : "unknown");
-         }
-
-         if (units != IPP_RES_PER_INCH && units != IPP_RES_PER_CM)
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad resolution value %dx%d%s - bad units value (RFC 2911 section 4.1.15).", name, xres, yres, units == IPP_RES_PER_INCH ? "dpi" : units == IPP_RES_PER_CM ? "dpcm" : "unknown");
-         }
-       }
-        break;
-
-    case IPP_TAG_RANGE :
-        for (i = 0; i < count; i ++)
-       {
-         int lower, upper;             /* Range values */
-
-         lower = ippGetRange(attr, i, &upper);
-
-         if (lower > upper)
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad rangeOfInteger value %d-%d - lower greater than upper (RFC 2911 section 4.1.13).", name, lower, upper);
-         }
-       }
-        break;
-
-    case IPP_TAG_BEGIN_COLLECTION :
-        for (i = 0; i < count; i ++)
-       {
-         ipp_t *col = ippGetCollection(attr, i);
-                                       /* Collection value */
-
-         for (colattr = ippFirstAttribute(col); colattr; colattr = ippNextAttribute(col))
-         {
-           if (!validate_attr(data, NULL, colattr))
-           {
-             valid = 0;
-             break;
-           }
-         }
-
-         if (colattr && errors)
-         {
-            add_stringf(data->errors, "\"%s\": Bad collection value.", name);
-
-           while (colattr)
-           {
-             validate_attr(data, errors, colattr);
-             colattr = ippNextAttribute(col);
-           }
-         }
-       }
-        break;
-
-    case IPP_TAG_TEXT :
-    case IPP_TAG_TEXTLANG :
-        for (i = 0; i < count; i ++)
-       {
-         const char *s = ippGetString(attr, i, NULL);
-                               /* Text value */
-
-         for (ptr = s; *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;
-         }
-
-         if (*ptr)
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad text value \"%s\" - bad UTF-8 sequence (RFC 2911 section 4.1.1).", name, s);
-         }
-
-         if ((ptr - s) > (IPP_MAX_TEXT - 1))
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad text value \"%s\" - bad length %d (RFC 2911 section 4.1.1).", name, s, (int)strlen(s));
-         }
-       }
-        break;
-
-    case IPP_TAG_NAME :
-    case IPP_TAG_NAMELANG :
-        for (i = 0; i < count; i ++)
-       {
-         const char *s = ippGetString(attr, i, NULL);
-                                       /* Name value */
-
-         for (ptr = s; *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;
-         }
-
-         if (*ptr)
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad name value \"%s\" - bad UTF-8 sequence (RFC 2911 section 4.1.2).", name, s);
-         }
-
-         if ((ptr - s) > (IPP_MAX_NAME - 1))
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad name value \"%s\" - bad length %d (RFC 2911 section 4.1.2).", name, s, (int)strlen(s));
-         }
-       }
-        break;
-
-    case IPP_TAG_KEYWORD :
-        for (i = 0; i < count; i ++)
-       {
-         const char *keyword = ippGetString(attr, i, NULL);
-                                       /* Keyword value */
-
-         for (ptr = keyword; *ptr; ptr ++)
-           if (!isalnum(*ptr & 255) && *ptr != '-' && *ptr != '.' &&
-               *ptr != '_')
-             break;
-
-         if (*ptr || ptr == keyword)
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad keyword value \"%s\" - invalid character (RFC 2911 section 4.1.3).", name, keyword);
-         }
-
-         if ((ptr - keyword) > (IPP_MAX_KEYWORD - 1))
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad keyword value \"%s\" - bad length %d (RFC 2911 section 4.1.3).", name, keyword, (int)strlen(keyword));
-         }
-       }
-        break;
-
-    case IPP_TAG_URI :
-        for (i = 0; i < count; i ++)
-       {
-         const char *uri = ippGetString(attr, i, NULL);
-                                       /* URI value */
-
-         uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, uri, scheme, sizeof(scheme), userpass, sizeof(userpass), hostname, sizeof(hostname), &port, resource, sizeof(resource));
-
-         if (uri_status < HTTP_URI_OK)
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad URI value \"%s\" - %s (RFC 2911 section 4.1.5).", name, uri, httpURIStatusString(uri_status));
-         }
-
-         if (strlen(uri) > (IPP_MAX_URI - 1))
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad URI value \"%s\" - bad length %d (RFC 2911 section 4.1.5).", name, uri, (int)strlen(uri));
-         }
-       }
-        break;
-
-    case IPP_TAG_URISCHEME :
-        for (i = 0; i < count; i ++)
-       {
-         const char *urischeme = ippGetString(attr, i, NULL);
-                                       /* URI scheme value */
-
-         ptr = urischeme;
-         if (islower(*ptr & 255))
-         {
-           for (ptr ++; *ptr; ptr ++)
-             if (!islower(*ptr & 255) && !isdigit(*ptr & 255) &&
-                 *ptr != '+' && *ptr != '-' && *ptr != '.')
-                break;
-         }
-
-         if (*ptr || ptr == urischeme)
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad uriScheme value \"%s\" - bad characters (RFC 2911 section 4.1.6).", name, urischeme);
-         }
-
-         if ((ptr - urischeme) > (IPP_MAX_URISCHEME - 1))
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad uriScheme value \"%s\" - bad length %d (RFC 2911 section 4.1.6).", name, urischeme, (int)strlen(urischeme));
-         }
-       }
-        break;
-
-    case IPP_TAG_CHARSET :
-        for (i = 0; i < count; i ++)
-       {
-         const char *charset = ippGetString(attr, i, NULL);
-                                       /* Charset value */
-
-         for (ptr = charset; *ptr; ptr ++)
-           if (!isprint(*ptr & 255) || isupper(*ptr & 255) ||
-               isspace(*ptr & 255))
-             break;
-
-         if (*ptr || ptr == charset)
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad charset value \"%s\" - bad characters (RFC 2911 section 4.1.7).", name, charset);
-         }
-
-         if ((ptr - charset) > (IPP_MAX_CHARSET - 1))
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad charset value \"%s\" - bad length %d (RFC 2911 section 4.1.7).", name, charset, (int)strlen(charset));
-         }
-       }
-        break;
-
-    case IPP_TAG_LANGUAGE :
-       /*
-        * 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...
-       */
-
-        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 */
-
-          regerror(i, &re, temp, sizeof(temp));
-         print_fatal_error(data, "Unable to compile naturalLanguage regular "
-                           "expression: %s.", temp);
-         break;
-        }
-
-        for (i = 0; i < count; i ++)
-       {
-         const char *lang = ippGetString(attr, i, NULL);
-                                       /* Language string */
-
-         if (regexec(&re, lang, 0, NULL, 0))
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad naturalLanguage value \"%s\" - bad characters (RFC 2911 section 4.1.8).", name, lang);
-         }
-
-         if (strlen(lang) > (IPP_MAX_LANGUAGE - 1))
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad naturalLanguage value \"%s\" - bad length %d (RFC 2911 section 4.1.8).", name, lang, (int)strlen(lang));
-         }
-       }
-
-       regfree(&re);
-        break;
-
-    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 ((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 */
-
-          regerror(i, &re, temp, sizeof(temp));
-         print_fatal_error(data, "Unable to compile mimeMediaType regular "
-                           "expression: %s.", temp);
-         break;
-        }
-
-        for (i = 0; i < count; i ++)
-       {
-         const char *mimetype = ippGetString(attr, i, NULL);
-                                       /* Mime media type string */
-
-         if (regexec(&re, mimetype, 0, NULL, 0))
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad mimeMediaType value \"%s\" - bad characters (RFC 2911 section 4.1.9).", name, mimetype);
-         }
-
-         if (strlen(mimetype) > (IPP_MAX_MIMETYPE - 1))
-         {
-           valid = 0;
-
-           add_stringf(data->errors, "\"%s\": Bad mimeMediaType value \"%s\" - bad length %d (RFC 2911 section 4.1.9).", name, mimetype, (int)strlen(mimetype));
-         }
-       }
-
-       regfree(&re);
-        break;
-
-    default :
-        break;
-  }
-
-  return (valid);
-}
-
-
 /*
  * 'with_flags_string()' - Return the "WITH-xxx" predicate that corresponds to
                            the flags.
@@ -4919,7 +4400,7 @@ with_value(_cups_testdata_t *data,        /* I - Test data */
   {
     case IPP_TAG_INTEGER :
     case IPP_TAG_ENUM :
-        for (i = 0; i < ippGetCount(attr); i ++)
+        for (i = 0; i < count; i ++)
         {
          char  op,                     /* Comparison operator */
                *nextptr;               /* Next pointer */
@@ -4979,13 +4460,13 @@ with_value(_cups_testdata_t *data,      /* I - Test data */
 
         if (!match && errors)
        {
-         for (i = 0; i < ippGetCount(attr); i ++)
+         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 < ippGetCount(attr); i ++)
+        for (i = 0; i < count; i ++)
         {
          char  op,                     /* Comparison operator */
                *nextptr;               /* Next pointer */
@@ -5046,7 +4527,7 @@ with_value(_cups_testdata_t *data,        /* I - Test data */
 
         if (!match && errors)
        {
-         for (i = 0; i < ippGetCount(attr); i ++)
+         for (i = 0; i < count; i ++)
          {
            int lower, upper;           /* Range values */
 
@@ -5057,7 +4538,7 @@ with_value(_cups_testdata_t *data,        /* I - Test data */
        break;
 
     case IPP_TAG_BOOLEAN :
-       for (i = 0; i < ippGetCount(attr); i ++)
+       for (i = 0; i < count; i ++)
        {
           if ((!strcmp(value, "true")) == ippGetBoolean(attr, i))
           {
@@ -5079,13 +4560,13 @@ with_value(_cups_testdata_t *data,      /* I - Test data */
 
        if (!match && errors)
        {
-         for (i = 0; i < ippGetCount(attr); i ++)
+         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 < ippGetCount(attr); i ++)
+       for (i = 0; i < count; i ++)
        {
          int           xres, yres;     /* Resolution values */
          ipp_res_t     units;          /* Resolution units */
@@ -5116,7 +4597,7 @@ with_value(_cups_testdata_t *data,        /* I - Test data */
 
        if (!match && errors)
        {
-         for (i = 0; i < ippGetCount(attr); i ++)
+         for (i = 0; i < count; i ++)
          {
            int         xres, yres;     /* Resolution values */
            ipp_res_t   units;          /* Resolution units */
@@ -5167,7 +4648,7 @@ with_value(_cups_testdata_t *data,        /* I - Test data */
          * See if ALL of the values match the given regular expression.
          */
 
-         for (i = 0; i < ippGetCount(attr); i ++)
+         for (i = 0; i < count; i ++)
          {
            if (!regexec(&re, get_string(attr, i, flags, temp, sizeof(temp)),
                         0, NULL, 0))
@@ -5196,7 +4677,7 @@ with_value(_cups_testdata_t *data,        /* I - Test data */
          * Value is a literal URI string, see if the value(s) match...
          */
 
-         for (i = 0; i < ippGetCount(attr); i ++)
+         for (i = 0; i < count; i ++)
          {
            if (!compare_uris(value, get_string(attr, i, flags, temp, sizeof(temp))))
            {
@@ -5222,7 +4703,7 @@ with_value(_cups_testdata_t *data,        /* I - Test data */
          * Value is a literal string, see if the value(s) match...
          */
 
-         for (i = 0; i < ippGetCount(attr); i ++)
+         for (i = 0; i < count; i ++)
          {
            int result;
 
@@ -5284,11 +4765,104 @@ with_value(_cups_testdata_t *data,     /* I - Test data */
 
         if (!match && errors)
         {
-         for (i = 0; i < ippGetCount(attr); i ++)
+         for (i = 0; i < count; i ++)
            add_stringf(data->errors, "GOT: %s=\"%s\"", name, ippGetString(attr, i, NULL));
         }
        break;
 
+    case IPP_TAG_STRING :
+        {
+          unsigned char        withdata[1023], /* WITH-VALUE data */
+                       *adata;         /* Pointer to octetString data */
+         int           withlen,        /* Length of WITH-VALUE data */
+                       adatalen;       /* Length of octetString */
+
+          if (*value == '<')
+          {
+           /*
+            * Grab hex-encoded value...
+            */
+
+            if ((withlen = (int)strlen(value)) & 1 || withlen > (int)(2 * (sizeof(withdata) + 1)))
+            {
+             print_fatal_error(data, "Bad WITH-VALUE hex value.");
+              return (0);
+           }
+
+           withlen = withlen / 2 - 1;
+
+            for (valptr = value + 1, adata = withdata; *valptr; valptr += 2)
+            {
+              int ch;                  /* Current character/byte */
+
+             if (isdigit(valptr[0]))
+               ch = (valptr[0] - '0') << 4;
+             else if (isalpha(valptr[0]))
+               ch = (tolower(valptr[0]) - 'a' + 10) << 4;
+             else
+               break;
+
+             if (isdigit(valptr[1]))
+               ch |= valptr[1] - '0';
+             else if (isalpha(valptr[1]))
+               ch |= tolower(valptr[1]) - 'a' + 10;
+             else
+               break;
+
+             *adata++ = (unsigned char)ch;
+           }
+
+           if (*valptr)
+           {
+             print_fatal_error(data, "Bad WITH-VALUE hex value.");
+              return (0);
+           }
+          }
+          else
+          {
+           /*
+            * Copy literal string value...
+            */
+
+            withlen = (int)strlen(value);
+
+            memcpy(withdata, value, withlen);
+         }
+
+         for (i = 0; i < count; i ++)
+         {
+           adata = ippGetOctetString(attr, i, &adatalen);
+
+           if (withlen == adatalen && !memcmp(withdata, adata, (size_t)withlen))
+           {
+             if (!matchbuf[0])
+               copy_hex_string(matchbuf, adata, adatalen, matchlen);
+
+             if (!(flags & _CUPS_WITH_ALL))
+             {
+               match = 1;
+               break;
+             }
+           }
+           else if (flags & _CUPS_WITH_ALL)
+           {
+             match = 0;
+             break;
+           }
+         }
+
+         if (!match && errors)
+         {
+           for (i = 0; i < count; i ++)
+           {
+             adata = ippGetOctetString(attr, i, &adatalen);
+             copy_hex_string(temp, adata, adatalen, sizeof(temp));
+             add_stringf(data->errors, "GOT: %s=\"%s\"", name, temp);
+           }
+         }
+        }
+        break;
+
     default :
         break;
   }