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 a2adc5b45 add a max-chunk-count config (#1195)
a2adc5b45 is described below

commit a2adc5b456d22e27baeb0690bce2bc851a4f8bc8
Author: PJ Fanning <[email protected]>
AuthorDate: Tue Aug 4 09:58:51 2026 +0100

    add a max-chunk-count config (#1195)
    
    * add a max-chunk-count config
    
    * new test
    
    * Update HttpMessageParser.scala
---
 .../max-chunk-count.excludes                         | 20 ++++++++++++++++++++
 http-core/src/main/resources/reference.conf          |  1 +
 .../http/impl/engine/parsing/HttpMessageParser.scala | 12 +++++++++---
 .../http/impl/settings/ParserSettingsImpl.scala      |  3 +++
 .../pekko/http/javadsl/settings/ParserSettings.scala |  2 ++
 .../http/scaladsl/settings/ParserSettings.scala      |  3 +++
 .../http/impl/engine/parsing/RequestParserSpec.scala | 10 ++++++++++
 7 files changed, 48 insertions(+), 3 deletions(-)

diff --git 
a/http-core/src/main/mima-filters/2.0.x.backwards.excludes/max-chunk-count.excludes
 
b/http-core/src/main/mima-filters/2.0.x.backwards.excludes/max-chunk-count.excludes
new file mode 100644
index 000000000..9211d2d19
--- /dev/null
+++ 
b/http-core/src/main/mima-filters/2.0.x.backwards.excludes/max-chunk-count.excludes
@@ -0,0 +1,20 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# new max-chunk-count setting
+ProblemFilters.exclude[ReversedMissingMethodProblem]("org.apache.pekko.http.javadsl.settings.ParserSettings.getMaxChunkCount")
+ProblemFilters.exclude[ReversedMissingMethodProblem]("org.apache.pekko.http.scaladsl.settings.ParserSettings.maxChunkCount")
diff --git a/http-core/src/main/resources/reference.conf 
b/http-core/src/main/resources/reference.conf
index b47d6c48e..74bb229ed 100644
--- a/http-core/src/main/resources/reference.conf
+++ b/http-core/src/main/resources/reference.conf
@@ -736,6 +736,7 @@ pekko.http {
     max-header-count           = 64
     max-chunk-ext-length       = 256
     max-chunk-size             = 1m
+    max-chunk-count            = 100000
 
     # HTTP comments (as e.g. prominently used in User-Agent headers) can be 
nested. To avoid too deep nesting
     # and the associated parsing and storage cost, the depth of nested 
comments is limited to the given value.
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 2678707b4..110564916 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
@@ -249,7 +249,8 @@ private[http] trait HttpMessageParser[Output >: 
MessageOutput <: ParserOutput] {
   }
 
   protected final def parseChunk(
-      input: ByteString, offset: Int, isLastMessage: Boolean, totalBytesRead: 
Long): StateResult = {
+      input: ByteString, offset: Int, isLastMessage: Boolean, totalBytesRead: 
Long, chunkCount: Int = 0)
+      : StateResult = {
     @tailrec def parseTrailer(extension: String, lineStart: Int, headers: 
List[HttpHeader] = Nil,
         headerCount: Int = 0): StateResult = {
       var errorInfo: ErrorInfo = null
@@ -277,10 +278,14 @@ private[http] trait HttpMessageParser[Output >: 
MessageOutput <: ParserOutput] {
 
     def parseChunkBody(chunkSize: Int, extension: String, cursor: Int): 
StateResult =
       if (chunkSize > 0) {
+        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))
+          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)
@@ -318,7 +323,8 @@ private[http] trait HttpMessageParser[Output >: 
MessageOutput <: ParserOutput] {
 
     try parseSize(offset, 0)
     catch {
-      case NotEnoughDataException => continue(input, offset)(parseChunk(_, _, 
isLastMessage, totalBytesRead))
+      case NotEnoughDataException =>
+        continue(input, offset)(parseChunk(_, _, isLastMessage, 
totalBytesRead, chunkCount))
     }
   }
 
diff --git 
a/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ParserSettingsImpl.scala
 
b/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ParserSettingsImpl.scala
index aeb3715c9..70885f6f1 100644
--- 
a/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ParserSettingsImpl.scala
+++ 
b/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ParserSettingsImpl.scala
@@ -44,6 +44,7 @@ private[pekko] final case class ParserSettingsImpl(
     maxToStrictBytes: Long,
     maxChunkExtLength: Int,
     maxChunkSize: Int,
+    maxChunkCount: Int,
     maxCommentParsingDepth: Int,
     uriParsingMode: Uri.ParsingMode,
     cookieParsingMode: CookieParsingMode,
@@ -71,6 +72,7 @@ private[pekko] final case class ParserSettingsImpl(
   require(maxContentLengthSetting.forall(_ >= 0), "if set max-content-length 
must be >= 0")
   require(maxChunkExtLength > 0, "max-chunk-ext-length must be > 0")
   require(maxChunkSize > 0, "max-chunk-size must be > 0")
+  require(maxChunkCount > 0, "max-chunk-count must be > 0")
   require(maxCommentParsingDepth > 0, "max-comment-parsing-depth must be > 0")
 
   override val defaultHeaderValueCacheLimit: Int = 
headerValueCacheLimits("default")
@@ -112,6 +114,7 @@ object ParserSettingsImpl extends 
SettingsCompanionImpl[ParserSettingsImpl]("pek
       c.getPossiblyInfiniteBytes("max-to-strict-bytes"),
       c.getIntBytes("max-chunk-ext-length"),
       c.getIntBytes("max-chunk-size"),
+      c.getIntBytes("max-chunk-count"),
       c.getInt("max-comment-parsing-depth"),
       Uri.ParsingMode(c.getString("uri-parsing-mode")),
       CookieParsingMode(c.getString("cookie-parsing-mode")),
diff --git 
a/http-core/src/main/scala/org/apache/pekko/http/javadsl/settings/ParserSettings.scala
 
b/http-core/src/main/scala/org/apache/pekko/http/javadsl/settings/ParserSettings.scala
index c726c6597..03df29a10 100644
--- 
a/http-core/src/main/scala/org/apache/pekko/http/javadsl/settings/ParserSettings.scala
+++ 
b/http-core/src/main/scala/org/apache/pekko/http/javadsl/settings/ParserSettings.scala
@@ -44,6 +44,7 @@ abstract class ParserSettings private[pekko] () extends 
BodyPartParser.Settings
   def getMaxToStrictBytes: Long
   def getMaxChunkExtLength: Int
   def getMaxChunkSize: Int
+  def getMaxChunkCount: Int
   def getMaxCommentParsingDepth: Int
   def getUriParsingMode: Uri.ParsingMode
   def getCookieParsingMode: ParserSettings.CookieParsingMode
@@ -74,6 +75,7 @@ abstract class ParserSettings private[pekko] () extends 
BodyPartParser.Settings
   def withMaxToStrictBytes(newValue: Long): ParserSettings = 
self.copy(maxToStrictBytes = newValue)
   def withMaxChunkExtLength(newValue: Int): ParserSettings = 
self.copy(maxChunkExtLength = newValue)
   def withMaxChunkSize(newValue: Int): ParserSettings = self.copy(maxChunkSize 
= newValue)
+  def withMaxChunkCount(newValue: Int): ParserSettings = 
self.copy(maxChunkCount = newValue)
   def withMaxCommentParsingDepth(newValue: Int): ParserSettings = 
self.copy(maxCommentParsingDepth = newValue)
   def withUriParsingMode(newValue: Uri.ParsingMode): ParserSettings = 
self.copy(uriParsingMode = newValue.asScala)
   def withCookieParsingMode(newValue: ParserSettings.CookieParsingMode): 
ParserSettings =
diff --git 
a/http-core/src/main/scala/org/apache/pekko/http/scaladsl/settings/ParserSettings.scala
 
b/http-core/src/main/scala/org/apache/pekko/http/scaladsl/settings/ParserSettings.scala
index 2c0d455e1..23ee3c75c 100644
--- 
a/http-core/src/main/scala/org/apache/pekko/http/scaladsl/settings/ParserSettings.scala
+++ 
b/http-core/src/main/scala/org/apache/pekko/http/scaladsl/settings/ParserSettings.scala
@@ -47,6 +47,7 @@ abstract class ParserSettings private[pekko] () extends 
pekko.http.javadsl.setti
   def maxToStrictBytes: Long
   def maxChunkExtLength: Int
   def maxChunkSize: Int
+  def maxChunkCount: Int
   def maxCommentParsingDepth: Int
   def uriParsingMode: Uri.ParsingMode
   def cookieParsingMode: ParserSettings.CookieParsingMode
@@ -68,6 +69,7 @@ abstract class ParserSettings private[pekko] () extends 
pekko.http.javadsl.setti
   override def getCookieParsingMode: js.ParserSettings.CookieParsingMode = 
this.cookieParsingMode
   override def getHeaderValueCacheLimits: util.Map[String, Int] = 
this.headerValueCacheLimits.asJava
   override def getMaxChunkExtLength = this.maxChunkExtLength
+  override def getMaxChunkCount = this.maxChunkCount
   override def getUriParsingMode: pekko.http.javadsl.model.Uri.ParsingMode = 
this.uriParsingMode
   override def getMaxHeaderCount = this.maxHeaderCount
   override def getMaxContentLength = this.maxContentLength
@@ -111,6 +113,7 @@ abstract class ParserSettings private[pekko] () extends 
pekko.http.javadsl.setti
   override def withMaxToStrictBytes(newValue: Long): ParserSettings = 
self.copy(maxToStrictBytes = newValue)
   override def withMaxChunkExtLength(newValue: Int): ParserSettings = 
self.copy(maxChunkExtLength = newValue)
   override def withMaxChunkSize(newValue: Int): ParserSettings = 
self.copy(maxChunkSize = newValue)
+  override def withMaxChunkCount(newValue: Int): ParserSettings = 
self.copy(maxChunkCount = newValue)
   override def withMaxCommentParsingDepth(newValue: Int): ParserSettings = 
self.copy(maxCommentParsingDepth = newValue)
   override def withIllegalHeaderWarnings(newValue: Boolean): ParserSettings =
     self.copy(illegalHeaderWarnings = newValue)
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 6782492d3..53ff54e85 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
@@ -556,6 +556,16 @@ abstract class RequestParserSpec(mode: String, newLine: 
String) extends AnyFreeS
         closeAfterResponseCompletion shouldEqual Seq(false)
       }
 
+      "too many chunks" in new Test {
+        override def parserSettings: ParserSettings =
+          super.parserSettings.withMaxChunkCount(5)
+
+        val chunks = (1 to 10).map(i => s"1\na\n").mkString + "0\n"
+        val result = multiParse(newParser)(Seq(prep(start + chunks)))
+        val errors = result.collect { case Left(EntityStreamError(info)) => 
info.summary }
+        errors should contain("HTTP chunk count exceeds the configured limit 
of 5 chunks")
+      }
+
       "an illegal chunk termination" in new Test {
         Seq(
           start,


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to