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 3ee26ed8e..9e2fcd5d6 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,9 @@ 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) + // the boundary starts a new part, so its header state starts empty. Note this must stay a direct + // self-call: it is what keeps this method tail-recursive. + 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 d5cf37c32..6770b342b 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 @@ -19,6 +19,7 @@ import scala.concurrent.duration._ import org.apache.pekko import pekko.http.impl.util._ import pekko.http.scaladsl.model._ +import pekko.http.scaladsl.settings.ParserSettings import pekko.http.scaladsl.model.MediaTypes._ import pekko.http.scaladsl.model.headers._ import pekko.http.scaladsl.util.FastFuture._ @@ -62,6 +63,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"),