From c570f3b2da2f17dbffdcdbed2e0e99e685d8b47f Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Mon, 5 Feb 2024 10:58:25 +0100 Subject: [PATCH] Fix off-by-one error in PartEvent part count This commit fixes an off-by-one error in the PartEventHttpMessageReader, so that it no longer counts empty windows. Closes gh-32122 --- .../multipart/PartEventHttpMessageReader.java | 33 ++++++++----------- .../PartEventHttpMessageReaderTests.java | 1 + 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/PartEventHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/PartEventHttpMessageReader.java index 09987862b11..9f54c4727d9 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/multipart/PartEventHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/PartEventHttpMessageReader.java @@ -155,29 +155,22 @@ public class PartEventHttpMessageReader extends LoggingCodecSupport implements H AtomicInteger partCount = new AtomicInteger(); return allPartsTokens .windowUntil(t -> t instanceof MultipartParser.HeadersToken, true) - .concatMap(partTokens -> { - if (tooManyParts(partCount)) { - return Mono.error(new DecodingException("Too many parts (" + partCount.get() + "/" + - this.maxParts + " allowed)")); - } - else { - return partTokens.switchOnFirst((signal, flux) -> { - if (signal.hasValue()) { - MultipartParser.HeadersToken headersToken = (MultipartParser.HeadersToken) signal.get(); - Assert.state(headersToken != null, "Signal should be headers token"); - - HttpHeaders headers = headersToken.headers(); - Flux bodyTokens = flux.ofType( - MultipartParser.BodyToken.class); - return createEvents(headers, bodyTokens); - } - else { + .concatMap(partTokens -> partTokens + .switchOnFirst((signal, flux) -> { + if (!signal.hasValue()) { // complete or error signal return flux.cast(PartEvent.class); } - }); - } - }); + else if (tooManyParts(partCount)) { + return Mono.error(new DecodingException("Too many parts (" + partCount.get() + + "/" + this.maxParts + " allowed)")); + } + MultipartParser.HeadersToken headersToken = (MultipartParser.HeadersToken) signal.get(); + Assert.state(headersToken != null, "Signal should be headers token"); + + HttpHeaders headers = headersToken.headers(); + return createEvents(headers, flux.ofType(MultipartParser.BodyToken.class)); + })); }); } diff --git a/spring-web/src/test/java/org/springframework/http/codec/multipart/PartEventHttpMessageReaderTests.java b/spring-web/src/test/java/org/springframework/http/codec/multipart/PartEventHttpMessageReaderTests.java index 11de6ab281a..0e739d8307d 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/multipart/PartEventHttpMessageReaderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/multipart/PartEventHttpMessageReaderTests.java @@ -238,6 +238,7 @@ class PartEventHttpMessageReaderTests { Flux result = reader.read(forClass(PartEvent.class), request, emptyMap()); StepVerifier.create(result) + .assertNext(form(headers -> assertThat(headers).isEmpty(), "This is implicitly typed plain ASCII text.\r\nIt does NOT end with a linebreak.")) .expectError(DecodingException.class) .verify(); }