From: Nathan Muggli Date: Thu, 23 Jul 2026 23:13:49 +0000 (-0600) Subject: Validate .language subfield of IPP attributes X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e42099afb8daa9612e04beee2534ec545cac6ae6;p=thirdparty%2Fcups.git Validate .language subfield of IPP attributes 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. --- diff --git a/cups/ipp.c b/cups/ipp.c index 183dc1ceeb..1ee8fef931 100644 --- a/cups/ipp.c +++ b/cups/ipp.c @@ -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. // diff --git a/cups/testipp.c b/cups/testipp.c index 41f3d91162..dec24e2f93 100644 --- a/cups/testipp.c +++ b/cups/testipp.c @@ -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