diff --git a/spring-web/src/main/java/org/springframework/http/codec/multipart/SynchronossPartHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/multipart/SynchronossPartHttpMessageReader.java index 47f40fccf66428b651d71316f081daac84f3d70c..c303b1f7e7bb53da798d9aca0e21f27a2c6b6fbd 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/multipart/SynchronossPartHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/multipart/SynchronossPartHttpMessageReader.java @@ -261,6 +261,13 @@ public class SynchronossPartHttpMessageReader extends LoggingCodecSupport implem } } + @Override + protected void hookOnComplete() { + if (this.listener != null) { + this.listener.onAllPartsFinished(); + } + } + @Override protected void hookFinally(SignalType type) { try { diff --git a/spring-web/src/test/java/org/springframework/http/codec/multipart/SynchronossPartHttpMessageReaderTests.java b/spring-web/src/test/java/org/springframework/http/codec/multipart/SynchronossPartHttpMessageReaderTests.java index 8533dd426da00276d7db28ef580a3062db65efc0..8c09becc439520c113408655391ace103ea56857 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/multipart/SynchronossPartHttpMessageReaderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/multipart/SynchronossPartHttpMessageReaderTests.java @@ -18,6 +18,7 @@ package org.springframework.http.codec.multipart; import java.io.File; import java.io.IOException; +import java.nio.channels.ReadableByteChannel; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.Map; @@ -46,6 +47,7 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest; import org.springframework.util.MultiValueMap; import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.core.ResolvableType.forClassWithGenerics; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; @@ -142,6 +144,24 @@ public class SynchronossPartHttpMessageReaderTests extends AbstractLeakCheckingT subscriber.cancel(); } + @Test + void gh23768() throws IOException { + ReadableByteChannel channel = new ClassPathResource("invalid.multipart", getClass()).readableChannel(); + Flux body = DataBufferUtils.readByteChannel(() -> channel, this.bufferFactory, 1024); + + MediaType contentType = new MediaType("multipart", "form-data", + singletonMap("boundary", "NbjrKgjbsaMLdnMxMfDpD6myWomYc0qNX0w")); + ServerHttpRequest request = MockServerHttpRequest.post("/") + .contentType(contentType) + .body(body); + + Mono> parts = this.reader.readMono(PARTS_ELEMENT_TYPE, request, emptyMap()); + + StepVerifier.create(parts) + .assertNext(result -> assertThat(result).isEmpty()) + .verifyComplete(); + } + @Test void readTooManyParts() { testMultipartExceptions(reader -> reader.setMaxParts(1), ex -> { diff --git a/spring-web/src/test/resources/org/springframework/http/codec/multipart/invalid.multipart b/spring-web/src/test/resources/org/springframework/http/codec/multipart/invalid.multipart new file mode 100644 index 0000000000000000000000000000000000000000..9f09680d0433e9cc077eccce1799f66f6d34ea2e --- /dev/null +++ b/spring-web/src/test/resources/org/springframework/http/codec/multipart/invalid.multipart @@ -0,0 +1,5 @@ +--NbjrKgjbsaMLdnMxMfDpD6myWomYc0qNX0w +Content-Disposition: form-data; name="part-00-name" + +post-payload-text-23456789ABCDEF:post-payload-0001-3456789ABCDEF:post-payload-0002-3456789ABCDEF:post-payload-0003-3456789ABCDEF +--NbjrKgjbsaMLdnMxMfDpD6myWomYc