This is an automated email from the ASF dual-hosted git repository.
pjfanning pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko-http.git
The following commit(s) were added to refs/heads/main by this push:
new 44b01c355 fix: reject a message whose Transfer-Encoding value cannot
be parsed (#1267)
44b01c355 is described below
commit 44b01c355969a4e0a3083a1ca46522d234a92b74
Author: PJ Fanning <[email protected]>
AuthorDate: Sun Sep 6 12:01:17 2026 +0100
fix: reject a message whose Transfer-Encoding value cannot be parsed (#1267)
Motivation:
When a modelled header value fails to parse, `ModeledHeaderValueParser`
calls `onIllegalHeader` - which by default only logs - and degrades the
header to a `RawHeader`. For `Transfer-Encoding` that is unsafe: the
degraded header never reaches the `case h: Transfer-Encoding` arm in
`parseHeaderLines`, so `isChunked` stays false and, with a
Content-Length also present, the message is framed by Content-Length.
An upstream that does parse the value - stripping quotes, tolerating
trailing junk - frames the same message as chunked. The two disagree
about where the message ends, which is a request smuggling primitive.
Pekko HTTP already rejects the clear cases: `chunked` together with a
Content-Length, an unsupported coding, and multiple entries. Only the
unparseable value was silently tolerated.
Modification:
Fail the message when a `Transfer-Encoding` header arrives as a
`RawHeader`. That can only happen when the modelled parse failed:
`transfer-encoding` is in `alwaysParsedHeaders`, so it is modelled even
when `modeled-header-parsing` is off, and a well-formed value always
reaches the modelled arm.
Result:
A message whose Transfer-Encoding cannot be understood is rejected
rather than framed by a different rule than the sender used.
Tests:
- sbt "http-core/testOnly org.apache.pekko.http.impl.engine.parsing.*" -
pass (246 tests); a new test sends `Transfer-Encoding: "chunked"` alongside a
Content-Length and expects a 400. Verified it fails without the change, where
the message is accepted and framed by Content-Length.
- sbt http-core/mimaReportBinaryIssues - pass
References:
None - rejects a message whose Transfer-Encoding value cannot be parsed
---
.../pekko/http/impl/engine/parsing/HttpMessageParser.scala | 7 +++++++
.../pekko/http/impl/engine/parsing/RequestParserSpec.scala | 11 +++++++++++
2 files changed, 18 insertions(+)
diff --git
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpMessageParser.scala
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpMessageParser.scala
index fbdc2d664..3080aa834 100644
---
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpMessageParser.scala
+++
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/HttpMessageParser.scala
@@ -204,6 +204,13 @@ private[http] trait HttpMessageParser[Output >:
MessageOutput <: ParserOutput] {
// only allow one 'chunked'
failMessageStart("Multiple Transfer-Encoding entries not
supported")
}
+
+ // `transfer-encoding` is always modelled (it is in
`alwaysParsedHeaders`), so it only reaches us as a
+ // RawHeader when its value failed to parse and was degraded to one.
Framing must not silently fall back to
+ // Content-Length then: an upstream that does understand the value
would frame the message differently, which
+ // is a request smuggling discrepancy.
+ case h: RawHeader if h.lowercaseName == "transfer-encoding" =>
+ failMessageStart("Illegal `Transfer-Encoding` header value")
case h: Connection => ch match {
case None =>
parseHeaderLines(input, lineEnd, headers += h, headerCount + 1,
Some(h), clh, cth, isChunked, e100c, hh)
diff --git
a/http-core/src/test/scala/org/apache/pekko/http/impl/engine/parsing/RequestParserSpec.scala
b/http-core/src/test/scala/org/apache/pekko/http/impl/engine/parsing/RequestParserSpec.scala
index d346f045f..7edbc1526 100644
---
a/http-core/src/test/scala/org/apache/pekko/http/impl/engine/parsing/RequestParserSpec.scala
+++
b/http-core/src/test/scala/org/apache/pekko/http/impl/engine/parsing/RequestParserSpec.scala
@@ -708,6 +708,17 @@ abstract class RequestParserSpec(mode: String, newLine:
String) extends AnyFreeS
ErrorInfo("HTTP message contains more than the configured limit of 2
headers"))
}
+ "with an unparseable Transfer-Encoding header value" in new Test {
+ // the value cannot be modelled, so it would otherwise degrade to a
RawHeader and the message would be framed
+ // by Content-Length while an upstream that does understand it frames
by chunked encoding
+ """POST / HTTP/1.1
+ |Host: x
+ |Transfer-Encoding: "chunked"
+ |Content-Length: 3
+ |
+ |abc""" should parseToError(BadRequest, ErrorInfo("Illegal
`Transfer-Encoding` header value"))
+ }
+
"with an invalid Content-Length header value" in new Test {
"""GET / HTTP/1.0
|Content-Length: 1.5
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]