Skip to content

fix: acceptParams truncates quoted values containing a semicolon - #7479

Open
mdalikadar wants to merge 2 commits into
expressjs:masterfrom
mdalikadar:fix/accept-params-quoted-semicolon
Open

mdalikadar wants to merge 2 commits into
expressjs:masterfrom
mdalikadar:fix/accept-params-quoted-semicolon

Conversation

@mdalikadar

Copy link
Copy Markdown

res.format() normalizes its keys through acceptParams() in lib/utils.js, which parses each ;-separated parameter by scanning for the next raw ; to know where the value ends. That breaks when the value is a quoted string that legitimately contains a ;, e.g.:

text/plain; foo="a;b"; bar=baz

Right now this parses foo as "a and silently drops the rest (b"), because the parser has no idea it's inside quotes when it hits that semicolon. I ran into this while poking around res.format() and figured it was worth fixing properly rather than patching around it.

The fix adds a small helper, indexOfUnquoted(), that walks the string and ignores delimiters found inside a quoted span (also handles an escaped \" so it doesnt end the quote early), and swap it in for the one indexOf(';') call that needed it. Everything else about acceptParams is untouched — same behavior for plain types, missing =, malformed params, etc.

Added two tests in test/utils.js covering the quoted-semicolon case and the escaped-quote case. Ran the full suite locally (1263 passing) and lint is clean.

@krzysdz

krzysdz commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

I haven't reviewed this and don't plan to, but here are some relevant definitions from RFC 9110 (only most important parts, follow the links for full text):

  • Accept hader field:
      Accept = #( media-range [ weight ] )
    
      media-range    = ( "*/*"
                         / ( type "/" "*" )
                         / ( type "/" subtype )
                       ) parameters
    

    Each media-range might be followed by optional applicable media type parameters (e.g., charset), followed by an optional "q" parameter for indicating a relative weight (Section 12.4.2).

    Previous specifications allowed additional extension parameters to appear after the weight parameter. The accept extension grammar (accept-params, accept-ext) has been removed because it had a complicated definition, was not being used in practice, and is more easily deployed through new header fields. Senders using weights SHOULD send "q" last (after all media-range parameters). Recipients SHOULD process any parameter named "q" as weight, regardless of parameter ordering.

  • parameter definition:
      parameters      = *( OWS ";" OWS [ parameter ] )
      parameter       = parameter-name "=" parameter-value
      parameter-name  = token
      parameter-value = ( token / quoted-string )
    

    A parameter value that matches the token production can be transmitted either as a token or within a quoted-string. The quoted and unquoted values are equivalent.

  • token definition:

    Tokens are short textual identifiers that do not include whitespace or delimiters.

      token          = 1*tchar
    
      tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
                     / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
                     / DIGIT / ALPHA
                     ; any VCHAR, except delimiters
    
  • Quoted Strings:

    A string of text is parsed as a single value if it is quoted using double-quote marks.

     quoted-string  = DQUOTE *( qdtext / quoted-pair ) DQUOTE
     qdtext         = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
    

    The backslash octet ("\") can be used as a single-octet quoting mechanism within quoted-string and comment constructs. Recipients that process the value of a quoted-string MUST handle a quoted-pair as if it were replaced by the octet following the backslash.

      quoted-pair    = "\" ( HTAB / SP / VCHAR / obs-text )
    

    A sender SHOULD NOT generate a quoted-pair in a quoted-string except where necessary to quote DQUOTE and backslash octets occurring within that string.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants