Skip to content

Commit 7387cb9

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 863570a commit 7387cb9

File tree

4 files changed

+229
-59
lines changed

4 files changed

+229
-59
lines changed

spring-core/src/main/java/org/springframework/util/xml/StaxUtils.java

+14-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,16 @@ public static Source createStaxSource(XMLEventReader eventReader) throws XMLStre
111111
* 1.4 {@link StAXSource}; {@code false} otherwise.
112112
*/
113113
public static boolean isStaxSource(Source source) {
114-
return (source instanceof StaxSource || (jaxp14Available && Jaxp14StaxHandler.isStaxSource(source)));
114+
return ((source instanceof StaxSource) || (jaxp14Available && Jaxp14StaxHandler.isStaxSource(source)));
115+
}
116+
117+
/**
118+
* Indicate whether the given class is a StAX Source class.
119+
* @return {@code true} if {@code source} is a custom StAX source or JAXP
120+
* 1.4 {@link StAXSource} class; {@code false} otherwise.
121+
*/
122+
public static boolean isStaxSourceClass(Class<? extends Source> clazz) {
123+
return (StaxSource.class.equals(clazz) || (jaxp14Available && Jaxp14StaxHandler.isStaxSourceClass(clazz)));
115124
}
116125

117126

@@ -348,6 +357,10 @@ private static boolean isStaxSource(Source source) {
348357
return (source instanceof StAXSource);
349358
}
350359

360+
private static boolean isStaxSourceClass(Class<? extends Source> clazz) {
361+
return StAXSource.class.equals(clazz);
362+
}
363+
351364
private static boolean isStaxResult(Result result) {
352365
return (result instanceof StAXResult);
353366
}

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

+125-44
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.
@@ -19,73 +19,147 @@
1919
import java.io.ByteArrayInputStream;
2020
import java.io.ByteArrayOutputStream;
2121
import java.io.IOException;
22+
import java.io.InputStream;
2223
import java.io.OutputStream;
24+
import javax.xml.parsers.DocumentBuilder;
25+
import javax.xml.parsers.DocumentBuilderFactory;
26+
import javax.xml.parsers.ParserConfigurationException;
27+
import javax.xml.stream.XMLInputFactory;
28+
import javax.xml.stream.XMLStreamException;
29+
import javax.xml.stream.XMLStreamReader;
2330
import javax.xml.transform.Result;
2431
import javax.xml.transform.Source;
2532
import javax.xml.transform.TransformerException;
33+
import javax.xml.transform.TransformerFactory;
2634
import javax.xml.transform.dom.DOMResult;
2735
import javax.xml.transform.dom.DOMSource;
2836
import javax.xml.transform.sax.SAXSource;
2937
import javax.xml.transform.stream.StreamResult;
3038
import javax.xml.transform.stream.StreamSource;
3139

40+
import org.w3c.dom.Document;
3241
import org.xml.sax.InputSource;
42+
import org.xml.sax.SAXException;
43+
import org.xml.sax.XMLReader;
44+
import org.xml.sax.helpers.XMLReaderFactory;
3345

3446
import org.springframework.http.HttpHeaders;
47+
import org.springframework.http.HttpInputMessage;
48+
import org.springframework.http.HttpOutputMessage;
3549
import org.springframework.http.MediaType;
50+
import org.springframework.http.converter.AbstractHttpMessageConverter;
3651
import org.springframework.http.converter.HttpMessageConversionException;
3752
import org.springframework.http.converter.HttpMessageNotReadableException;
3853
import org.springframework.http.converter.HttpMessageNotWritableException;
54+
import org.springframework.util.StreamUtils;
55+
import org.springframework.util.xml.StaxUtils;
3956

4057
/**
41-
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter} that can read and write {@link
42-
* Source} objects.
58+
* Implementation of {@link org.springframework.http.converter.HttpMessageConverter}
59+
* that can read and write {@link Source} objects.
4360
*
4461
* @author Arjen Poutsma
4562
* @since 3.0
4663
*/
47-
public class SourceHttpMessageConverter<T extends Source> extends AbstractXmlHttpMessageConverter<T> {
64+
public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMessageConverter<T> {
4865

49-
@Override
50-
public boolean supports(Class<?> clazz) {
51-
return DOMSource.class.equals(clazz) || SAXSource.class.equals(clazz) || StreamSource.class.equals(clazz) ||
52-
Source.class.equals(clazz);
53-
}
66+
private final TransformerFactory transformerFactory = TransformerFactory.newInstance();
5467

55-
@Override
56-
@SuppressWarnings("unchecked")
57-
protected T readFromSource(Class clazz, HttpHeaders headers, Source source) throws IOException {
58-
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-
}
76-
}
77-
catch (TransformerException ex) {
78-
throw new HttpMessageNotReadableException("Could not transform from [" + source + "] to [" + clazz + "]",
79-
ex);
80-
}
81-
}
68+
private boolean processExternalEntities = false;
69+
70+
/**
71+
* Sets the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes}
72+
* to {@code text/xml} and {@code application/xml}, and {@code application/*-xml}.
73+
*/
74+
public SourceHttpMessageConverter() {
75+
super(MediaType.APPLICATION_XML, MediaType.TEXT_XML, new MediaType("application", "*+xml"));
76+
}
77+
78+
79+
/**
80+
* Indicates whether external XML entities are processed when converting
81+
* to a Source.
82+
* <p>Default is {@code false}, meaning that external entities are not resolved.
83+
*/
84+
public void setProcessExternalEntities(boolean processExternalEntities) {
85+
this.processExternalEntities = processExternalEntities;
86+
}
8287

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());
88+
@Override
89+
public boolean supports(Class<?> clazz) {
90+
return DOMSource.class.equals(clazz) || SAXSource.class.equals(clazz)
91+
|| StreamSource.class.equals(clazz) || Source.class.equals(clazz);
8792
}
8893

94+
@Override
95+
protected T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
96+
throws IOException, HttpMessageNotReadableException {
97+
98+
InputStream body = inputMessage.getBody();
99+
if (DOMSource.class.equals(clazz)) {
100+
return (T) readDOMSource(body);
101+
}
102+
else if (StaxUtils.isStaxSourceClass(clazz)) {
103+
return (T) readStAXSource(body);
104+
}
105+
else if (SAXSource.class.equals(clazz)) {
106+
return (T) readSAXSource(body);
107+
}
108+
else if (StreamSource.class.equals(clazz) || Source.class.equals(clazz)) {
109+
return (T) readStreamSource(body);
110+
}
111+
else {
112+
throw new HttpMessageConversionException("Could not read class [" + clazz +
113+
"]. Only DOMSource, SAXSource, and StreamSource are supported.");
114+
}
115+
}
116+
117+
private DOMSource readDOMSource(InputStream body) throws IOException {
118+
try {
119+
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
120+
documentBuilderFactory.setNamespaceAware(true);
121+
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", processExternalEntities);
122+
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
123+
Document document = documentBuilder.parse(body);
124+
return new DOMSource(document);
125+
}
126+
catch (ParserConfigurationException ex) {
127+
throw new HttpMessageNotReadableException("Could not set feature: " + ex.getMessage(), ex);
128+
}
129+
catch (SAXException ex) {
130+
throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex);
131+
}
132+
}
133+
134+
private SAXSource readSAXSource(InputStream body) throws IOException {
135+
try {
136+
XMLReader reader = XMLReaderFactory.createXMLReader();
137+
reader.setFeature("http://xml.org/sax/features/external-general-entities", processExternalEntities);
138+
byte[] bytes = StreamUtils.copyToByteArray(body);
139+
return new SAXSource(reader, new InputSource(new ByteArrayInputStream(bytes)));
140+
}
141+
catch (SAXException ex) {
142+
throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex);
143+
}
144+
}
145+
146+
private Source readStAXSource(InputStream body) {
147+
try {
148+
XMLInputFactory inputFactory = XMLInputFactory.newFactory();
149+
inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", processExternalEntities);
150+
XMLStreamReader streamReader = inputFactory.createXMLStreamReader(body);
151+
return StaxUtils.createStaxSource(streamReader);
152+
}
153+
catch (XMLStreamException ex) {
154+
throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex);
155+
}
156+
}
157+
158+
private StreamSource readStreamSource(InputStream body) throws IOException {
159+
byte[] bytes = StreamUtils.copyToByteArray(body);
160+
return new StreamSource(new ByteArrayInputStream(bytes));
161+
}
162+
89163
@Override
90164
protected Long getContentLength(T t, MediaType contentType) {
91165
if (t instanceof DOMSource) {
@@ -101,17 +175,24 @@ protected Long getContentLength(T t, MediaType contentType) {
101175
return null;
102176
}
103177

104-
@Override
105-
protected void writeToResult(T t, HttpHeaders headers, Result result) throws IOException {
178+
@Override
179+
protected void writeInternal(T t, HttpOutputMessage outputMessage)
180+
throws IOException, HttpMessageNotWritableException {
106181
try {
182+
Result result = new StreamResult(outputMessage.getBody());
107183
transform(t, result);
108184
}
109185
catch (TransformerException ex) {
110-
throw new HttpMessageNotWritableException("Could not transform [" + t + "] to [" + result + "]", ex);
186+
throw new HttpMessageNotWritableException("Could not transform [" + t + "] to output message", ex);
111187
}
112188
}
113189

114-
private static class CountingOutputStream extends OutputStream {
190+
private void transform(Source source, Result result) throws TransformerException {
191+
this.transformerFactory.newTransformer().transform(source, result);
192+
}
193+
194+
195+
private static class CountingOutputStream extends OutputStream {
115196

116197
private long count = 0;
117198

0 commit comments

Comments
 (0)