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