Merge branch '6.2.x'
This commit is contained in:
		
						commit
						1ea8a91b85
					
				|  | @ -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; | ||||
| 
 | ||||
|  | @ -23,7 +24,10 @@ import jakarta.xml.bind.JAXBContext; | |||
| import jakarta.xml.bind.JAXBException; | ||||
| import jakarta.xml.bind.Marshaller; | ||||
| import jakarta.xml.bind.Unmarshaller; | ||||
| import org.jspecify.annotations.Nullable; | ||||
| 
 | ||||
| import org.springframework.http.HttpHeaders; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.http.converter.HttpMessageConversionException; | ||||
| 
 | ||||
| /** | ||||
|  | @ -116,4 +120,19 @@ 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 | ||||
| 	 */ | ||||
| 	protected @Nullable 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; | ||||
|  | @ -134,7 +135,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); | ||||
|  | @ -159,9 +160,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. | ||||
|  | @ -35,6 +35,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.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