diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/request/AbstractMockHttpServletRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/request/AbstractMockHttpServletRequestBuilder.java index 503f48f2ca1..2540b45d8e8 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/request/AbstractMockHttpServletRequestBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/request/AbstractMockHttpServletRequestBuilder.java @@ -898,16 +898,14 @@ public abstract class AbstractMockHttpServletRequestBuilder "Invalid servlet path [" + this.servletPath + "] for request URI [" + requestUri + "]"); + String other = requestUri.substring(this.contextPath.length() + this.servletPath.length()); + path = (StringUtils.hasText(other) ? UrlPathHelper.defaultInstance.decodeRequestString(request, other) : null); } - request.setPathInfo(this.pathInfo); + request.setPathInfo(path); } private void addRequestParams(MockHttpServletRequest request, MultiValueMap map) { diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/request/AbstractMockHttpServletRequestBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/request/AbstractMockHttpServletRequestBuilderTests.java index 227b3316211..01e9dc99dfb 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/request/AbstractMockHttpServletRequestBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/request/AbstractMockHttpServletRequestBuilderTests.java @@ -32,6 +32,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link AbstractMockHttpServletRequestBuilder} * * @author Stephane Nicoll + * @author Réda Housni Alaoui */ class AbstractMockHttpServletRequestBuilderTests { @@ -128,6 +129,14 @@ class AbstractMockHttpServletRequestBuilderTests { assertThat(buildRequest(builder).getHeader("API-Version")).isEqualTo("1.1"); } + @Test // gh-35493 + void pathInfoIsNotMutatedByBuildMethod() { + TestRequestBuilder builder = new TestRequestBuilder(HttpMethod.GET).uri("/b"); + assertThat(buildRequest(builder).getPathInfo()).isEqualTo("/b"); + builder.uri("/a"); + assertThat(buildRequest(builder).getPathInfo()).isEqualTo("/a"); + } + private MockHttpServletRequest buildRequest(AbstractMockHttpServletRequestBuilder builder) { return builder.buildRequest(this.servletContext); } diff --git a/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java index 5e56fe1e482..45831935324 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ResourceHttpMessageWriter.java @@ -52,12 +52,14 @@ import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.MimeTypeUtils; /** - * {@code HttpMessageWriter} that can write a {@link Resource}. + * {@code HttpMessageWriter} that can write a {@link Resource} from both`` client + * and server perspectives. * - *

Also an implementation of {@code HttpMessageWriter} with support for writing one - * or more {@link ResourceRegion}'s based on the HTTP ranges specified in the request. + *

From a server perspective, the server-side only write method supports + * writing one or more {@link ResourceRegion}'s based on HTTP ranges specified + * in the request. * - *

For reading to a Resource, use {@link ResourceDecoder} wrapped with + *

To read a Resource, use {@link ResourceDecoder} wrapped with * {@link DecoderHttpMessageReader}. * * @author Arjen Poutsma @@ -123,16 +125,19 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter { if (result != null) { return result; } - else { - Mono input = Mono.just(resource); - DataBufferFactory factory = message.bufferFactory(); - Flux body = this.encoder.encode(input, factory, type, message.getHeaders().getContentType(), hints) - .subscribeOn(Schedulers.boundedElastic()); - if (logger.isDebugEnabled()) { - body = body.doOnNext(buffer -> Hints.touchDataBuffer(buffer, hints, logger)); - } - return message.writeWith(body); + + Mono input = Mono.just(resource); + DataBufferFactory factory = message.bufferFactory(); + MediaType contentType = message.getHeaders().getContentType(); + + Flux body = this.encoder.encode(input, factory, type, contentType, hints) + .subscribeOn(Schedulers.boundedElastic()); + + if (logger.isDebugEnabled()) { + body = body.doOnNext(buffer -> Hints.touchDataBuffer(buffer, hints, logger)); } + + return message.writeWith(body); })); } @@ -140,7 +145,10 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter { * Adds the default headers for the given resource to the given message. * @since 6.1 */ - public Mono addDefaultHeaders(ReactiveHttpOutputMessage message, Resource resource, @Nullable MediaType contentType, Map hints) { + public Mono addDefaultHeaders( + ReactiveHttpOutputMessage message, Resource resource, @Nullable MediaType contentType, + Map hints) { + return Mono.defer(() -> { HttpHeaders headers = message.getHeaders(); MediaType resourceMediaType = getResourceMediaType(contentType, resource, hints); @@ -150,16 +158,15 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter { headers.set(HttpHeaders.ACCEPT_RANGES, "bytes"); } - if (headers.getContentLength() < 0) { - return lengthOf(resource) - .flatMap(contentLength -> { - headers.setContentLength(contentLength); - return Mono.empty(); - }); - } - else { + if (headers.getContentLength() >= 0) { return Mono.empty(); } + + return lengthOf(resource) + .flatMap(contentLength -> { + headers.setContentLength(contentLength); + return Mono.empty(); + }); }); } @@ -226,8 +233,7 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter { ranges = request.getHeaders().getRange(); } catch (IllegalArgumentException ex) { - response.setStatusCode(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE); - return response.setComplete(); + return handleInvalidRange(response); } return Mono.from(inputStream).flatMap(resource -> { @@ -235,7 +241,13 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter { return writeResource(resource, elementType, mediaType, response, hints); } response.setStatusCode(HttpStatus.PARTIAL_CONTENT); - List regions = HttpRange.toResourceRegions(ranges, resource); + List regions; + try { + regions = HttpRange.toResourceRegions(ranges, resource); + } + catch (IllegalArgumentException ex) { + return handleInvalidRange(response); + } MediaType resourceMediaType = getResourceMediaType(mediaType, resource, hints); if (regions.size() == 1){ ResourceRegion region = regions.get(0); @@ -261,6 +273,11 @@ public class ResourceHttpMessageWriter implements HttpMessageWriter { }); } + private static Mono handleInvalidRange(ServerHttpResponse response) { + response.setStatusCode(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE); + return response.setComplete(); + } + private Mono writeSingleRegion(ResourceRegion region, ReactiveHttpOutputMessage message, Map hints) { diff --git a/spring-web/src/test/java/org/springframework/http/codec/ResourceHttpMessageWriterTests.java b/spring-web/src/test/java/org/springframework/http/codec/ResourceHttpMessageWriterTests.java index 19af37c5610..436513a3fb6 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/ResourceHttpMessageWriterTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/ResourceHttpMessageWriterTests.java @@ -156,6 +156,15 @@ class ResourceHttpMessageWriterTests { assertThat(this.response.getStatusCode()).isEqualTo(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE); } + @Test // gh-35536 + void invalidRangePosition() { + + testWrite(get("/").header(HttpHeaders.RANGE, "bytes=2000-5000").build()); + + assertThat(this.response.getHeaders().getFirst(HttpHeaders.ACCEPT_RANGES)).isEqualTo("bytes"); + assertThat(this.response.getStatusCode()).isEqualTo(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE); + } + private void testWrite(MockServerHttpRequest request) { Mono mono = this.writer.write(this.input, null, null, TEXT_PLAIN, request, this.response, HINTS);