From 3e5986b1f293ce7bbb873824c50b1f0c1fc5509e Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Thu, 4 Jun 2020 17:32:06 +0200 Subject: [PATCH] Respect MediaType charset in Jackson converters Before this commit, AbstractJackson2HttpMessageConverter and subclasses did not check media type encoding in the canRead and canWrite methods. As a result, the converter 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. See: gh-25076 --- .../AbstractJackson2HttpMessageConverter.java | 42 +++++++++++++++++-- ...pingJackson2HttpMessageConverterTests.java | 4 ++ ...ackson2SmileHttpMessageConverterTests.java | 4 ++ ...gJackson2XmlHttpMessageConverterTests.java | 4 ++ 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java index 9230ff025da5..bfb30140b71d 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java @@ -22,7 +22,11 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; +import java.util.EnumSet; +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.stream.Collectors; import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonGenerator; @@ -69,6 +73,8 @@ */ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter { + private static final Map ENCODINGS = jsonEncodings(); + /** * The default charset used by the converter. */ @@ -167,6 +173,14 @@ public boolean canRead(Type type, @Nullable Class contextClass, @Nullable Med return false; } + @Override + protected boolean canRead(@Nullable MediaType mediaType) { + if (!super.canRead(mediaType)) { + return false; + } + return checkEncoding(mediaType); + } + @Override public boolean canWrite(Class clazz, @Nullable MediaType mediaType) { if (!canWrite(mediaType)) { @@ -180,6 +194,14 @@ public boolean canWrite(Class clazz, @Nullable MediaType mediaType) { return false; } + @Override + protected boolean canWrite(@Nullable MediaType mediaType) { + if (!super.canWrite(mediaType)) { + return false; + } + return checkEncoding(mediaType); + } + /** * Determine whether to log the given exception coming from a * {@link ObjectMapper#canDeserialize} / {@link ObjectMapper#canSerialize} check. @@ -211,6 +233,14 @@ else if (logger.isDebugEnabled()) { } } + private boolean checkEncoding(@Nullable MediaType mediaType) { + if (mediaType != null && mediaType.getCharset() != null) { + Charset charset = mediaType.getCharset(); + return ENCODINGS.containsKey(charset.name()); + } + return true; + } + @Override protected Object readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { @@ -333,10 +363,9 @@ protected JavaType getJavaType(Type type, @Nullable Class contextClass) { protected JsonEncoding getJsonEncoding(@Nullable MediaType contentType) { if (contentType != null && contentType.getCharset() != null) { Charset charset = contentType.getCharset(); - for (JsonEncoding encoding : JsonEncoding.values()) { - if (charset.name().equals(encoding.getJavaName())) { - return encoding; - } + JsonEncoding encoding = ENCODINGS.get(charset.name()); + if (encoding != null) { + return encoding; } } return JsonEncoding.UTF8; @@ -359,4 +388,9 @@ protected Long getContentLength(Object object, @Nullable MediaType contentType) return super.getContentLength(object, contentType); } + 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/converter/json/MappingJackson2HttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java index 8b2ef6bf6459..3817d0635826 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java @@ -63,12 +63,16 @@ public class MappingJackson2HttpMessageConverterTests { public void canRead() { assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json"))); assertTrue(converter.canRead(Map.class, new MediaType("application", "json"))); + assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json", StandardCharsets.UTF_8))); + assertFalse(converter.canRead(MyBean.class, new MediaType("application", "json", StandardCharsets.ISO_8859_1))); } @Test public void canWrite() { assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "json"))); assertTrue(converter.canWrite(Map.class, new MediaType("application", "json"))); + assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "json", StandardCharsets.UTF_8))); + assertFalse(converter.canWrite(MyBean.class, new MediaType("application", "json", StandardCharsets.ISO_8859_1))); } @Test // SPR-7905 diff --git a/spring-web/src/test/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverterTests.java index 9860ae36ac97..88d8d7de0fdc 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/smile/MappingJackson2SmileHttpMessageConverterTests.java @@ -49,6 +49,8 @@ public void canRead() { assertTrue(converter.canRead(MyBean.class, new MediaType("application", "x-jackson-smile"))); assertFalse(converter.canRead(MyBean.class, new MediaType("application", "json"))); assertFalse(converter.canRead(MyBean.class, new MediaType("application", "xml"))); + assertTrue(converter.canRead(MyBean.class, new MediaType("application", "x-jackson-smile", StandardCharsets.UTF_8))); + assertFalse(converter.canRead(MyBean.class, new MediaType("application", "x-jackson-smile", StandardCharsets.ISO_8859_1))); } @Test @@ -56,6 +58,8 @@ public void canWrite() { assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "x-jackson-smile"))); assertFalse(converter.canWrite(MyBean.class, new MediaType("application", "json"))); assertFalse(converter.canWrite(MyBean.class, new MediaType("application", "xml"))); + assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "x-jackson-smile", StandardCharsets.UTF_8))); + assertFalse(converter.canWrite(MyBean.class, new MediaType("application", "x-jackson-smile", StandardCharsets.ISO_8859_1))); } @Test diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java index ab03d33fd90e..01e79fdaac4a 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/MappingJackson2XmlHttpMessageConverterTests.java @@ -54,6 +54,8 @@ public void canRead() { assertTrue(converter.canRead(MyBean.class, new MediaType("application", "xml"))); assertTrue(converter.canRead(MyBean.class, new MediaType("text", "xml"))); assertTrue(converter.canRead(MyBean.class, new MediaType("application", "soap+xml"))); + assertTrue(converter.canRead(MyBean.class, new MediaType("text", "xml", StandardCharsets.UTF_8))); + assertFalse(converter.canRead(MyBean.class, new MediaType("text", "xml", StandardCharsets.ISO_8859_1))); } @Test @@ -61,6 +63,8 @@ public void canWrite() { assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "xml"))); assertTrue(converter.canWrite(MyBean.class, new MediaType("text", "xml"))); assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "soap+xml"))); + assertTrue(converter.canWrite(MyBean.class, new MediaType("text", "xml", StandardCharsets.UTF_8))); + assertFalse(converter.canWrite(MyBean.class, new MediaType("text", "xml", StandardCharsets.ISO_8859_1))); } @Test