This is an automated email from the ASF dual-hosted git repository.

pjfanning pushed a commit to branch 1.4.x
in repository https://gitbox.apache.org/repos/asf/pekko-http.git


The following commit(s) were added to refs/heads/1.4.x by this push:
     new d5593bfb0 fix: start each multipart part with its own header state 
(#1279) (#1327)
d5593bfb0 is described below

commit d5593bfb07e31604f183ceb34913dc61fc74feeb
Author: PJ Fanning <[email protected]>
AuthorDate: Fri Oct 9 20:01:12 2026 +0100

    fix: start each multipart part with its own header state (#1279) (#1327)
    
    When a body part ends without a header-separating empty line, the
    `BoundaryHeader` branch of `parseHeaderLines` emits that part and continues
    into the next one, but passed its own `headers` and `headerCount` along. The
    boundary starts a new part, so both should start empty -- `cth` was already
    reset to `None` on the same call, which suggests the other two were simply
    missed.
    
    Two visible effects, both fixed here:
    
      - the following part was reported carrying headers it never declared;
      - `headerCount` was never reset either, so a run of such parts accumulated
        towards `max-header-count` across parts rather than per part, failing
        parts that are individually well within the limit.
    
    The reset stays a direct self-call to `parseHeaderLines`: that call is what
    makes the method `@tailrec`, and routing it through a helper would turn it
    into an unoptimised mutual recursion.
    
    Fixes #1278.
    
    (cherry picked from commit dc22225857f0cdbbe32d80b3685ea4de74764392)
---
 .../http/impl/engine/parsing/BodyPartParser.scala  |  3 +-
 .../unmarshalling/MultipartUnmarshallersSpec.scala | 40 ++++++++++++++++++++++
 2 files changed, 42 insertions(+), 1 deletion(-)

diff --git 
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/BodyPartParser.scala
 
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/BodyPartParser.scala
index 04e0a16cb..a0c9e0bf7 100644
--- 
a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/BodyPartParser.scala
+++ 
b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/parsing/BodyPartParser.scala
@@ -178,7 +178,8 @@ private[http] final class BodyPartParser(
             emit(BodyPartStart(headers.toList, _ => 
HttpEntity.empty(contentType)))
             val ix = lineStart + eolConfiguration.boundaryLength
             if (eolConfiguration.isEndOfLine(input, ix))
-              parseHeaderLines(input, ix + eolConfiguration.eolLength, 
headers, headerCount, None)
+              // an empty part; the boundary starts another one, so its header 
state starts empty
+              parseHeaderLines(input, ix + eolConfiguration.eolLength, 
ListBuffer[HttpHeader](), 0, None)
             else if (doubleDash(input, ix)) setShouldTerminate()
             else fail("Illegal multipart boundary in message content")
 
diff --git 
a/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/unmarshalling/MultipartUnmarshallersSpec.scala
 
b/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/unmarshalling/MultipartUnmarshallersSpec.scala
index a1d65687d..8f43d31a8 100644
--- 
a/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/unmarshalling/MultipartUnmarshallersSpec.scala
+++ 
b/http-tests/src/test/scala/org/apache/pekko/http/scaladsl/unmarshalling/MultipartUnmarshallersSpec.scala
@@ -18,6 +18,7 @@ import pekko.http.impl.util._
 import pekko.http.scaladsl.model.MediaTypes._
 import pekko.http.scaladsl.model._
 import pekko.http.scaladsl.model.headers._
+import pekko.http.scaladsl.settings.ParserSettings
 import pekko.http.scaladsl.util.FastFuture._
 import pekko.stream.scaladsl._
 import pekko.testkit._
@@ -61,6 +62,45 @@ trait MultipartUnmarshallersSpec extends 
PekkoSpecWithMaterializer {
                        
|--XYZABC--""".stripMarginWithNewline(lineFeed)))).to[Multipart.General] should 
haveParts(
           
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/xml(UTF-8)`),
 List(Age(12))))
       }
+      "consecutive parts without header separation, each keeping its own 
headers" in {
+        Unmarshal(HttpEntity(
+          `multipart/mixed`.withBoundary("XYZABC"),
+          ByteString("""--XYZABC
+                       |Age: 12
+                       |--XYZABC
+                       |Age: 13
+                       |--XYZABC
+                       
|--XYZABC--""".stripMarginWithNewline(lineFeed)))).to[Multipart.General] should 
haveParts(
+          
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`),
 List(Age(12))),
+          
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`),
 List(Age(13))),
+          
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`)))
+      }
+      "a part without header separation not carrying its Content-Type into the 
next part" in {
+        Unmarshal(HttpEntity(
+          `multipart/mixed`.withBoundary("XYZABC"),
+          ByteString("""--XYZABC
+                       |Content-type: text/xml; charset=UTF-8
+                       |--XYZABC
+                       
|--XYZABC--""".stripMarginWithNewline(lineFeed)))).to[Multipart.General] should 
haveParts(
+          
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/xml(UTF-8)`)),
+          
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`)))
+      }
+      "parts without header separation counting headers per part, not across 
parts" in {
+        implicit val parserSettings: ParserSettings = 
ParserSettings(system).withMaxHeaderCount(2)
+        Unmarshal(HttpEntity(
+          `multipart/mixed`.withBoundary("XYZABC"),
+          ByteString("""--XYZABC
+                       |Age: 12
+                       |X-Foo: bar
+                       |--XYZABC
+                       |Age: 13
+                       |X-Foo: baz
+                       
|--XYZABC--""".stripMarginWithNewline(lineFeed)))).to[Multipart.General] should 
haveParts(
+          
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`),
+            List(Age(12), RawHeader("X-Foo", "bar"))),
+          
Multipart.General.BodyPart.Strict(HttpEntity.empty(ContentTypes.`text/plain(UTF-8)`),
+            List(Age(13), RawHeader("X-Foo", "baz"))))
+      }
       "an implicitly typed part (without headers) (Strict)" in {
         Unmarshal(HttpEntity(
           `multipart/mixed`.withBoundary("XYZABC"),


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

Reply via email to