Skip to content

Commit 8476294

Browse files
committed
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
1 parent eb0aae0 commit 8476294

File tree

7 files changed

+75
-12
lines changed

7 files changed

+75
-12
lines changed

spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,18 @@
1818

1919
import java.lang.annotation.Annotation;
2020
import java.lang.reflect.Type;
21+
import java.nio.charset.Charset;
2122
import java.util.Arrays;
2223
import java.util.Collections;
24+
import java.util.EnumSet;
2325
import java.util.HashMap;
2426
import java.util.List;
2527
import java.util.Map;
28+
import java.util.function.Function;
29+
import java.util.stream.Collectors;
2630

2731
import com.fasterxml.jackson.annotation.JsonView;
32+
import com.fasterxml.jackson.core.JsonEncoding;
2833
import com.fasterxml.jackson.databind.JavaType;
2934
import com.fasterxml.jackson.databind.ObjectMapper;
3035
import com.fasterxml.jackson.databind.type.TypeFactory;
@@ -75,6 +80,9 @@ public abstract class Jackson2CodecSupport {
7580
new MimeType("application", "json"),
7681
new MimeType("application", "*+json")));
7782

83+
private static final Map<String, JsonEncoding> ENCODINGS = jsonEncodings();
84+
85+
7886

7987
protected final Log logger = HttpLogging.forLogName(getClass());
8088

@@ -107,7 +115,17 @@ protected List<MimeType> getMimeTypes() {
107115

108116

109117
protected boolean supportsMimeType(@Nullable MimeType mimeType) {
110-
return (mimeType == null || this.mimeTypes.stream().anyMatch(m -> m.isCompatibleWith(mimeType)));
118+
if (mimeType == null) {
119+
return true;
120+
}
121+
else if (this.mimeTypes.stream().noneMatch(m -> m.isCompatibleWith(mimeType))) {
122+
return false;
123+
}
124+
else if (mimeType.getCharset() != null) {
125+
Charset charset = mimeType.getCharset();
126+
return ENCODINGS.containsKey(charset.name());
127+
}
128+
return true;
111129
}
112130

113131
protected JavaType getJavaType(Type type, @Nullable Class<?> contextClass) {
@@ -145,4 +163,10 @@ protected MethodParameter getParameter(ResolvableType type) {
145163
@Nullable
146164
protected abstract <A extends Annotation> A getAnnotation(MethodParameter parameter, Class<A> annotType);
147165

166+
private static Map<String, JsonEncoding> jsonEncodings() {
167+
return EnumSet.allOf(JsonEncoding.class).stream()
168+
.collect(Collectors.toMap(JsonEncoding::getJavaName, Function.identity()));
169+
}
170+
171+
148172
}

spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborDecoderTests.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.http.codec.cbor;
1818

19+
import java.nio.charset.StandardCharsets;
1920
import java.util.Arrays;
2021
import java.util.List;
2122

@@ -27,13 +28,13 @@
2728
import org.springframework.core.ResolvableType;
2829
import org.springframework.core.io.buffer.DataBuffer;
2930
import org.springframework.core.testfixture.codec.AbstractDecoderTests;
31+
import org.springframework.http.MediaType;
3032
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
3133
import org.springframework.util.MimeType;
3234
import org.springframework.web.testfixture.xml.Pojo;
3335

3436
import static org.assertj.core.api.Assertions.assertThat;
3537
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
36-
import static org.springframework.core.ResolvableType.forClass;
3738
import static org.springframework.http.MediaType.APPLICATION_JSON;
3839

3940
/**
@@ -58,11 +59,16 @@ public Jackson2CborDecoderTests() {
5859
@Override
5960
@Test
6061
public void canDecode() {
61-
assertThat(decoder.canDecode(forClass(Pojo.class), CBOR_MIME_TYPE)).isTrue();
62-
assertThat(decoder.canDecode(forClass(Pojo.class), null)).isTrue();
62+
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), CBOR_MIME_TYPE)).isTrue();
63+
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), null)).isTrue();
6364

64-
assertThat(decoder.canDecode(forClass(String.class), null)).isFalse();
65-
assertThat(decoder.canDecode(forClass(Pojo.class), APPLICATION_JSON)).isFalse();
65+
assertThat(decoder.canDecode(ResolvableType.forClass(String.class), null)).isFalse();
66+
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), APPLICATION_JSON)).isFalse();
67+
68+
assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class),
69+
new MediaType("application", "cbor", StandardCharsets.UTF_8))).isTrue();
70+
assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class),
71+
new MediaType("application", "cbor", StandardCharsets.ISO_8859_1))).isFalse();
6672
}
6773

6874
@Override

spring-web/src/test/java/org/springframework/http/codec/cbor/Jackson2CborEncoderTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.IOException;
2020
import java.io.UncheckedIOException;
21+
import java.nio.charset.StandardCharsets;
2122
import java.util.function.Consumer;
2223

2324
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -28,6 +29,7 @@
2829
import org.springframework.core.io.buffer.DataBuffer;
2930
import org.springframework.core.testfixture.io.buffer.AbstractLeakCheckingTests;
3031
import org.springframework.core.testfixture.io.buffer.DataBufferTestUtils;
32+
import org.springframework.http.MediaType;
3133
import org.springframework.http.codec.ServerSentEvent;
3234
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
3335
import org.springframework.util.MimeType;
@@ -73,6 +75,12 @@ public void canEncode() {
7375

7476
// SPR-15464
7577
assertThat(this.encoder.canEncode(ResolvableType.NONE, null)).isTrue();
78+
79+
80+
assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class),
81+
new MediaType("application", "cbor", StandardCharsets.UTF_8))).isTrue();
82+
assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class),
83+
new MediaType("application", "cbor", StandardCharsets.ISO_8859_1))).isFalse();
7684
}
7785

7886
@Test

spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ public void canDecode() {
8585

8686
assertThat(decoder.canDecode(forClass(String.class), null)).isFalse();
8787
assertThat(decoder.canDecode(forClass(Pojo.class), APPLICATION_XML)).isFalse();
88+
assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class),
89+
new MediaType("application", "json", StandardCharsets.UTF_8))).isTrue();
90+
assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class),
91+
new MediaType("application", "json", StandardCharsets.ISO_8859_1))).isFalse();
8892
}
8993

9094
@Test // SPR-15866

spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,18 @@ public void canEncode() {
6969
assertThat(this.encoder.canEncode(pojoType, APPLICATION_STREAM_JSON)).isTrue();
7070
assertThat(this.encoder.canEncode(pojoType, null)).isTrue();
7171

72+
assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class),
73+
new MediaType("application", "json", StandardCharsets.UTF_8))).isTrue();
74+
assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class),
75+
new MediaType("application", "json", StandardCharsets.ISO_8859_1))).isFalse();
76+
7277
// SPR-15464
7378
assertThat(this.encoder.canEncode(ResolvableType.NONE, null)).isTrue();
7479

7580
// SPR-15910
7681
assertThat(this.encoder.canEncode(ResolvableType.forClass(Object.class), APPLICATION_OCTET_STREAM)).isFalse();
82+
83+
7784
}
7885

7986
@Override

spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileDecoderTests.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.http.codec.json;
1818

19+
import java.nio.charset.StandardCharsets;
1920
import java.util.Arrays;
2021
import java.util.List;
2122

@@ -27,12 +28,12 @@
2728
import org.springframework.core.ResolvableType;
2829
import org.springframework.core.io.buffer.DataBuffer;
2930
import org.springframework.core.testfixture.codec.AbstractDecoderTests;
31+
import org.springframework.http.MediaType;
3032
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
3133
import org.springframework.util.MimeType;
3234
import org.springframework.web.testfixture.xml.Pojo;
3335

3436
import static org.assertj.core.api.Assertions.assertThat;
35-
import static org.springframework.core.ResolvableType.forClass;
3637
import static org.springframework.http.MediaType.APPLICATION_JSON;
3738

3839
/**
@@ -58,12 +59,18 @@ public Jackson2SmileDecoderTests() {
5859
@Override
5960
@Test
6061
public void canDecode() {
61-
assertThat(decoder.canDecode(forClass(Pojo.class), SMILE_MIME_TYPE)).isTrue();
62-
assertThat(decoder.canDecode(forClass(Pojo.class), STREAM_SMILE_MIME_TYPE)).isTrue();
63-
assertThat(decoder.canDecode(forClass(Pojo.class), null)).isTrue();
62+
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), SMILE_MIME_TYPE)).isTrue();
63+
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), STREAM_SMILE_MIME_TYPE)).isTrue();
64+
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), null)).isTrue();
65+
66+
assertThat(decoder.canDecode(ResolvableType.forClass(String.class), null)).isFalse();
67+
assertThat(decoder.canDecode(ResolvableType.forClass(Pojo.class), APPLICATION_JSON)).isFalse();
68+
69+
assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class),
70+
new MediaType("application", "x-jackson-smile", StandardCharsets.UTF_8))).isTrue();
71+
assertThat(this.decoder.canDecode(ResolvableType.forClass(Pojo.class),
72+
new MediaType("application", "x-jackson-smile", StandardCharsets.ISO_8859_1))).isFalse();
6473

65-
assertThat(decoder.canDecode(forClass(String.class), null)).isFalse();
66-
assertThat(decoder.canDecode(forClass(Pojo.class), APPLICATION_JSON)).isFalse();
6774
}
6875

6976
@Override

spring-web/src/test/java/org/springframework/http/codec/json/Jackson2SmileEncoderTests.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.IOException;
2020
import java.io.UncheckedIOException;
21+
import java.nio.charset.StandardCharsets;
2122
import java.util.Arrays;
2223
import java.util.List;
2324

@@ -32,6 +33,7 @@
3233
import org.springframework.core.io.buffer.DataBuffer;
3334
import org.springframework.core.io.buffer.DataBufferUtils;
3435
import org.springframework.core.testfixture.codec.AbstractEncoderTests;
36+
import org.springframework.http.MediaType;
3537
import org.springframework.http.codec.ServerSentEvent;
3638
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
3739
import org.springframework.util.MimeType;
@@ -68,6 +70,11 @@ public void canEncode() {
6870
assertThat(this.encoder.canEncode(pojoType, STREAM_SMILE_MIME_TYPE)).isTrue();
6971
assertThat(this.encoder.canEncode(pojoType, null)).isTrue();
7072

73+
assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class),
74+
new MediaType("application", "x-jackson-smile", StandardCharsets.UTF_8))).isTrue();
75+
assertThat(this.encoder.canEncode(ResolvableType.forClass(Pojo.class),
76+
new MediaType("application", "x-jackson-smile", StandardCharsets.ISO_8859_1))).isFalse();
77+
7178
// SPR-15464
7279
assertThat(this.encoder.canEncode(ResolvableType.NONE, null)).isTrue();
7380
}

0 commit comments

Comments
 (0)