Merge branch '6.2.x'
This commit is contained in:
		
						commit
						1ea8a91b85
					
				|  | @ -16,6 +16,7 @@ | ||||||
| 
 | 
 | ||||||
| package org.springframework.http.converter.xml; | package org.springframework.http.converter.xml; | ||||||
| 
 | 
 | ||||||
|  | import java.nio.charset.Charset; | ||||||
| import java.util.concurrent.ConcurrentHashMap; | import java.util.concurrent.ConcurrentHashMap; | ||||||
| import java.util.concurrent.ConcurrentMap; | import java.util.concurrent.ConcurrentMap; | ||||||
| 
 | 
 | ||||||
|  | @ -23,7 +24,10 @@ import jakarta.xml.bind.JAXBContext; | ||||||
| import jakarta.xml.bind.JAXBException; | import jakarta.xml.bind.JAXBException; | ||||||
| import jakarta.xml.bind.Marshaller; | import jakarta.xml.bind.Marshaller; | ||||||
| import jakarta.xml.bind.Unmarshaller; | 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; | 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.io.IOException; | ||||||
| import java.lang.reflect.ParameterizedType; | import java.lang.reflect.ParameterizedType; | ||||||
| import java.lang.reflect.Type; | import java.lang.reflect.Type; | ||||||
|  | import java.nio.charset.Charset; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
| import java.util.LinkedHashSet; | import java.util.LinkedHashSet; | ||||||
|  | @ -148,7 +149,10 @@ public class Jaxb2CollectionHttpMessageConverter<T extends Collection> | ||||||
| 
 | 
 | ||||||
| 		try { | 		try { | ||||||
| 			Unmarshaller unmarshaller = createUnmarshaller(elementClass); | 			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); | 			int event = moveToFirstChildOfRootElement(streamReader); | ||||||
| 
 | 
 | ||||||
| 			while (event != XMLStreamReader.END_DOCUMENT) { | 			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"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with the License. |  * you may not use this file except in compliance with the License. | ||||||
|  | @ -17,6 +17,7 @@ | ||||||
| package org.springframework.http.converter.xml; | package org.springframework.http.converter.xml; | ||||||
| 
 | 
 | ||||||
| import java.io.StringReader; | import java.io.StringReader; | ||||||
|  | import java.nio.charset.Charset; | ||||||
| 
 | 
 | ||||||
| import javax.xml.parsers.ParserConfigurationException; | import javax.xml.parsers.ParserConfigurationException; | ||||||
| import javax.xml.parsers.SAXParser; | import javax.xml.parsers.SAXParser; | ||||||
|  | @ -134,7 +135,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa | ||||||
| 	@Override | 	@Override | ||||||
| 	protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source source) throws Exception { | 	protected Object readFromSource(Class<?> clazz, HttpHeaders headers, Source source) throws Exception { | ||||||
| 		try { | 		try { | ||||||
| 			source = processSource(source); | 			source = processSource(source, detectCharset(headers)); | ||||||
| 			Unmarshaller unmarshaller = createUnmarshaller(clazz); | 			Unmarshaller unmarshaller = createUnmarshaller(clazz); | ||||||
| 			if (clazz.isAnnotationPresent(XmlRootElement.class)) { | 			if (clazz.isAnnotationPresent(XmlRootElement.class)) { | ||||||
| 				return unmarshaller.unmarshal(source); | 				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) { | 		if (source instanceof StreamSource streamSource) { | ||||||
| 			InputSource inputSource = new InputSource(streamSource.getInputStream()); | 			InputSource inputSource = new InputSource(streamSource.getInputStream()); | ||||||
|  | 			if (charset != null) { | ||||||
|  | 				inputSource.setEncoding(charset.name()); | ||||||
|  | 			} | ||||||
| 			try { | 			try { | ||||||
| 				// By default, Spring will prevent the processing of external entities. | 				// By default, Spring will prevent the processing of external entities. | ||||||
| 				// This is a mitigation against XXE attacks. | 				// 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"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with 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.ParameterizedTypeReference; | ||||||
| import org.springframework.core.io.ClassPathResource; | import org.springframework.core.io.ClassPathResource; | ||||||
| import org.springframework.core.io.Resource; | import org.springframework.core.io.Resource; | ||||||
|  | import org.springframework.http.MediaType; | ||||||
| import org.springframework.http.converter.HttpMessageNotReadableException; | import org.springframework.http.converter.HttpMessageNotReadableException; | ||||||
| import org.springframework.web.testfixture.http.MockHttpInputMessage; | import org.springframework.web.testfixture.http.MockHttpInputMessage; | ||||||
| 
 | 
 | ||||||
|  | @ -204,6 +205,18 @@ class Jaxb2CollectionHttpMessageConverterTests { | ||||||
| 				.withMessageContaining("\"lol9\""); | 				.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 | 	@XmlRootElement | ||||||
| 	public static class RootElement { | 	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"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with the License. |  * you may not use this file except in compliance with the License. | ||||||
|  | @ -180,6 +180,15 @@ class Jaxb2RootElementHttpMessageConverterTests { | ||||||
| 				.withMessageContaining("DOCTYPE"); | 				.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 | 	@Test | ||||||
| 	void writeXmlRootElement() throws Exception { | 	void writeXmlRootElement() throws Exception { | ||||||
| 		MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); | 		MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue