Skip to content

Commit

Permalink
Respect MimeType charset in Jackson codecs
Browse files Browse the repository at this point in the history
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: spring-projectsgh-25076
  • Loading branch information
poutsma authored and kenny5he committed Jun 21, 2020
1 parent 0daf13e commit e4314b4
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 17 deletions.
Expand Up @@ -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;
Expand Down Expand Up @@ -75,6 +80,9 @@ public abstract class Jackson2CodecSupport {
new MimeType("application", "json"),
new MimeType("application", "*+json")));

private static final Map<String, JsonEncoding> ENCODINGS = jsonEncodings();



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

Expand Down Expand Up @@ -107,7 +115,17 @@ protected List<MimeType> 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) {
Expand Down Expand Up @@ -145,4 +163,10 @@ protected MethodParameter getParameter(ResolvableType type) {
@Nullable
protected abstract <A extends Annotation> A getAnnotation(MethodParameter parameter, Class<A> annotType);

private static Map<String, JsonEncoding> jsonEncodings() {
return EnumSet.allOf(JsonEncoding.class).stream()
.collect(Collectors.toMap(JsonEncoding::getJavaName, Function.identity()));
}


}
Expand Up @@ -16,6 +16,7 @@

package org.springframework.http.codec.cbor;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -235,21 +235,6 @@ public void decodeNonUtf8Encoding() {
null);
}

@Test
@SuppressWarnings("unchecked")
public void decodeNonUnicode() {
Flux<DataBuffer> input = Flux.concat(
stringBuffer("{\"føø\":\"bår\"}", StandardCharsets.ISO_8859_1)
);

testDecode(input, ResolvableType.forType(new ParameterizedTypeReference<Map<String, String>>() {
}),
step -> step.assertNext(o -> assertThat((Map<String, String>) o).containsEntry("føø", "bår"))
.verifyComplete(),
MediaType.parseMediaType("application/json; charset=iso-8859-1"),
null);
}

@Test
@SuppressWarnings("unchecked")
public void decodeMonoNonUtf8Encoding() {
Expand Down
Expand Up @@ -16,6 +16,7 @@

package org.springframework.http.codec.json;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down

0 comments on commit e4314b4

Please sign in to comment.