Skip to content

Commit 2ae6a6a

Browse files
committed
Disable ext entities in SourceHttpMessageConverter
This change disables the processing of external entities in SourceHttpMessageConverter by default and provides an option to enable it if required.
1 parent c5fcf19 commit 2ae6a6a

File tree

3 files changed

+204
-50
lines changed

3 files changed

+204
-50
lines changed

spring-web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java

Lines changed: 115 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2013 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,73 +17,144 @@
1717
package org.springframework.http.converter.xml;
1818

1919
import java.io.ByteArrayInputStream;
20-
import java.io.ByteArrayOutputStream;
2120
import java.io.IOException;
21+
import java.io.InputStream;
2222
import java.io.OutputStream;
23+
import javax.xml.parsers.DocumentBuilder;
24+
import javax.xml.parsers.DocumentBuilderFactory;
25+
import javax.xml.parsers.ParserConfigurationException;
26+
import javax.xml.stream.XMLInputFactory;
27+
import javax.xml.stream.XMLStreamException;
28+
import javax.xml.stream.XMLStreamReader;
2329
import javax.xml.transform.Result;
2430
import javax.xml.transform.Source;
2531
import javax.xml.transform.TransformerException;
26-
import javax.xml.transform.dom.DOMResult;
32+
import javax.xml.transform.TransformerFactory;
2733
import javax.xml.transform.dom.DOMSource;
2834
import javax.xml.transform.sax.SAXSource;
35+
import javax.xml.transform.stax.StAXSource;
2936
import javax.xml.transform.stream.StreamResult;
3037
import javax.xml.transform.stream.StreamSource;
3138

39+
import org.w3c.dom.Document;
3240
import org.xml.sax.InputSource;
41+
import org.xml.sax.SAXException;
42+
import org.xml.sax.XMLReader;
43+
import org.xml.sax.helpers.XMLReaderFactory;
3344

34-
import org.springframework.http.HttpHeaders;
45+
import org.springframework.http.HttpInputMessage;
46+
import org.springframework.http.HttpOutputMessage;
3547
import org.springframework.http.MediaType;
48+
import org.springframework.http.converter.AbstractHttpMessageConverter;
3649
import org.springframework.http.converter.HttpMessageConversionException;
3750
import org.springframework.http.converter.HttpMessageNotReadableException;
3851
import org.springframework.http.converter.HttpMessageNotWritableException;
52+
import org.springframework.util.StreamUtils;
3953

4054
/**
41-
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter} that can read and write {@link
42-
* Source} objects.
55+
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter}
56+
* that can read and write {@link Source} objects.
4357
*
4458
* @author Arjen Poutsma
4559
* @since 3.0
4660
*/
47-
public class SourceHttpMessageConverter<T extends Source> extends AbstractXmlHttpMessageConverter<T> {
61+
public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMessageConverter<T> {
62+
63+
private final TransformerFactory transformerFactory = TransformerFactory.newInstance();
64+
65+
private boolean processExternalEntities = false;
66+
67+
/**
68+
* Sets the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes}
69+
* to {@code text/xml} and {@code application/xml}, and {@code application/*-xml}.
70+
*/
71+
public SourceHttpMessageConverter() {
72+
super(MediaType.APPLICATION_XML, MediaType.TEXT_XML, new MediaType("application", "*+xml"));
73+
}
74+
75+
76+
/**
77+
* Indicates whether external XML entities are processed when converting
78+
* to a Source.
79+
* <p>Default is {@code false}, meaning that external entities are not resolved.
80+
*/
81+
public void setProcessExternalEntities(boolean processExternalEntities) {
82+
this.processExternalEntities = processExternalEntities;
83+
}
4884

4985
@Override
5086
public boolean supports(Class<?> clazz) {
51-
return DOMSource.class.equals(clazz) || SAXSource.class.equals(clazz) || StreamSource.class.equals(clazz) ||
52-
Source.class.equals(clazz);
87+
return DOMSource.class.equals(clazz) || SAXSource.class.equals(clazz)
88+
|| StreamSource.class.equals(clazz) || Source.class.equals(clazz);
5389
}
5490

5591
@Override
56-
@SuppressWarnings("unchecked")
57-
protected T readFromSource(Class clazz, HttpHeaders headers, Source source) throws IOException {
92+
protected T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
93+
throws IOException, HttpMessageNotReadableException {
94+
95+
InputStream body = inputMessage.getBody();
96+
if (DOMSource.class.equals(clazz)) {
97+
return (T) readDOMSource(body);
98+
}
99+
else if (SAXSource.class.equals(clazz)) {
100+
return (T) readSAXSource(body);
101+
}
102+
else if (StAXSource.class.equals(clazz)) {
103+
return (T) readStAXSource(body);
104+
}
105+
else if (StreamSource.class.equals(clazz) || Source.class.equals(clazz)) {
106+
return (T) readStreamSource(body);
107+
}
108+
else {
109+
throw new HttpMessageConversionException("Could not read class [" + clazz +
110+
"]. Only DOMSource, SAXSource, and StreamSource are supported.");
111+
}
112+
}
113+
114+
private DOMSource readDOMSource(InputStream body) throws IOException {
58115
try {
59-
if (DOMSource.class.equals(clazz)) {
60-
DOMResult domResult = new DOMResult();
61-
transform(source, domResult);
62-
return (T) new DOMSource(domResult.getNode());
63-
}
64-
else if (SAXSource.class.equals(clazz)) {
65-
ByteArrayInputStream bis = transformToByteArrayInputStream(source);
66-
return (T) new SAXSource(new InputSource(bis));
67-
}
68-
else if (StreamSource.class.equals(clazz) || Source.class.equals(clazz)) {
69-
ByteArrayInputStream bis = transformToByteArrayInputStream(source);
70-
return (T) new StreamSource(bis);
71-
}
72-
else {
73-
throw new HttpMessageConversionException("Could not read class [" + clazz +
74-
"]. Only DOMSource, SAXSource, and StreamSource are supported.");
75-
}
116+
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
117+
documentBuilderFactory.setNamespaceAware(true);
118+
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", processExternalEntities);
119+
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
120+
Document document = documentBuilder.parse(body);
121+
return new DOMSource(document);
76122
}
77-
catch (TransformerException ex) {
78-
throw new HttpMessageNotReadableException("Could not transform from [" + source + "] to [" + clazz + "]",
79-
ex);
123+
catch (ParserConfigurationException ex) {
124+
throw new HttpMessageNotReadableException("Could not set feature: " + ex.getMessage(), ex);
125+
}
126+
catch (SAXException ex) {
127+
throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex);
80128
}
81129
}
82130

83-
private ByteArrayInputStream transformToByteArrayInputStream(Source source) throws TransformerException {
84-
ByteArrayOutputStream bos = new ByteArrayOutputStream();
85-
transform(source, new StreamResult(bos));
86-
return new ByteArrayInputStream(bos.toByteArray());
131+
private SAXSource readSAXSource(InputStream body) throws IOException {
132+
try {
133+
XMLReader reader = XMLReaderFactory.createXMLReader();
134+
reader.setFeature("http://xml.org/sax/features/external-general-entities", processExternalEntities);
135+
byte[] bytes = StreamUtils.copyToByteArray(body);
136+
return new SAXSource(reader, new InputSource(new ByteArrayInputStream(bytes)));
137+
}
138+
catch (SAXException ex) {
139+
throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex);
140+
}
141+
}
142+
143+
private Source readStAXSource(InputStream body) {
144+
try {
145+
XMLInputFactory inputFactory = XMLInputFactory.newFactory();
146+
inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", processExternalEntities);
147+
XMLStreamReader streamReader = inputFactory.createXMLStreamReader(body);
148+
return new StAXSource(streamReader);
149+
}
150+
catch (XMLStreamException ex) {
151+
throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex);
152+
}
153+
}
154+
155+
private StreamSource readStreamSource(InputStream body) throws IOException {
156+
byte[] bytes = StreamUtils.copyToByteArray(body);
157+
return new StreamSource(new ByteArrayInputStream(bytes));
87158
}
88159

89160
@Override
@@ -102,15 +173,22 @@ protected Long getContentLength(T t, MediaType contentType) {
102173
}
103174

104175
@Override
105-
protected void writeToResult(T t, HttpHeaders headers, Result result) throws IOException {
176+
protected void writeInternal(T t, HttpOutputMessage outputMessage)
177+
throws IOException, HttpMessageNotWritableException {
106178
try {
179+
Result result = new StreamResult(outputMessage.getBody());
107180
transform(t, result);
108181
}
109182
catch (TransformerException ex) {
110-
throw new HttpMessageNotWritableException("Could not transform [" + t + "] to [" + result + "]", ex);
183+
throw new HttpMessageNotWritableException("Could not transform [" + t + "] to output message", ex);
111184
}
112185
}
113186

187+
private void transform(Source source, Result result) throws TransformerException {
188+
this.transformerFactory.newTransformer().transform(source, result);
189+
}
190+
191+
114192
private static class CountingOutputStream extends OutputStream {
115193

116194
private long count = 0;

spring-web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java

Lines changed: 88 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,59 @@
1717
package org.springframework.http.converter.xml;
1818

1919
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
20-
import static org.junit.Assert.assertEquals;
21-
import static org.junit.Assert.assertTrue;
20+
import static org.junit.Assert.*;
21+
import static org.junit.Assert.assertNotEquals;
2222

23+
import java.io.IOException;
24+
import java.io.InputStream;
2325
import java.io.InputStreamReader;
2426
import java.io.StringReader;
2527
import java.nio.charset.Charset;
2628

2729
import javax.xml.parsers.DocumentBuilderFactory;
30+
import javax.xml.stream.XMLStreamException;
31+
import javax.xml.stream.XMLStreamReader;
2832
import javax.xml.transform.Source;
2933
import javax.xml.transform.dom.DOMSource;
3034
import javax.xml.transform.sax.SAXSource;
35+
import javax.xml.transform.stax.StAXSource;
3136
import javax.xml.transform.stream.StreamSource;
3237

3338
import org.junit.Before;
3439
import org.junit.Test;
40+
41+
import org.springframework.core.io.ClassPathResource;
42+
import org.springframework.core.io.Resource;
3543
import org.springframework.http.MediaType;
3644
import org.springframework.http.MockHttpInputMessage;
3745
import org.springframework.http.MockHttpOutputMessage;
3846
import org.springframework.util.FileCopyUtils;
3947
import org.w3c.dom.Document;
4048
import org.w3c.dom.Element;
4149
import org.xml.sax.InputSource;
50+
import org.xml.sax.SAXException;
51+
import org.xml.sax.XMLReader;
52+
import org.xml.sax.helpers.DefaultHandler;
4253

4354
/**
4455
* @author Arjen Poutsma
4556
*/
4657
public class SourceHttpMessageConverterTests {
4758

59+
private static final String BODY = "<root>Hello World</root>";
60+
4861
private SourceHttpMessageConverter<Source> converter;
4962

63+
private String bodyExternal;
64+
5065
@Before
51-
public void setUp() {
66+
public void setUp() throws IOException {
5267
converter = new SourceHttpMessageConverter<Source>();
68+
Resource external = new ClassPathResource("external.txt", getClass());
69+
70+
bodyExternal = "<!DOCTYPE root [" +
71+
" <!ELEMENT root ANY >\n" +
72+
" <!ENTITY ext SYSTEM \"" + external.getURI() + "\" >]><root>&ext;</root>";
5373
}
5474

5575
@Test
@@ -67,39 +87,94 @@ public void canWrite() {
6787

6888
@Test
6989
public void readDOMSource() throws Exception {
70-
String body = "<root>Hello World</root>";
71-
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
90+
MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8"));
7291
inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
7392
DOMSource result = (DOMSource) converter.read(DOMSource.class, inputMessage);
7493
Document document = (Document) result.getNode();
7594
assertEquals("Invalid result", "root", document.getDocumentElement().getLocalName());
7695
}
7796

97+
@Test
98+
public void readDOMSourceExternal() throws Exception {
99+
MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes("UTF-8"));
100+
inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
101+
DOMSource result = (DOMSource) converter.read(DOMSource.class, inputMessage);
102+
Document document = (Document) result.getNode();
103+
assertEquals("Invalid result", "root", document.getDocumentElement().getLocalName());
104+
assertNotEquals("Invalid result", "Foo Bar", document.getDocumentElement().getTextContent());
105+
}
106+
78107
@Test
79108
public void readSAXSource() throws Exception {
80-
String body = "<root>Hello World</root>";
81-
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
109+
MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8"));
82110
inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
83111
SAXSource result = (SAXSource) converter.read(SAXSource.class, inputMessage);
84112
InputSource inputSource = result.getInputSource();
85113
String s = FileCopyUtils.copyToString(new InputStreamReader(inputSource.getByteStream()));
86-
assertXMLEqual("Invalid result", body, s);
114+
assertXMLEqual("Invalid result", BODY, s);
115+
}
116+
117+
@Test
118+
public void readSAXSourceExternal() throws Exception {
119+
MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes("UTF-8"));
120+
inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
121+
SAXSource result = (SAXSource) converter.read(SAXSource.class, inputMessage);
122+
InputSource inputSource = result.getInputSource();
123+
XMLReader reader = result.getXMLReader();
124+
reader.setContentHandler(new DefaultHandler() {
125+
@Override
126+
public void characters(char[] ch, int start, int length) throws SAXException {
127+
String s = new String(ch, start, length);
128+
assertNotEquals("Invalid result", "Foo Bar", s);
129+
}
130+
});
131+
reader.parse(inputSource);
87132
}
88133

134+
@Test
135+
public void readStAXSource() throws Exception {
136+
MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8"));
137+
inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
138+
StAXSource result = (StAXSource) converter.read(StAXSource.class, inputMessage);
139+
XMLStreamReader streamReader = result.getXMLStreamReader();
140+
assertTrue(streamReader.hasNext());
141+
streamReader.nextTag();
142+
String s = streamReader.getLocalName();
143+
assertEquals("root", s);
144+
s = streamReader.getElementText();
145+
assertEquals("Hello World", s);
146+
streamReader.close();
147+
}
148+
149+
@Test
150+
public void readStAXSourceExternal() throws Exception {
151+
MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes("UTF-8"));
152+
inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
153+
StAXSource result = (StAXSource) converter.read(StAXSource.class, inputMessage);
154+
XMLStreamReader streamReader = result.getXMLStreamReader();
155+
assertTrue(streamReader.hasNext());
156+
streamReader.next();
157+
streamReader.next();
158+
String s = streamReader.getLocalName();
159+
assertEquals("root", s);
160+
s = streamReader.getElementText();
161+
assertNotEquals("Foo Bar", s);
162+
streamReader.close();
163+
}
164+
165+
89166
@Test
90167
public void readStreamSource() throws Exception {
91-
String body = "<root>Hello World</root>";
92-
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
168+
MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8"));
93169
inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
94170
StreamSource result = (StreamSource) converter.read(StreamSource.class, inputMessage);
95171
String s = FileCopyUtils.copyToString(new InputStreamReader(result.getInputStream()));
96-
assertXMLEqual("Invalid result", body, s);
172+
assertXMLEqual("Invalid result", BODY, s);
97173
}
98174

99175
@Test
100176
public void readSource() throws Exception {
101-
String body = "<root>Hello World</root>";
102-
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
177+
MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8"));
103178
inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
104179
converter.read(Source.class, inputMessage);
105180
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Foo Bar

0 commit comments

Comments
 (0)