Add processExternalEntities support to OXM
Update OXM AbstractMarshaller to support processing of external XML entities. By default external entities will not be processed. Issue: SPR-11376
This commit is contained in:
committed by
Phillip Webb
parent
09c57203bb
commit
fb0683c066
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
* <p>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.
|
||||
* <p>
|
||||
* 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 <a href="https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing">XML_External_Entity_(XXE)_Processing</a>
|
||||
*/
|
||||
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.
|
||||
* <p>This implementation defers to {@code unmarshalInputStream} or {@code unmarshalReader}.
|
||||
* <p>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
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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.
|
||||
* <p>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;
|
||||
|
||||
@@ -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<XMLReader> result = new AtomicReference<XMLReader>();
|
||||
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<XMLReader> result = new AtomicReference<XMLReader>();
|
||||
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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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(), "<airplane><name>test</name></airplane>");
|
||||
}
|
||||
|
||||
// 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<SAXSource> 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<SAXSource> 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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Object> {
|
||||
|
||||
private boolean processExternalEntities = false;
|
||||
|
||||
|
||||
/**
|
||||
* Indicates whether external XML entities are processed when converting to a Source.
|
||||
* <p>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 {
|
||||
|
||||
@@ -90,6 +90,12 @@ public class SourceHttpMessageConverter<T extends Source> 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) {
|
||||
|
||||
@@ -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 = "<!DOCTYPE root [" +
|
||||
" <!ELEMENT external ANY >\n" +
|
||||
" <!ENTITY ext SYSTEM \"" + external.getURI() + "\" >]>" +
|
||||
" <rootElement><external>&ext;</external></rootElement>";
|
||||
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 = "<!DOCTYPE root [" +
|
||||
" <!ELEMENT external ANY >\n" +
|
||||
" <!ENTITY ext SYSTEM \"" + external.getURI() + "\" >]>" +
|
||||
" <rootElement><external>&ext;</external></rootElement>";
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user