From e4314b4bde8f23c029af5ebf3d759224e3fc92bb Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Fri, 5 Jun 2020 10:52:09 +0200 Subject: [PATCH] Respect MimeType charset in Jackson codecs Before this commit, Jackson2CodecSupport and subclasses did not check media type encoding in the supportsMimeType method (called from canEncode/canDecode). As a result, the encoder reported that it can write (for instance) "application/json;charset=ISO-8859-1", but in practice wrote the default charset (UTF-8). This commit fixes that bug. Closes: gh-25076 --- .../http/codec/json/Jackson2CodecSupport.java | 26 ++++++++++++++++++- .../codec/cbor/Jackson2CborDecoderTests.java | 7 +++++ .../codec/cbor/Jackson2CborEncoderTests.java | 8 ++++++ .../codec/json/Jackson2JsonDecoderTests.java | 17 +----------- .../codec/json/Jackson2SmileDecoderTests.java | 8 ++++++ .../codec/json/Jackson2SmileEncoderTests.java | 7 +++++ 6 files changed, 56 insertions(+), 17 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java index 6162be280c91..63eabcd75012 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java @@ -18,13 +18,18 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonView; +import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; @@ -75,6 +80,9 @@ public abstract class Jackson2CodecSupport { new MimeType("application", "json"), new MimeType("application", "*+json"))); + private static final Map ENCODINGS = jsonEncodings(); + + protected final Log logger = HttpLogging.forLogName(getClass()); @@ -107,7 +115,17 @@ protected List getMimeTypes() { protected boolean supportsMimeType(@Nullable MimeType mimeType) { - return (mimeType == null || this.mimeTypes.stream().anyMatch(m -> m.isCompatibleWith(mimeType))); + if (mimeType == null) { + return true; + } + else if (this.mimeTypes.stream().noneMatch(m -> m.isCompatibleWith(mimeType))) { + return false; + } + else if (mimeType.getCharset() != null) { + Charset charset = mimeType.getCharset(); + return ENCODINGS.containsKey(charset.name()); + } + return true; } protected JavaType getJavaType(Type type, @Nullable Class contextClass) { @@ -145,4 +163,10 @@ protected MethodParameter getParameter(ResolvableType type) { @Nullable protected abstract A getAnnotation(MethodParameter parameter, Class annotType); + private static Map jsonEncodings() { + return EnumSet.allOf(JsonEncoding.class).stream() + .collect(Collectors.toMap(JsonEncoding::getJavaName, Function.identity())); + } + + } diff --git a/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborDecoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborDecoderTests.java index 7eaab7173312..041ad618b5d4 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborDecoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborDecoderTests.java @@ -16,6 +16,7 @@ package org.springframework.http.codec.cbor; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; @@ -27,6 +28,7 @@ import org.springframework.core.ResolvableType; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.testfixture.codec.AbstractDecoderTests; +import org.springframework.http.MediaType; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.MimeType; import org.springframework.web.testfixture.xml.Pojo; @@ -62,6 +64,11 @@ public void canDecode() { assertThat(decoder.canDecode(ResolvableType.forClass(String.class), null)).isFalse(); assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), APPLICATION_JSON)).isFalse(); + + assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "cbor", StandardCharsets.UTF_8))).isTrue(); + assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "cbor", StandardCharsets.ISO_8859_1))).isFalse(); } @Override diff --git a/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborEncoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborEncoderTests.java index a2ce8c51a3d1..b9b984581d6e 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborEncoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborEncoderTests.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.util.function.Consumer; import com.fasterxml.jackson.databind.ObjectMapper; @@ -28,6 +29,7 @@ import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.testfixture.io.buffer.AbstractLeakCheckingTests; import org.springframework.core.testfixture.io.buffer.DataBufferTestUtils; +import org.springframework.http.MediaType; import org.springframework.http.codec.ServerSentEvent; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.MimeType; @@ -73,6 +75,12 @@ public void canEncode() { // SPR-15464 assertThat(this.encoder.canEncode(ResolvableType.NONE, null)).isTrue(); + + + assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "cbor", StandardCharsets.UTF_8))).isTrue(); + assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "cbor", StandardCharsets.ISO_8859_1))).isFalse(); } @Test diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java index 2b8100934de3..12f999a27256 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java @@ -88,7 +88,7 @@ public void canDecode() { assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class), new MediaType("application", "json", StandardCharsets.UTF_8))).isTrue(); assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class), - new MediaType("application", "json", StandardCharsets.ISO_8859_1))).isTrue(); + new MediaType("application", "json", StandardCharsets.ISO_8859_1))).isFalse(); } @Test // SPR-15866 @@ -235,21 +235,6 @@ public void decodeNonUtf8Encoding() { null); } - @Test - @SuppressWarnings("unchecked") - public void decodeNonUnicode() { - Flux input = Flux.concat( - stringBuffer("{\"føø\":\"bår\"}", StandardCharsets.ISO_8859_1) - ); - - testDecode(input, ResolvableType.forType(new ParameterizedTypeReference>() { - }), - step -> step.assertNext(o -> assertThat((Map) o).containsEntry("føø", "bår")) - .verifyComplete(), - MediaType.parseMediaType("application/json; charset=iso-8859-1"), - null); - } - @Test @SuppressWarnings("unchecked") public void decodeMonoNonUtf8Encoding() { diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileDecoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileDecoderTests.java index 9e3404ce1ac9..f67772cdae25 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileDecoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileDecoderTests.java @@ -16,6 +16,7 @@ package org.springframework.http.codec.json; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; @@ -27,6 +28,7 @@ import org.springframework.core.ResolvableType; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.testfixture.codec.AbstractDecoderTests; +import org.springframework.http.MediaType; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.MimeType; import org.springframework.web.testfixture.xml.Pojo; @@ -63,6 +65,12 @@ public void canDecode() { assertThat(decoder.canDecode(ResolvableType.forClass(String.class), null)).isFalse(); assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), APPLICATION_JSON)).isFalse(); + + assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "x-jackson-smile", StandardCharsets.UTF_8))).isTrue(); + assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "x-jackson-smile", StandardCharsets.ISO_8859_1))).isFalse(); + } @Override diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileEncoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileEncoderTests.java index 32b15ba0b52f..f12171a052e4 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileEncoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileEncoderTests.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; @@ -32,6 +33,7 @@ import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.testfixture.codec.AbstractEncoderTests; +import org.springframework.http.MediaType; import org.springframework.http.codec.ServerSentEvent; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.MimeType; @@ -68,6 +70,11 @@ public void canEncode() { assertThat(this.encoder.canEncode(pojoType, STREAM_SMILE_MIME_TYPE)).isTrue(); assertThat(this.encoder.canEncode(pojoType, null)).isTrue(); + assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "x-jackson-smile", StandardCharsets.UTF_8))).isTrue(); + assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class), + new MediaType("application", "x-jackson-smile", StandardCharsets.ISO_8859_1))).isFalse(); + // SPR-15464 assertThat(this.encoder.canEncode(ResolvableType.NONE, null)).isTrue(); }