]> git.ipfire.org Git - thirdparty/cups.git/commitdiff
Validate .language subfield of IPP attributes
authorNathan Muggli <nmuggli@google.com>
Thu, 23 Jul 2026 23:13:49 +0000 (17:13 -0600)
committerNathan Muggli <nmuggli@google.com>
Thu, 23 Jul 2026 23:34:53 +0000 (17:34 -0600)
ippValidateAttribute will validate strings, but it doesn't validate the
string.language subfield of IPP_TAG_TEXTLANG or IPP_TAG_NAMELANG.  Add a
helper method to validate these values using the same validation that is
currently used for IPP_TAG_LANGUAGE.

Added some tests as well.

cups/ipp.c
cups/testipp.c

index 183dc1ceebfcff7a8939bdd2069ef5405873717a..1ee8fef9318435164b2384b66439ffef47b59491 100644 (file)
@@ -30,6 +30,7 @@ static ssize_t                ipp_read_http(http_t *http, ipp_uchar_t *buffer, size_t length);
 static ipp_state_t     ipp_read_io(void *src, ipp_io_cb_t cb, bool blocking, ipp_t *parent, ipp_t *ipp, int depth);
 static void            ipp_set_error(ipp_status_t status, const char *format, ...);
 static _ipp_value_t    *ipp_set_value(ipp_t *ipp, ipp_attribute_t **attr, int element);
+static int             ipp_validate_language(const char *lang);
 static ssize_t         ipp_write_file(int *fd, ipp_uchar_t *buffer, size_t length);
 
 
@@ -3844,6 +3845,12 @@ ippValidateAttribute(
     case IPP_TAG_TEXTLANG :
         for (i = 0; i < attr->num_values; i ++)
        {
+         if (attr->value_tag == IPP_TAG_TEXTLANG && attr->values[i].string.language && !ipp_validate_language(attr->values[i].string.language))
+         {
+           ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST, _("\"%s\": Bad naturalLanguage value \"%s\" in textWithLanguage - bad characters (RFC 8011 section 5.1.9)."), attr->name, attr->values[i].string.language);
+           return (0);
+         }
+
          for (ptr = attr->values[i].string.text; *ptr; ptr ++)
          {
            if ((*ptr & 0xe0) == 0xc0)
@@ -3903,6 +3910,12 @@ ippValidateAttribute(
     case IPP_TAG_NAMELANG :
         for (i = 0; i < attr->num_values; i ++)
        {
+         if (attr->value_tag == IPP_TAG_NAMELANG && attr->values[i].string.language && !ipp_validate_language(attr->values[i].string.language))
+         {
+           ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST, _("\"%s\": Bad naturalLanguage value \"%s\" in nameWithLanguage - bad characters (RFC 8011 section 5.1.9)."), attr->name, attr->values[i].string.language);
+           return (0);
+         }
+
          for (ptr = attr->values[i].string.text; *ptr; ptr ++)
          {
            if ((*ptr & 0xe0) == 0xc0)
@@ -4050,50 +4063,14 @@ ippValidateAttribute(
         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 ((r = 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(r, &re, temp, sizeof(temp));
-         ipp_set_error(IPP_STATUS_ERROR_INTERNAL, _("Unable to compile naturalLanguage regular expression: %s."), temp);
-         return (0);
-        }
-
         for (i = 0; i < attr->num_values; i ++)
        {
-         if (regexec(&re, attr->values[i].string.text, 0, NULL, 0))
+         if (!ipp_validate_language(attr->values[i].string.text))
          {
            ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST, _("\"%s\": Bad naturalLanguage value \"%s\" - bad characters (RFC 8011 section 5.1.9)."), attr->name, attr->values[i].string.text);
-           regfree(&re);
-           return (0);
-         }
-
-         if (strlen(attr->values[i].string.text) > (IPP_MAX_LANGUAGE - 1))
-         {
-           ipp_set_error(IPP_STATUS_ERROR_BAD_REQUEST, _("\"%s\": Bad naturalLanguage value \"%s\" - bad length %d (RFC 8011 section 5.1.9)."), attr->name, attr->values[i].string.text, (int)strlen(attr->values[i].string.text));
-           regfree(&re);
            return (0);
          }
        }
-
-       regfree(&re);
         break;
 
     case IPP_TAG_MIMETYPE :
@@ -6123,6 +6100,49 @@ ipp_set_value(ipp_t           *ipp,      // IO - IPP message
 }
 
 
+//
+// 'ipp_validate_language()' - Validate a natural language code string.
+//
+
+static int                             // O - 1 if valid, 0 if invalid
+ipp_validate_language(
+    const char *lang)                  // I - Language code
+{
+  int          i, status;              // Result
+  regex_t      re;                     // Regular expression
+
+
+  // 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 (!lang || strlen(lang) > (IPP_MAX_LANGUAGE - 1))
+    return (0);
+
+  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)
+    return (0);
+
+  status = regexec(&re, lang, 0, NULL, 0) == 0;
+  regfree(&re);
+
+  return (status);
+}
+
+
 //
 // 'ipp_write_file()' - Write IPP data to a file.
 //
index 41f3d911626c22f7a12907be728a0b32a37931a3..dec24e2f939e0b96ceaecba1a8ae4070e9994f3a 100644 (file)
@@ -735,6 +735,27 @@ main(int  argc,                            // I - Number of command-line arguments
     else
       testEnd(true);
 
+    testBegin("ippValidateAttribute(IPP_TAG_TEXTLANG)");
+    attr = ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXTLANG,
+                        "job-name", "en-US", "My Job");
+    if (!attr || !ippValidateAttribute(attr))
+    {
+      testEndMessage(false, "Unable to validate .language subfield");
+      status = 1;
+    }
+    else
+      testEnd(true);
+
+    testBegin("ippValidateAttribute(IPP_TAG_TEXTLANG invalid .language)");
+    attr = ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_TEXTLANG, "job-name", "en\"\n*cupsFilter:", "My Job");
+    if (!attr || ippValidateAttribute(attr))
+    {
+      testEndMessage(false, "accepted bad .language subfield");
+      status = 1;
+    }
+    else
+      testEnd(true);
+
     ippDelete(request);
 
 #ifdef DEBUG