From 2446c5cb3f688316a196814e92d81e74cdfb89c3 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sun, 23 Aug 2026 00:41:14 +0100 Subject: [PATCH] stop parsing chunks once the chunk count limit is reached Motivation: The `max-chunk-count` check in `HttpMessageParser.parseChunkBody` has no `else` branch, so the `StateResult` of `failEntityStream` is discarded and parsing continues after the error has been emitted: the current chunk is emitted, the parser trampolines into the next one, and every following chunk in the buffer emits another error. The parser therefore keeps producing output after it has set itself to terminated. A consumer that stops at the first error does not notice, which is why the behaviour has gone unseen; the parser still does the work. Modification: Put the rest of `parseChunkBody` into the `else` branch of the check, the way the other limits in this parser are written. Result: Nothing is emitted after the chunk count error, and the parser stops parsing the rest of the body. Tests: - sbt "http-core/testOnly org.apache.pekko.http.impl.engine.parsing.RequestParserCRLFSpec org.apache.pekko.http.impl.engine.parsing.RequestParserLFSpec" - pass, 1 new test that collects the raw parser output; without the change it sees the chunks "a", "b", "c", "" instead of "a", "b" - sbt http-core/test - pass - sbt http-core/mimaReportBinaryIssues - pass - sbt http-core/scalafmt http-core/Test/scalafmt - clean References: None - makes the max-chunk-count limit stop parsing --- .../engine/parsing/HttpMessageParser.scala | 23 +++++++++++-------- .../engine/parsing/RequestParserSpec.scala | 23 +++++++++++++++++++ 2 files changed, 36 insertions(+), 10 deletions(-) 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 110564916..e7bcad180 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 @@ -281,16 +281,19 @@ private[http] trait HttpMessageParser[Output >: MessageOutput <: ParserOutput] { if (chunkCount >= settings.maxChunkCount) failEntityStream( s"HTTP chunk count exceeds the configured limit of ${settings.maxChunkCount} chunks") - val chunkBodyEnd = cursor + chunkSize - def result(terminatorLen: Int) = { - emit(EntityChunk(HttpEntity.Chunk(input.slice(cursor, chunkBodyEnd).compact, extension))) - Trampoline(_ => - parseChunk(input, chunkBodyEnd + terminatorLen, isLastMessage, totalBytesRead + chunkSize, chunkCount + 1)) - } - byteAt(input, chunkBodyEnd) match { - case CR_BYTE if byteAt(input, chunkBodyEnd + 1) == LF_BYTE => result(2) - case LF_BYTE => result(1) - case x => failEntityStream("Illegal chunk termination") + else { + val chunkBodyEnd = cursor + chunkSize + def result(terminatorLen: Int) = { + emit(EntityChunk(HttpEntity.Chunk(input.slice(cursor, chunkBodyEnd).compact, extension))) + Trampoline(_ => + parseChunk(input, chunkBodyEnd + terminatorLen, isLastMessage, totalBytesRead + chunkSize, + chunkCount + 1)) + } + byteAt(input, chunkBodyEnd) match { + case CR_BYTE if byteAt(input, chunkBodyEnd + 1) == LF_BYTE => result(2) + case LF_BYTE => result(1) + case x => failEntityStream("Illegal chunk termination") + } } } else parseTrailer(extension, cursor) 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 53ff54e85..702871d45 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 @@ -337,6 +337,29 @@ abstract class RequestParserSpec(mode: String, newLine: String) extends AnyFreeS closeAfterResponseCompletion shouldEqual Seq(false) } + "stop parsing a request that has more chunks than the configured limit" in new Test { + override protected def parserSettings: ParserSettings = super.parserSettings.withMaxChunkCount(2) + + val input = prep(start + + """1 + |a + |1 + |b + |1 + |c + |0 + | + |""") + // collect the raw parser output: nothing must be emitted after the error, in particular no further chunk + val outputs = + Source.single(SessionBytes(TLSPlacebo.dummySession, ByteString(input))) + .via(newParser).runWith(Sink.seq).awaitResult(awaitAtMost) + + outputs.collect { case EntityChunk(chunk) => chunk.data.utf8String } shouldEqual Seq("a", "b") + outputs.last shouldEqual EntityStreamError( + ErrorInfo("HTTP chunk count exceeds the configured limit of 2 chunks")) + } + "don't overflow the stack for large buffers of chunks" in new Test { override val awaitAtMost = 10000.millis.dilated