Use Content-Type charset in JAXB message converters
Backport Bot / build (push) Has been cancelled
Details
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Has been cancelled
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:ubuntu-latest name:Linux]) (push) Has been cancelled
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:ubuntu-latest name:Linux]) (push) Has been cancelled
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:ubuntu-latest name:Linux]) (push) Has been cancelled
Details
Deploy Docs / Dispatch docs deployment (push) Has been cancelled
Details
Build and Deploy Snapshot / Verify (push) Has been cancelled
Details
Backport Bot / build (push) Has been cancelled
Details
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Has been cancelled
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:ubuntu-latest name:Linux]) (push) Has been cancelled
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:21], map[id:ubuntu-latest name:Linux]) (push) Has been cancelled
Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:ubuntu-latest name:Linux]) (push) Has been cancelled
Details
Deploy Docs / Dispatch docs deployment (push) Has been cancelled
Details
Build and Deploy Snapshot / Verify (push) Has been cancelled
Details
Prior to this commit, the JAXB message converters would only rely on the encoding declaration inside the XML document for reading the document. This would then use the default UTF-8 encoding, even if the HTTP message has the `"application/xml;charset=iso-8859-1"` Content-Type. This commit ensures that both `Jaxb2CollectionHttpMessageConverter` and `Jaxb2RootElementHttpMessageConverter` use the encoding declared in the HTTP Content-Type, if present. Fixes gh-34745
This commit is contained in:
parent
fdab8fabd2
commit
2af0323c21
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.http.converter.xml;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
|
@ -24,7 +25,10 @@ import jakarta.xml.bind.JAXBException;
|
|||
import jakarta.xml.bind.Marshaller;
|
||||
import jakarta.xml.bind.Unmarshaller;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConversionException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverters}
|
||||
|
@ -116,4 +120,20 @@ public abstract class AbstractJaxb2HttpMessageConverter<T> extends AbstractXmlHt
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the charset from the given {@link HttpHeaders#getContentType()}.
|
||||
* @param httpHeaders the current HTTP headers
|
||||
* @return the charset defined in the content type header, or {@code null} if not found
|
||||
*/
|
||||
@Nullable
|
||||
protected Charset detectCharset(HttpHeaders httpHeaders) {
|
||||
MediaType contentType = httpHeaders.getContentType();
|
||||
if (contentType != null && contentType.getCharset() != null) {
|
||||
return contentType.getCharset();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.springframework.http.converter.xml;
|
|||
import java.io.IOException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
|
@ -148,7 +149,10 @@ public class Jaxb2CollectionHttpMessageConverter<T extends Collection>
|
|||
|
||||
try {
|
||||
Unmarshaller unmarshaller = createUnmarshaller(elementClass);
|
||||
XMLStreamReader streamReader = this.inputFactory.createXMLStreamReader(inputMessage.getBody());
|
||||
Charset detectedCharset = detectCharset(inputMessage.getHeaders());
|
||||
XMLStreamReader streamReader = (detectedCharset != null) ?
|
||||
this.inputFactory.createXMLStreamReader(inputMessage.getBody(), detectedCharset.name()) :
|
||||
this.inputFactory.createXMLStreamReader(inputMessage.getBody());
|
||||
int event = moveToFirstChildOfRootElement(streamReader);
|
||||
|
||||
while (event != XMLStreamReader.END_DOCUMENT) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.http.converter.xml;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParser;
|
||||
|
@ -135,7 +136,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
|
|||
@Override
|
||||
protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source source) throws Exception {
|
||||
try {
|
||||
source = processSource(source);
|
||||
source = processSource(source, detectCharset(headers));
|
||||
Unmarshaller unmarshaller = createUnmarshaller(clazz);
|
||||
if (clazz.isAnnotationPresent(XmlRootElement.class)) {
|
||||
return unmarshaller.unmarshal(source);
|
||||
|
@ -160,9 +161,12 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa
|
|||
}
|
||||
}
|
||||
|
||||
protected Source processSource(Source source) {
|
||||
protected Source processSource(Source source, @Nullable Charset charset) {
|
||||
if (source instanceof StreamSource streamSource) {
|
||||
InputSource inputSource = new InputSource(streamSource.getInputStream());
|
||||
if (charset != null) {
|
||||
inputSource.setEncoding(charset.name());
|
||||
}
|
||||
try {
|
||||
// By default, Spring will prevent the processing of external entities.
|
||||
// This is a mitigation against XXE attacks.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -34,6 +34,7 @@ import org.junit.jupiter.api.Test;
|
|||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.testfixture.http.MockHttpInputMessage;
|
||||
|
@ -204,6 +205,18 @@ class Jaxb2CollectionHttpMessageConverterTests {
|
|||
.withMessageContaining("\"lol9\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void readXmlRootElementListHeaderCharset() throws Exception {
|
||||
String content = "<list><rootElement><type s=\"Hellø Wørld\"/></rootElement></list>";
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes(StandardCharsets.ISO_8859_1));
|
||||
inputMessage.getHeaders().setContentType(MediaType.parseMediaType("application/xml;charset=iso-8859-1"));
|
||||
List<RootElement> result = (List<RootElement>) converter.read(rootElementListType, null, inputMessage);
|
||||
|
||||
assertThat(result).as("Invalid result").hasSize(1);
|
||||
assertThat(result.get(0).type.s).as("Invalid result").isEqualTo("Hellø Wørld");
|
||||
}
|
||||
|
||||
|
||||
@XmlRootElement
|
||||
public static class RootElement {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
* Copyright 2002-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -180,6 +180,15 @@ class Jaxb2RootElementHttpMessageConverterTests {
|
|||
.withMessageContaining("DOCTYPE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void readXmlRootElementHeaderCharset() throws Exception {
|
||||
byte[] body = "<rootElement><type s=\"Hellø Wørld\"/></rootElement>".getBytes(StandardCharsets.ISO_8859_1);
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body);
|
||||
inputMessage.getHeaders().setContentType(MediaType.parseMediaType("application/xml;charset=iso-8859-1"));
|
||||
RootElement result = (RootElement) converter.read(RootElement.class, inputMessage);
|
||||
assertThat(result.type.s).as("Invalid result").isEqualTo("Hellø Wørld");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeXmlRootElement() throws Exception {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
|
|
Loading…
Reference in New Issue