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