From dde79a9b65797c6aa5dd32b76f1705d060a3a391 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 14 Sep 2020 14:06:15 +0100 Subject: [PATCH 1/3] Upgrade to Reactor Dysprosium-SR12 Closes gh-25730 --- build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index b26418f7005..77271279533 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ configure(allprojects) { project -> imports { mavenBom "com.fasterxml.jackson:jackson-bom:2.10.5" mavenBom "io.netty:netty-bom:4.1.51.Final" - mavenBom "io.projectreactor:reactor-bom:Dysprosium-BUILD-SNAPSHOT" + mavenBom "io.projectreactor:reactor-bom:Dysprosium-SR12" mavenBom "io.rsocket:rsocket-bom:1.0.1" mavenBom "org.eclipse.jetty:jetty-bom:9.4.31.v20200723" mavenBom "org.jetbrains.kotlin:kotlin-bom:1.3.72" @@ -279,7 +279,6 @@ configure(allprojects) { project -> repositories { mavenCentral() maven { url "https://repo.spring.io/libs-spring-framework-build" } - maven { url "https://repo.spring.io/snapshot" } // Reactor } } configurations.all { From dccc78146a281208874b76e5bb69bb8bc3af608d Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 14 Sep 2020 14:28:02 +0100 Subject: [PATCH 2/3] Expose defaultCharset in StringDecoder Closes gh-25762 --- .../core/codec/StringDecoder.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java b/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java index 67ecfbe077f..212d23900ca 100644 --- a/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java +++ b/spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java @@ -72,6 +72,8 @@ public final class StringDecoder extends AbstractDataBufferDecoder { private final boolean stripDelimiter; + private Charset defaultCharset = DEFAULT_CHARSET; + private final ConcurrentMap delimitersCache = new ConcurrentHashMap<>(); @@ -83,6 +85,24 @@ public final class StringDecoder extends AbstractDataBufferDecoder { } + /** + * Set the default character set to fall back on if the MimeType does not specify any. + *

By default this is {@code UTF-8}. + * @param defaultCharset the charset to fall back on + * @since 5.2.9 + */ + public void setDefaultCharset(Charset defaultCharset) { + this.defaultCharset = defaultCharset; + } + + /** + * Return the configured {@link #setDefaultCharset(Charset) defaultCharset}. + * @since 5.2.9 + */ + public Charset getDefaultCharset() { + return this.defaultCharset; + } + @Override public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) { return (elementType.resolve() == String.class && super.canDecode(elementType, mimeType)); @@ -136,12 +156,12 @@ public final class StringDecoder extends AbstractDataBufferDecoder { return value; } - private static Charset getCharset(@Nullable MimeType mimeType) { + private Charset getCharset(@Nullable MimeType mimeType) { if (mimeType != null && mimeType.getCharset() != null) { return mimeType.getCharset(); } else { - return DEFAULT_CHARSET; + return getDefaultCharset(); } } From 16d125ce768b24ac2cb37892c197c9e6b2582689 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 14 Sep 2020 15:28:53 +0100 Subject: [PATCH 3/3] Extend Content-Disposition "inline" to error responses We don't expect a browser to save error responses to a file but we extend this protection anyway since "inline" is only a suggestion that shouldn't have any side effects. --- ...stractMessageConverterMethodProcessor.java | 2 +- ...questResponseBodyMethodProcessorTests.java | 27 +++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java index 932ff78a057..4f1a532b98c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java @@ -411,7 +411,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe try { int status = response.getServletResponse().getStatus(); - if (status < 200 || status > 299) { + if (status < 200 || (status > 299 && status < 400)) { return; } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java index 0e5ef25cd70..c952eb16a6e 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessorTests.java @@ -400,6 +400,25 @@ public class RequestResponseBodyMethodProcessorTests { this.servletRequest.removeAttribute(WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE); } + @Test + public void addContentDispositionHeaderToErrorResponse() throws Exception { + ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean(); + factory.addMediaType("pdf", new MediaType("application", "pdf")); + factory.afterPropertiesSet(); + + RequestResponseBodyMethodProcessor processor = new RequestResponseBodyMethodProcessor( + Collections.singletonList(new StringHttpMessageConverter()), + factory.getObject()); + + this.servletRequest.setRequestURI("/hello.dataless"); + this.servletResponse.setStatus(400); + + processor.handleReturnValue("body", this.returnTypeString, this.container, this.request); + + String header = servletResponse.getHeader("Content-Disposition"); + assertThat(header).isEqualTo("inline;filename=f.txt"); + } + @Test public void supportsReturnTypeResponseBodyOnType() throws Exception { Method method = ResponseBodyController.class.getMethod("handle"); @@ -724,10 +743,14 @@ public class RequestResponseBodyMethodProcessorTests { String header = servletResponse.getHeader("Content-Disposition"); if (expectContentDisposition) { - assertThat(header).as("Expected 'Content-Disposition' header. Use case: '" + comment + "'").isEqualTo("inline;filename=f.txt"); + assertThat(header) + .as("Expected 'Content-Disposition' header. Use case: '" + comment + "'") + .isEqualTo("inline;filename=f.txt"); } else { - assertThat(header).as("Did not expect 'Content-Disposition' header. Use case: '" + comment + "'").isNull(); + assertThat(header) + .as("Did not expect 'Content-Disposition' header. Use case: '" + comment + "'") + .isNull(); } this.servletRequest = new MockHttpServletRequest();