diff --git a/spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java index f708c4ca9d..489f9bbcc5 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/castor/CastorMarshaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -162,6 +162,11 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing this.encoding = encoding; } + @Override + protected String getDefaultEncoding() { + return this.encoding; + } + /** * Set the locations of the Castor XML mapping files. */ @@ -604,7 +609,7 @@ public class CastorMarshaller extends AbstractMarshaller implements Initializing } @Override - protected final Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) + protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) throws XmlMappingException, IOException { UnmarshalHandler unmarshalHandler = createUnmarshaller().createHandler(); diff --git a/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java index bbf775452e..7edaf0f8b0 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/jaxb/Jaxb2Marshaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -401,6 +401,13 @@ public class Jaxb2Marshaller implements MimeMarshaller, MimeUnmarshaller, Generi this.processExternalEntities = processExternalEntities; } + /** + * @return the configured value for whether XML external entities are allowed. + */ + public boolean isProcessExternalEntities() { + return this.processExternalEntities; + } + @Override public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; diff --git a/spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java index 1faf73e06b..b841c5d688 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/jibx/JibxMarshaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -28,6 +28,7 @@ import javax.xml.stream.XMLEventWriter; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; @@ -148,6 +149,11 @@ public class JibxMarshaller extends AbstractMarshaller implements InitializingBe this.encoding = encoding; } + @Override + protected String getDefaultEncoding() { + return this.encoding; + } + /** * Set the document standalone flag for marshalling. By default, this flag is not present. */ @@ -389,13 +395,12 @@ public class JibxMarshaller extends AbstractMarshaller implements InitializingBe } } - // Unsupported Unmarshalling @Override protected Object unmarshalDomNode(Node node) throws XmlMappingException { try { - return transformAndUnmarshal(new DOMSource(node)); + return transformAndUnmarshal(new DOMSource(node), null); } catch (IOException ex) { throw new UnmarshallingFailureException("JiBX unmarshalling exception", ex); @@ -406,12 +411,15 @@ public class JibxMarshaller extends AbstractMarshaller implements InitializingBe protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) throws XmlMappingException, IOException { - return transformAndUnmarshal(new SAXSource(xmlReader, inputSource)); + return transformAndUnmarshal(new SAXSource(xmlReader, inputSource), inputSource.getEncoding()); } - private Object transformAndUnmarshal(Source source) throws IOException { + private Object transformAndUnmarshal(Source source, String encoding) throws IOException { try { Transformer transformer = this.transformerFactory.newTransformer(); + if (encoding != null) { + transformer.setOutputProperty(OutputKeys.ENCODING, encoding); + } ByteArrayOutputStream os = new ByteArrayOutputStream(); transformer.transform(source, new StreamResult(os)); ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); @@ -419,7 +427,7 @@ public class JibxMarshaller extends AbstractMarshaller implements InitializingBe } catch (TransformerException ex) { throw new MarshallingFailureException( - "Could not transform from [" + ClassUtils.getShortName(source.getClass()) + "]"); + "Could not transform from [" + ClassUtils.getShortName(source.getClass()) + "]", ex); } } diff --git a/spring-oxm/src/main/java/org/springframework/oxm/support/AbstractMarshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/support/AbstractMarshaller.java index cf584efa88..0cf75ad6b7 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/support/AbstractMarshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/support/AbstractMarshaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2014 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. @@ -73,6 +73,33 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller { private final Object documentBuilderFactoryMonitor = new Object(); + private boolean processExternalEntities = false; + + + /** + * Indicates whether external XML entities are processed when unmarshalling. + *

Default is {@code false}, meaning that external entities are not resolved. + * Note that processing of external entities will only be enabled/disabled when the + * {@code Source} passed to {@link #unmarshal(Source)} is a {@link SAXSource} or + * {@link StreamSource}. It has no effect for {@link DOMSource} or {@link StAXSource} + * instances. + */ + public void setProcessExternalEntities(boolean processExternalEntities) { + this.processExternalEntities = processExternalEntities; + } + + /** + * @return the configured value for whether XML external entities are allowed. + */ + public boolean isProcessExternalEntities() { + return this.processExternalEntities; + } + + /** + * @return the default encoding to use for marshalling or unmarshalling from + * a byte stream, or {@code null}. + */ + abstract protected String getDefaultEncoding(); /** * Marshals the object graph with the given root into the provided {@code javax.xml.transform.Result}. @@ -131,7 +158,7 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller { return unmarshalSaxSource((SAXSource) source); } else if (source instanceof StreamSource) { - return unmarshalStreamSource((StreamSource) source); + return unmarshalStreamSourceNoExternalEntitities((StreamSource) source); } else { throw new IllegalArgumentException("Unknown Source type: " + source.getClass()); @@ -173,7 +200,9 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller { * @throws SAXException if thrown by JAXP methods */ protected XMLReader createXmlReader() throws SAXException { - return XMLReaderFactory.createXMLReader(); + XMLReader xmlReader = XMLReaderFactory.createXMLReader(); + xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", isProcessExternalEntities()); + return xmlReader; } @@ -355,9 +384,44 @@ public abstract class AbstractMarshaller implements Marshaller, Unmarshaller { return unmarshalSaxReader(saxSource.getXMLReader(), saxSource.getInputSource()); } + /** + * Template method for handling {@code StreamSource}s with protection against + * the XML External Entity (XXE) processing vulnerability taking into account + * the value of the {@link #setProcessExternalEntities(boolean)} property. + *

+ * The default implementation wraps the StreamSource as a SAXSource and delegates + * to {@link #unmarshalSaxSource(javax.xml.transform.sax.SAXSource)}. + * + * @param streamSource the {@code StreamSource} + * @return the object graph + * @throws IOException if an I/O exception occurs + * @throws XmlMappingException if the given source cannot be mapped to an object + * + * @see XML_External_Entity_(XXE)_Processing + */ + protected Object unmarshalStreamSourceNoExternalEntitities(StreamSource streamSource) + throws XmlMappingException, IOException { + + InputSource inputSource; + if (streamSource.getInputStream() != null) { + inputSource = new InputSource(streamSource.getInputStream()); + inputSource.setEncoding(getDefaultEncoding()); + } + else if (streamSource.getReader() != null) { + inputSource = new InputSource(streamSource.getReader()); + } + else { + inputSource = new InputSource(streamSource.getSystemId()); + } + return unmarshalSaxSource(new SAXSource(inputSource)); + } + /** * Template method for handling {@code StreamSource}s. - *

This implementation defers to {@code unmarshalInputStream} or {@code unmarshalReader}. + *

As of 3.2.8 and 4.0.2 this method is no longer invoked from + * {@link #unmarshal(javax.xml.transform.Source)}. The method invoked instead is + * {@link #unmarshalStreamSourceNoExternalEntitities(javax.xml.transform.stream.StreamSource)}. + * * @param streamSource the {@code StreamSource} * @return the object graph * @throws IOException if an I/O exception occurs diff --git a/spring-oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansMarshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansMarshaller.java index 991d262645..fc519c7f1e 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansMarshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/xmlbeans/XmlBeansMarshaller.java @@ -113,6 +113,10 @@ public class XmlBeansMarshaller extends AbstractMarshaller { return this.validating; } + @Override + protected String getDefaultEncoding() { + return null; + } /** * This implementation returns true if the given class is an implementation of {@link XmlObject}. diff --git a/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java b/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java index a93f3b2db6..ef254b423c 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/xstream/XStreamMarshaller.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -31,6 +31,7 @@ import javax.xml.stream.XMLEventWriter; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.stream.StreamSource; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.ConversionException; @@ -353,6 +354,11 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin this.encoding = encoding; } + @Override + protected String getDefaultEncoding() { + return this.encoding; + } + /** * Set the classes supported by this marshaller. *

If this property is empty (the default), all classes are supported. @@ -482,6 +488,11 @@ public class XStreamMarshaller extends AbstractMarshaller implements Initializin // Unmarshalling + @Override + protected Object unmarshalStreamSourceNoExternalEntitities(StreamSource streamSource) throws XmlMappingException, IOException { + return super.unmarshalStreamSource(streamSource); + } + @Override protected Object unmarshalDomNode(Node node) throws XmlMappingException { HierarchicalStreamReader streamReader; diff --git a/spring-oxm/src/test/java/org/springframework/oxm/castor/CastorUnmarshallerTests.java b/spring-oxm/src/test/java/org/springframework/oxm/castor/CastorUnmarshallerTests.java index 585640893b..8d32deb901 100644 --- a/spring-oxm/src/test/java/org/springframework/oxm/castor/CastorUnmarshallerTests.java +++ b/spring-oxm/src/test/java/org/springframework/oxm/castor/CastorUnmarshallerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -19,6 +19,8 @@ package org.springframework.oxm.castor; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringReader; +import java.util.concurrent.atomic.AtomicReference; +import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamSource; import org.junit.Ignore; @@ -28,6 +30,8 @@ import org.springframework.core.io.ClassPathResource; import org.springframework.oxm.AbstractUnmarshallerTests; import org.springframework.oxm.MarshallingException; import org.springframework.oxm.Unmarshaller; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; @@ -203,4 +207,59 @@ public class CastorUnmarshallerTests extends AbstractUnmarshallerTests { StreamSource source = new StreamSource(new StringReader(xml)); return unmarshaller.unmarshal(source); } + + @Test + public void unmarshalStreamSourceExternalEntities() throws Exception { + + final AtomicReference result = new AtomicReference(); + CastorMarshaller marshaller = new CastorMarshaller() { + @Override + protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) { + result.set(xmlReader); + return null; + } + }; + + // 1. external-general-entities disabled (default) + + marshaller.unmarshal(new StreamSource("1")); + assertNotNull(result.get()); + assertEquals(false, result.get().getFeature("http://xml.org/sax/features/external-general-entities")); + + // 2. external-general-entities disabled (default) + + result.set(null); + marshaller.setProcessExternalEntities(true); + marshaller.unmarshal(new StreamSource("1")); + assertNotNull(result.get()); + assertEquals(true, result.get().getFeature("http://xml.org/sax/features/external-general-entities")); + } + + @Test + public void unmarshalSaxSourceExternalEntities() throws Exception { + + final AtomicReference result = new AtomicReference(); + CastorMarshaller marshaller = new CastorMarshaller() { + @Override + protected Object unmarshalSaxReader(XMLReader xmlReader, InputSource inputSource) { + result.set(xmlReader); + return null; + } + }; + + // 1. external-general-entities disabled (default) + + marshaller.unmarshal(new SAXSource(new InputSource("1"))); + assertNotNull(result.get()); + assertEquals(false, result.get().getFeature("http://xml.org/sax/features/external-general-entities")); + + // 2. external-general-entities disabled (default) + + result.set(null); + marshaller.setProcessExternalEntities(true); + marshaller.unmarshal(new SAXSource(new InputSource("1"))); + assertNotNull(result.get()); + assertEquals(true, result.get().getFeature("http://xml.org/sax/features/external-general-entities")); + } + } diff --git a/spring-oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java b/spring-oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java index bd75b70adb..5e73404bc3 100644 --- a/spring-oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java +++ b/spring-oxm/src/test/java/org/springframework/oxm/jaxb/Jaxb2MarshallerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -31,9 +31,12 @@ import javax.xml.bind.annotation.XmlType; import javax.xml.namespace.QName; import javax.xml.transform.Result; import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXSource; import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -49,6 +52,7 @@ import org.springframework.util.FileCopyUtils; import org.springframework.util.ReflectionUtils; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; import org.xml.sax.Locator; @@ -301,6 +305,75 @@ public class Jaxb2MarshallerTests extends AbstractMarshallerTests { writer.toString(), "test"); } + // SPR-10806 + + @Test + public void unmarshalStreamSourceExternalEntities() throws Exception { + + final javax.xml.bind.Unmarshaller unmarshaller = mock(javax.xml.bind.Unmarshaller.class); + Jaxb2Marshaller marshaller = new Jaxb2Marshaller() { + @Override + protected javax.xml.bind.Unmarshaller createUnmarshaller() { + return unmarshaller; + } + }; + + // 1. external-general-entities disabled (default) + + marshaller.unmarshal(new StreamSource("1")); + ArgumentCaptor sourceCaptor = ArgumentCaptor.forClass(SAXSource.class); + verify(unmarshaller).unmarshal(sourceCaptor.capture()); + + SAXSource result = sourceCaptor.getValue(); + assertEquals(false, result.getXMLReader().getFeature("http://xml.org/sax/features/external-general-entities")); + + // 2. external-general-entities enabled + + reset(unmarshaller); + marshaller.setProcessExternalEntities(true); + + marshaller.unmarshal(new StreamSource("1")); + verify(unmarshaller).unmarshal(sourceCaptor.capture()); + + result = sourceCaptor.getValue(); + assertEquals(true, result.getXMLReader().getFeature("http://xml.org/sax/features/external-general-entities")); + } + + // SPR-10806 + + @Test + public void unmarshalSaxSourceExternalEntities() throws Exception { + + final javax.xml.bind.Unmarshaller unmarshaller = mock(javax.xml.bind.Unmarshaller.class); + Jaxb2Marshaller marshaller = new Jaxb2Marshaller() { + @Override + protected javax.xml.bind.Unmarshaller createUnmarshaller() { + return unmarshaller; + } + }; + + // 1. external-general-entities disabled (default) + + marshaller.unmarshal(new SAXSource(new InputSource("1"))); + ArgumentCaptor sourceCaptor = ArgumentCaptor.forClass(SAXSource.class); + verify(unmarshaller).unmarshal(sourceCaptor.capture()); + + SAXSource result = sourceCaptor.getValue(); + assertEquals(false, result.getXMLReader().getFeature("http://xml.org/sax/features/external-general-entities")); + + // 2. external-general-entities enabled + + reset(unmarshaller); + marshaller.setProcessExternalEntities(true); + + marshaller.unmarshal(new SAXSource(new InputSource("1"))); + verify(unmarshaller).unmarshal(sourceCaptor.capture()); + + result = sourceCaptor.getValue(); + assertEquals(true, result.getXMLReader().getFeature("http://xml.org/sax/features/external-general-entities")); + } + + @XmlRootElement @SuppressWarnings("unused") public static class DummyRootElement { diff --git a/spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxUnmarshallerTests.java b/spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxUnmarshallerTests.java index 505befb1c5..65c9344cb7 100644 --- a/spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxUnmarshallerTests.java +++ b/spring-oxm/src/test/java/org/springframework/oxm/jibx/JibxUnmarshallerTests.java @@ -25,7 +25,9 @@ import org.junit.Test; import org.springframework.oxm.AbstractUnmarshallerTests; import org.springframework.oxm.Unmarshaller; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + /** * @author Arjen Poutsma diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java index 89d5bd0bd2..132bce6ef4 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.java @@ -28,6 +28,8 @@ import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; import javax.xml.transform.Result; import javax.xml.transform.Source; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamSource; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.http.HttpHeaders; @@ -36,6 +38,10 @@ import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.util.ClassUtils; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; /** * Implementation of {@link org.springframework.http.converter.HttpMessageConverter HttpMessageConverter} that can read @@ -49,6 +55,18 @@ import org.springframework.util.ClassUtils; */ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessageConverter { + private boolean processExternalEntities = false; + + + /** + * Indicates whether external XML entities are processed when converting to a Source. + *

Default is {@code false}, meaning that external entities are not resolved. + */ + public void setProcessExternalEntities(boolean processExternalEntities) { + this.processExternalEntities = processExternalEntities; + } + + @Override public boolean canRead(Class clazz, MediaType mediaType) { return (clazz.isAnnotationPresent(XmlRootElement.class) || clazz.isAnnotationPresent(XmlType.class)) && @@ -69,6 +87,7 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa @Override protected Object readFromSource(Class clazz, HttpHeaders headers, Source source) throws IOException { try { + source = processSource(source); Unmarshaller unmarshaller = createUnmarshaller(clazz); if (clazz.isAnnotationPresent(XmlRootElement.class)) { return unmarshaller.unmarshal(source); @@ -87,6 +106,26 @@ public class Jaxb2RootElementHttpMessageConverter extends AbstractJaxb2HttpMessa } } + protected Source processSource(Source source) { + if (source instanceof StreamSource) { + StreamSource streamSource = (StreamSource) source; + InputSource inputSource = new InputSource(streamSource.getInputStream()); + try { + XMLReader xmlReader = XMLReaderFactory.createXMLReader(); + String featureName = "http://xml.org/sax/features/external-general-entities"; + xmlReader.setFeature(featureName, this.processExternalEntities); + return new SAXSource(xmlReader, inputSource); + } + catch (SAXException ex) { + logger.warn("Processing of external entities could not be disabled", ex); + return source; + } + } + else { + return source; + } + } + @Override protected void writeToResult(Object o, HttpHeaders headers, Result result) throws IOException { try { diff --git a/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java index 5da79e1cd5..04b9935451 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java @@ -90,6 +90,12 @@ public class SourceHttpMessageConverter extends AbstractHttpMe this.processExternalEntities = processExternalEntities; } + /** + * @return the configured value for whether XML external entities are allowed. + */ + public boolean isProcessExternalEntities() { + return this.processExternalEntities; + } @Override public boolean supports(Class clazz) { diff --git a/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTest.java b/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTest.java index 2f12bc988b..c7b5c4b115 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTest.java +++ b/spring-web/src/test/java/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverterTest.java @@ -32,6 +32,8 @@ import org.junit.Test; import org.springframework.aop.framework.AdvisedSupport; import org.springframework.aop.framework.AopProxy; import org.springframework.aop.framework.DefaultAopProxyFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; import org.springframework.http.MediaType; import org.springframework.http.MockHttpInputMessage; import org.springframework.http.MockHttpOutputMessage; @@ -95,6 +97,33 @@ public class Jaxb2RootElementHttpMessageConverterTest { assertEquals("Invalid result", "Hello World", result.s); } + @Test + public void readXmlRootElementExternalEntityDisabled() throws Exception { + Resource external = new ClassPathResource("external.txt", getClass()); + String content = "\n" + + " ]>" + + " &ext;"; + MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); + RootElement rootElement = (RootElement) converter.read(RootElement.class, inputMessage); + + assertEquals("", rootElement.external); + } + + @Test + public void readXmlRootElementExternalEntityEnabled() throws Exception { + Resource external = new ClassPathResource("external.txt", getClass()); + String content = "\n" + + " ]>" + + " &ext;"; + MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); + this.converter.setProcessExternalEntities(true); + RootElement rootElement = (RootElement) converter.read(RootElement.class, inputMessage); + + assertEquals("Foo Bar", rootElement.external); + } + @Test public void writeXmlRootElement() throws Exception { MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); @@ -121,6 +150,9 @@ public class Jaxb2RootElementHttpMessageConverterTest { @XmlElement public Type type = new Type(); + @XmlElement(required=false) + public String external; + } @XmlType