1 Commits
Author SHA1 Message Date
Jens GeyerandClaude Opus 5 519e1aeac6 THRIFT-6192: Match HTTP header names in full rather than by prefix
Client: cpp

THttpServer::parseHeader compared a name for as many characters as the peer
had sent before the colon:

  size_t sz = colon - header;
  if (THRIFT_strncasecmp(header, "Content-length", sz) == 0) ...

so every name that is a prefix of one the transport knows was accepted as that
name. Measured against the unmodified library: "C: 5", "co: 7" and
"Content-len: 9" each set the content length, and "T: chunked" switched on
chunked decoding. A header name is the whole token before the colon (RFC 9110
5.1), and a transport that reads it otherwise disagrees with every other party
on the connection about where a message ends -- one of these requests carries a
five byte body here and none at all to a parser that follows the grammar.

TWebSocketServer::parseHeader is the same idiom on the same base class, and the
handshake is satisfied by "U: websocket", "C: Upgrade" and "S: <key>"; note
that "S:" reaches the Sec-WebSocket-Key arm, which is tested before the version
one. Fixed here as well, together with the "value + 1" beside it that dropped
the space after the colon by stepping over it, and read one past the terminator
for a header that has no value at all.

Content-Length went through atoi(), which cannot report that it was handed a
negative number, something that is not a number, or a value too large to hold.
"Content-length: -1" therefore arrived in the uint32_t member as 4294967295.
It is now parsed with strtoll against RFC 9110 8.6's 1*DIGIT and refused
otherwise; the client had the same line and shares the new helper, though its
name comparison was already an equality and needed nothing.

lib/cpp/README.md gains a Breaking Changes entry under 0.25.0: refusing an
abbreviated name, and refusing a malformed Content-Length, can turn away
traffic that works today.

Nine tests, written first. Six fail against the unmodified library. The two
that cover a bad number assert the length that was parsed rather than that the
read threw, because an over-declared length outruns a five byte body and throws
on the unmodified library too -- "did it throw" would have passed either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 23:09:20 +02:00