Skip to content

http2: content-encoding is incorrectly listed in kSingleValueHeaders #67151

Open

Description

@panmenghan

Version

v26.13.0

Platform


Subsystem

No response

What steps will reproduce the bug?

Drafted by Gemini 4 Argon.

In lib/internal/http2/util.js, HTTP2_HEADER_CONTENT_ENCODING is listed in kSingleValueHeaders:

  • Incoming (mapToHeaders): When an HTTP/2 peer sends multiple content-encoding header lines (e.g., content-encoding: gzip twice on Google Drive viewer endpoints), only the first value is kept in headers['content-encoding'] and subsequent values are silently dropped (even with strictSingleValueFields: false from http2: add strictSingleValueFields option to allow relaxing header validation #59917). As a result, HTTP/2 clients only decompress the first layer and treat the still-compressed payload as decoded data, and reverse proxies forwarding headers strip the remaining encoding layers so downstream browsers fail to decode the response.
  • Outgoing (buildNgHeaderString): Passing multiple content-encoding values throws ERR_HTTP2_HEADER_SINGLE_VALUE by default, preventing servers and proxies from sending or forwarding multi-value content-encoding headers.

Evidence

  1. HTTP Semantics — RFC 9110 §8.4 (Content-Encoding) & §5.3:

    Content-Encoding = #content-coding

    If one or more encodings have been applied to a representation, the sender that applied the encodings MUST generate a Content-Encoding header field that lists the content codings in the order in which they were applied.

    Because Content-Encoding uses the #element list ABNF production, multiple content-encoding header field lines are valid per RFC 9110 §5.3 and must be combined in order with ', ' rather than discarded.

  2. HTTP/2 & HPACK — RFC 9113 §8.2.1 & RFC 7541:
    HTTP/2 only forbids connection-specific headers (connection, transfer-encoding, keep-alive, upgrade, proxy-connection); for all other fields, RFC 9113 §8.2.1 inherits RFC 9110 §5.3, allowing list-based headers to be transmitted as multiple separate HPACK header field entries (each referencing HPACK static table index 26 content-encoding). nghttp2 itself accepts multiple content-encoding entries and delivers all of them in rawHeaders—the truncation happens purely in Node's JS kSingleValueHeaders check.

  3. node:http consistency: lib/_http_incoming.js (_addHeaderLine) does not treat content-encoding as single-value and joins duplicate lines with ', '.

Removing HTTP2_HEADER_CONTENT_ENCODING from kSingleValueHeaders fixes both directions.

Compatibility

  • No type change: headers['content-encoding'] remains string | undefined (mapToHeaders joins non-single-value headers with ', '), and single content-encoding responses are unaffected.
  • Incoming duplicate headers: headers['content-encoding'] becomes 'gzip, br' instead of 'gzip' (matching what a single content-encoding: gzip, br header or node:http already produces). Callers doing exact single-token checks (headers['content-encoding'] === 'gzip') without splitting on ',' may stop matching when a peer sends multiple lines.
  • Outgoing validation: Passing multiple content-encoding values will succeed instead of throwing ERR_HTTP2_HEADER_SINGLE_VALUE (potentially semver-major per Node's error-removal policy).

How often does it reproduce? Is there a required condition?

What is the expected behavior? Why is that the expected behavior?

What do you see instead?

Additional information

No response

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions