diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/HttpHeadResponseDecorator.java b/spring-web/src/main/java/org/springframework/http/server/reactive/HttpHeadResponseDecorator.java index 4c8d0b0f06..d387abd329 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/HttpHeadResponseDecorator.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/HttpHeadResponseDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import reactor.core.publisher.Mono; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.http.HttpHeaders; /** * {@link ServerHttpResponse} decorator for HTTP HEAD requests. @@ -52,7 +53,11 @@ public class HttpHeadResponseDecorator extends ServerHttpResponseDecorator { DataBufferUtils.release(buffer); return next; }) - .doOnNext(count -> getHeaders().setContentLength(count)) + .doOnNext(length -> { + if (length > 0 || getHeaders().getFirst(HttpHeaders.CONTENT_LENGTH) == null) { + getHeaders().setContentLength(length); + } + }) .then(); } diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/HttpHeadResponseDecoratorTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/HttpHeadResponseDecoratorTests.java new file mode 100644 index 0000000000..8231bc4789 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/HttpHeadResponseDecoratorTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.http.server.reactive; + +import java.nio.charset.StandardCharsets; + +import io.netty.buffer.PooledByteBufAllocator; +import org.junit.After; +import org.junit.Test; +import reactor.core.publisher.Flux; + +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.LeakAwareDataBufferFactory; +import org.springframework.core.io.buffer.NettyDataBufferFactory; +import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for {@link HttpHeadResponseDecorator}. + * @author Rossen Stoyanchev + */ +public class HttpHeadResponseDecoratorTests { + + private final LeakAwareDataBufferFactory bufferFactory = + new LeakAwareDataBufferFactory(new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT)); + + private final ServerHttpResponse response = + new HttpHeadResponseDecorator(new MockServerHttpResponse(this.bufferFactory)); + + + @After + public void tearDown() { + this.bufferFactory.checkForLeaks(); + } + + + @Test + public void write() { + Flux body = Flux.just(toDataBuffer("data1"), toDataBuffer("data2")); + response.writeWith(body).block(); + assertEquals(10, response.getHeaders().getContentLength()); + } + + @Test // gh-23484 + public void writeWithGivenContentLength() { + int length = 15; + this.response.getHeaders().setContentLength(length); + this.response.writeWith(Flux.empty()).block(); + assertEquals(length, this.response.getHeaders().getContentLength()); + } + + + private DataBuffer toDataBuffer(String s) { + DataBuffer buffer = this.bufferFactory.allocateBuffer(); + buffer.write(s.getBytes(StandardCharsets.UTF_8)); + return buffer; + } + +} diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/PathResourceResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/PathResourceResolver.java index 17cda85be2..beafe65d0d 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/PathResourceResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/PathResourceResolver.java @@ -203,6 +203,9 @@ public class PathResourceResolver extends AbstractResourceResolver { return true; } } + catch (IllegalArgumentException ex) { + // May not be possible to decode... + } catch (UnsupportedEncodingException ex) { // Should never happen... } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java index 2735663812..87d59e7cd7 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/resource/ResourceWebHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -468,7 +468,10 @@ public class ResourceWebHandler implements WebHandler, InitializingBean { return true; } } - catch (IllegalArgumentException | UnsupportedEncodingException ex) { + catch (IllegalArgumentException ex) { + // May not be possible to decode... + } + catch (UnsupportedEncodingException ex) { // Should never happen... } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/resource/PathResourceResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/resource/PathResourceResolverTests.java index 29cd298c6f..bf6532537c 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/resource/PathResourceResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/resource/PathResourceResolverTests.java @@ -106,6 +106,13 @@ public class PathResourceResolverTests { assertThat(actual).isNull(); } + @Test // gh-23463 + public void ignoreInvalidEscapeSequence() throws IOException { + UrlResource location = new UrlResource(getClass().getResource("./test/")); + Resource resource = location.createRelative("test%file.txt"); + assertTrue(this.resolver.checkResource(resource, location)); + } + @Test public void checkResourceWithAllowedLocations() { this.resolver.setAllowedLocations( diff --git a/spring-webflux/src/test/resources/org/springframework/web/reactive/resource/test/test%file.txt b/spring-webflux/src/test/resources/org/springframework/web/reactive/resource/test/test%file.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java index db8e7c3c28..b1ebfe9423 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/PathResourceResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -288,6 +288,9 @@ public class PathResourceResolver extends AbstractResourceResolver { return true; } } + catch (IllegalArgumentException ex) { + // May not be possible to decode... + } catch (UnsupportedEncodingException ex) { // Should never happen... } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java index 587cffeb74..4ef71cc5bf 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceHttpRequestHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -605,7 +605,10 @@ public class ResourceHttpRequestHandler extends WebContentGenerator return true; } } - catch (IllegalArgumentException | UnsupportedEncodingException ex) { + catch (IllegalArgumentException ex) { + // May not be possible to decode... + } + catch (UnsupportedEncodingException ex) { // Should never happen... } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/PathResourceResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/PathResourceResolverTests.java index 653ab9e783..59286e6bf0 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/PathResourceResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/resource/PathResourceResolverTests.java @@ -91,6 +91,13 @@ public class PathResourceResolverTests { assertThat(actual).isNull(); } + @Test // gh-23463 + public void ignoreInvalidEscapeSequence() throws IOException { + UrlResource location = new UrlResource(getClass().getResource("./test/")); + Resource resource = location.createRelative("test%file.txt"); + assertTrue(this.resolver.checkResource(resource, location)); + } + @Test public void checkResourceWithAllowedLocations() { this.resolver.setAllowedLocations( @@ -104,8 +111,7 @@ public class PathResourceResolverTests { assertThat(actual).isEqualTo("../testalternatepath/bar.css"); } - // SPR-12432 - @Test + @Test // SPR-12432 public void checkServletContextResource() throws Exception { Resource classpathLocation = new ClassPathResource("test/", PathResourceResolver.class); MockServletContext context = new MockServletContext(); @@ -117,8 +123,7 @@ public class PathResourceResolverTests { assertThat(this.resolver.checkResource(resource, servletContextLocation)).isTrue(); } - // SPR-12624 - @Test + @Test // SPR-12624 public void checkRelativeLocation() throws Exception { String locationUrl= new UrlResource(getClass().getResource("./test/")).getURL().toExternalForm(); Resource location = new UrlResource(locationUrl.replace("/springframework","/../org/springframework")); @@ -126,15 +131,13 @@ public class PathResourceResolverTests { assertThat(this.resolver.resolveResource(null, "main.css", Collections.singletonList(location), null)).isNotNull(); } - // SPR-12747 - @Test + @Test // SPR-12747 public void checkFileLocation() throws Exception { Resource resource = getResource("main.css"); assertThat(this.resolver.checkResource(resource, resource)).isTrue(); } - // SPR-13241 - @Test + @Test // SPR-13241 public void resolvePathRootResource() { Resource webjarsLocation = new ClassPathResource("/META-INF/resources/webjars/", PathResourceResolver.class); String path = this.resolver.resolveUrlPathInternal("", Collections.singletonList(webjarsLocation), null); diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/resource/test/test%file.txt b/spring-webmvc/src/test/resources/org/springframework/web/servlet/resource/test/test%file.txt new file mode 100644 index 0000000000..e69de29bb2