From: Markus Koschany <apo@gambaru.de>
Date: Fri, 24 Jan 2014 16:46:07 +0100
Subject: CVE-2013-6429

Bug: http://bugs.debian.org/735420
---
 .../java/org/springframework/util/StreamUtils.java | 183 ++++++++++++++++++++
 .../org/springframework/util/xml/StaxUtils.java    |  15 +-
 .../converter/xml/SourceHttpMessageConverter.java  | 190 +++++++++++++++++----
 .../xml/SourceHttpMessageConverterTests.java       | 145 +++++++++++++---
 .../http/converter/xml/external.txt                |   1 +
 5 files changed, 478 insertions(+), 56 deletions(-)
 create mode 100644 projects/org.springframework.core/src/main/java/org/springframework/util/StreamUtils.java
 create mode 100644 projects/org.springframework.web/src/test/resources/org/springframework/http/converter/xml/external.txt

diff --git a/projects/org.springframework.core/src/main/java/org/springframework/util/StreamUtils.java b/projects/org.springframework.core/src/main/java/org/springframework/util/StreamUtils.java
new file mode 100644
index 0000000..cc3107d
--- /dev/null
+++ b/projects/org.springframework.core/src/main/java/org/springframework/util/StreamUtils.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2002-2013 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+
+/**
+ * Simple utility methods for dealing with streams. The copy methods of this class are
+ * similar to those defined in {@link FileCopyUtils} except that all affected streams are
+ * left open when done. All copy methods use a block size of 4096 bytes.
+ *
+ * <p>Mainly for use within the framework, but also useful for application code.
+ *
+ * @author Juergen Hoeller
+ * @author Phillip Webb
+ * @since 3.2.2
+ * @see FileCopyUtils
+ */
+public abstract class StreamUtils {
+
+	public static final int BUFFER_SIZE = 4096;
+
+
+	/**
+	 * Copy the contents of the given InputStream into a new byte array.
+	 * Leaves the stream open when done.
+	 * @param in the stream to copy from
+	 * @return the new byte array that has been copied to
+	 * @throws IOException in case of I/O errors
+	 */
+	public static byte[] copyToByteArray(InputStream in) throws IOException {
+		ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE);
+		copy(in, out);
+		return out.toByteArray();
+	}
+
+	/**
+	 * Copy the contents of the given InputStream into a String.
+	 * Leaves the stream open when done.
+	 * @param in the InputStream to copy from
+	 * @return the String that has been copied to
+	 * @throws IOException in case of I/O errors
+	 */
+	public static String copyToString(InputStream in, Charset charset) throws IOException {
+		Assert.notNull(in, "No InputStream specified");
+		StringBuilder out = new StringBuilder();
+		InputStreamReader reader = new InputStreamReader(in, charset);
+		char[] buffer = new char[BUFFER_SIZE];
+		int bytesRead = -1;
+		while ((bytesRead = reader.read(buffer)) != -1) {
+			out.append(buffer, 0, bytesRead);
+		}
+		return out.toString();
+	}
+
+	/**
+	 * Copy the contents of the given byte array to the given OutputStream.
+	 * Leaves the stream open when done.
+	 * @param in the byte array to copy from
+	 * @param out the OutputStream to copy to
+	 * @throws IOException in case of I/O errors
+	 */
+	public static void copy(byte[] in, OutputStream out) throws IOException {
+		Assert.notNull(in, "No input byte array specified");
+		Assert.notNull(out, "No OutputStream specified");
+		out.write(in);
+	}
+
+	/**
+	 * Copy the contents of the given String to the given output OutputStream.
+	 * Leaves the stream open when done.
+	 * @param in the String to copy from
+	 * @param charset the Charset
+	 * @param out the OutputStream to copy to
+	 * @throws IOException in case of I/O errors
+	 */
+	public static void copy(String in, Charset charset, OutputStream out) throws IOException {
+		Assert.notNull(in, "No input String specified");
+		Assert.notNull(charset, "No charset specified");
+		Assert.notNull(out, "No OutputStream specified");
+		Writer writer = new OutputStreamWriter(out, charset);
+		writer.write(in);
+		writer.flush();
+	}
+
+	/**
+	 * Copy the contents of the given InputStream to the given OutputStream.
+	 * Leaves both streams open when done.
+	 * @param in the InputStream to copy from
+	 * @param out the OutputStream to copy to
+	 * @return the number of bytes copied
+	 * @throws IOException in case of I/O errors
+	 */
+	public static int copy(InputStream in, OutputStream out) throws IOException {
+		Assert.notNull(in, "No InputStream specified");
+		Assert.notNull(out, "No OutputStream specified");
+		int byteCount = 0;
+		byte[] buffer = new byte[BUFFER_SIZE];
+		int bytesRead = -1;
+		while ((bytesRead = in.read(buffer)) != -1) {
+			out.write(buffer, 0, bytesRead);
+			byteCount += bytesRead;
+		}
+		out.flush();
+		return byteCount;
+	}
+
+	/**
+	 * Returns a variant of the given {@link InputStream} where calling
+	 * {@link InputStream#close() close()} has no effect.
+	 * @param in the InputStream to decorate
+	 * @return a version of the InputStream that ignores calls to close
+	 */
+	public static InputStream nonClosing(InputStream in) {
+		Assert.notNull(in, "No InputStream specified");
+		return new NonClosingInputStream(in);
+	}
+
+	/**
+	 * Returns a variant of the given {@link OutputStream} where calling
+	 * {@link OutputStream#close() close()} has no effect.
+	 * @param out the OutputStream to decorate
+	 * @return a version of the OutputStream that ignores calls to close
+	 */
+	public static OutputStream nonClosing(OutputStream out) {
+		Assert.notNull(out, "No OutputStream specified");
+		return new NonClosingOutputStream(out);
+	}
+
+
+	private static class NonClosingInputStream extends FilterInputStream {
+
+		public NonClosingInputStream(InputStream in) {
+			super(in);
+		}
+
+		@Override
+		public void close() throws IOException {
+		}
+	}
+
+
+	private static class NonClosingOutputStream extends FilterOutputStream {
+
+		public NonClosingOutputStream(OutputStream out) {
+			super(out);
+		}
+
+		@Override
+		public void write(byte[] b, int off, int let) throws IOException {
+			// It is critical that we override this method for performance
+			out.write(b, off, let);
+		}
+
+		@Override
+		public void close() throws IOException {
+		}
+	}
+}
diff --git a/projects/org.springframework.core/src/main/java/org/springframework/util/xml/StaxUtils.java b/projects/org.springframework.core/src/main/java/org/springframework/util/xml/StaxUtils.java
index 4bd4a7a..dee73ca 100644
--- a/projects/org.springframework.core/src/main/java/org/springframework/util/xml/StaxUtils.java
+++ b/projects/org.springframework.core/src/main/java/org/springframework/util/xml/StaxUtils.java
@@ -113,7 +113,16 @@ public abstract class StaxUtils {
 	 * 1.4 {@link StAXSource}; {@code false} otherwise.
 	 */
 	public static boolean isStaxSource(Source source) {
-		return (source instanceof StaxSource || (jaxp14Available && Jaxp14StaxHandler.isStaxSource(source)));
+		return ((source instanceof StaxSource) || (jaxp14Available && Jaxp14StaxHandler.isStaxSource(source)));
+	}
+
+	/**
+	 * Indicate whether the given class is a StAX Source class.
+	 * @return {@code true} if {@code source} is a custom StAX source or JAXP
+	 * 1.4 {@link StAXSource} class; {@code false} otherwise.
+	 */
+	public static boolean isStaxSourceClass(Class<? extends Source> clazz) {
+		return (StaxSource.class.equals(clazz) || (jaxp14Available && Jaxp14StaxHandler.isStaxSourceClass(clazz)));
 	}
 
 	// Stax Result
@@ -343,6 +352,10 @@ public abstract class StaxUtils {
 			return source instanceof StAXSource;
 		}
 
+		private static boolean isStaxSourceClass(Class<? extends Source> clazz) {
+            return StAXSource.class.equals(clazz);
+        }
+
 		private static boolean isStaxResult(Result result) {
 			return result instanceof StAXResult;
 		}
diff --git a/projects/org.springframework.web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java b/projects/org.springframework.web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java
index 4ba1aac..15b7d8e 100644
--- a/projects/org.springframework.web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java
+++ b/projects/org.springframework.web/src/main/java/org/springframework/http/converter/xml/SourceHttpMessageConverter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2013 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,21 +19,40 @@ package org.springframework.http.converter.xml;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
 import javax.xml.transform.Result;
 import javax.xml.transform.Source;
 import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.dom.DOMResult;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.sax.SAXSource;
 import javax.xml.transform.stream.StreamResult;
 import javax.xml.transform.stream.StreamSource;
 
+import org.w3c.dom.Document;
 import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
 
 import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpInputMessage;
+import org.springframework.http.HttpOutputMessage;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.AbstractHttpMessageConverter;
 import org.springframework.http.converter.HttpMessageConversionException;
 import org.springframework.http.converter.HttpMessageNotReadableException;
 import org.springframework.http.converter.HttpMessageNotWritableException;
+import org.springframework.util.StreamUtils;
+import org.springframework.util.xml.StaxUtils;
 
 /**
  * Implementation of {@link org.springframework.http.converter.HttpMessageConverter}
@@ -42,55 +61,154 @@ import org.springframework.http.converter.HttpMessageNotWritableException;
  * @author Arjen Poutsma
  * @since 3.0
  */
-public class SourceHttpMessageConverter<T extends Source> extends AbstractXmlHttpMessageConverter<T> {
+public class SourceHttpMessageConverter<T extends Source> extends AbstractHttpMessageConverter<T> {
 
-	@Override
+    private final TransformerFactory transformerFactory = TransformerFactory.newInstance();
+
+    private boolean processExternalEntities = false;
+
+    /**
+     * Sets the {@link #setSupportedMediaTypes(java.util.List) supportedMediaTypes}
+     * to {@code text/xml} and {@code application/xml}, and {@code application/*-xml}.
+     */
+    public SourceHttpMessageConverter() {
+        super(MediaType.APPLICATION_XML, MediaType.TEXT_XML, new MediaType("application", "*+xml"));
+    }
+
+
+    /**
+     * 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 supports(Class<?> clazz) {
-		return DOMSource.class.equals(clazz) || SAXSource.class.equals(clazz) || StreamSource.class.equals(clazz) ||
-				Source.class.equals(clazz);
+		return DOMSource.class.equals(clazz) || SAXSource.class.equals(clazz)
+				|| StreamSource.class.equals(clazz) || Source.class.equals(clazz);
 	}
 
+    @Override
+    protected T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
+            throws IOException, HttpMessageNotReadableException {
+
+        InputStream body = inputMessage.getBody();
+        if (DOMSource.class.equals(clazz)) {
+            return (T) readDOMSource(body);
+        }
+        else if (StaxUtils.isStaxSourceClass(clazz)) {
+            return (T) readStAXSource(body);
+        }
+        else if (SAXSource.class.equals(clazz)) {
+            return (T) readSAXSource(body);
+        }
+        else if (StreamSource.class.equals(clazz) || Source.class.equals(clazz)) {
+            return (T) readStreamSource(body);
+        }
+        else {
+            throw new HttpMessageConversionException("Could not read class [" + clazz +
+                    "]. Only DOMSource, SAXSource, and StreamSource are supported.");
+        }
+    }
+
+    private DOMSource readDOMSource(InputStream body) throws IOException {
+        try {
+            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
+            documentBuilderFactory.setNamespaceAware(true);
+            documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", processExternalEntities);
+            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
+            Document document = documentBuilder.parse(body);
+            return new DOMSource(document);
+        }
+        catch (ParserConfigurationException ex) {
+            throw new HttpMessageNotReadableException("Could not set feature: " + ex.getMessage(), ex);
+        }
+        catch (SAXException ex) {
+            throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex);
+        }
+    }
+
+    private SAXSource readSAXSource(InputStream body) throws IOException {
+        try {
+            XMLReader reader = XMLReaderFactory.createXMLReader();
+            reader.setFeature("http://xml.org/sax/features/external-general-entities", processExternalEntities);
+            byte[] bytes = StreamUtils.copyToByteArray(body);
+            return new SAXSource(reader, new InputSource(new ByteArrayInputStream(bytes)));
+        }
+        catch (SAXException ex) {
+            throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex);
+        }
+    }
+
+    private Source readStAXSource(InputStream body) {
+        try {
+            XMLInputFactory inputFactory = XMLInputFactory.newFactory();
+            inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", processExternalEntities);
+            XMLStreamReader streamReader = inputFactory.createXMLStreamReader(body);
+            return StaxUtils.createStaxSource(streamReader);
+        }
+        catch (XMLStreamException ex) {
+            throw new HttpMessageNotReadableException("Could not parse document: " + ex.getMessage(), ex);
+        }
+    }
+
+    private StreamSource readStreamSource(InputStream body) throws IOException {
+        byte[] bytes = StreamUtils.copyToByteArray(body);
+        return new StreamSource(new ByteArrayInputStream(bytes));
+    }
+
 	@Override
-	@SuppressWarnings("unchecked")
-	protected T readFromSource(Class clazz, HttpHeaders headers, Source source) throws IOException {
-		try {
-			if (DOMSource.class.equals(clazz)) {
-				DOMResult domResult = new DOMResult();
-				transform(source, domResult);
-				return (T) new DOMSource(domResult.getNode());
-			}
-			else if (SAXSource.class.equals(clazz)) {
-				ByteArrayInputStream bis = transformToByteArrayInputStream(source);
-				return (T) new SAXSource(new InputSource(bis));
+	protected Long getContentLength(T t, MediaType contentType) {
+		if (t instanceof DOMSource) {
+			try {
+				CountingOutputStream os = new CountingOutputStream();
+				transform(t, new StreamResult(os));
+				return os.count;
 			}
-			else if (StreamSource.class.equals(clazz) || Source.class.equals(clazz)) {
-				ByteArrayInputStream bis = transformToByteArrayInputStream(source);
-				return (T) new StreamSource(bis);
-			}
-			else {
-				throw new HttpMessageConversionException("Could not read class [" + clazz +
-						"]. Only DOMSource, SAXSource, and StreamSource are supported.");
+			catch (TransformerException ex) {
+				// ignore
 			}
 		}
-		catch (TransformerException ex) {
-			throw new HttpMessageNotReadableException("Could not transform from [" + source + "] to [" + clazz + "]",
-					ex);
-		}
-	}
-
-	private ByteArrayInputStream transformToByteArrayInputStream(Source source) throws TransformerException {
-		ByteArrayOutputStream bos = new ByteArrayOutputStream();
-		transform(source, new StreamResult(bos));
-		return new ByteArrayInputStream(bos.toByteArray());
+		return null;
 	}
 
-	@Override
-	protected void writeToResult(T t, HttpHeaders headers, Result result) throws IOException {
+    @Override
+    protected void writeInternal(T t, HttpOutputMessage outputMessage)
+            throws IOException, HttpMessageNotWritableException {
 		try {
+            Result result = new StreamResult(outputMessage.getBody());
 			transform(t, result);
 		}
 		catch (TransformerException ex) {
-			throw new HttpMessageNotWritableException("Could not transform [" + t + "] to [" + result + "]", ex);
+			throw new HttpMessageNotWritableException("Could not transform [" + t + "] to output message", ex);
+		}
+	}
+
+    private void transform(Source source, Result result) throws TransformerException {
+        this.transformerFactory.newTransformer().transform(source, result);
+    }
+
+
+    private static class CountingOutputStream extends OutputStream {
+
+		private long count = 0;
+
+		@Override
+		public void write(int b) throws IOException {
+			count++;
+		}
+
+		@Override
+		public void write(byte[] b) throws IOException {
+			count += b.length;
+		}
+
+		@Override
+		public void write(byte[] b, int off, int len) throws IOException {
+			count += len;
 		}
 	}
 
diff --git a/projects/org.springframework.web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java b/projects/org.springframework.web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java
index bb20f8a..8d47c22 100644
--- a/projects/org.springframework.web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java
+++ b/projects/org.springframework.web/src/test/java/org/springframework/http/converter/xml/SourceHttpMessageConverterTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2010 the original author or authors.
+ * Copyright 2002-2013 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.
@@ -16,35 +16,60 @@
 
 package org.springframework.http.converter.xml;
 
+import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertNotEquals;
+
+import java.io.IOException;
+import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.StringReader;
 import java.nio.charset.Charset;
+
 import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
 import javax.xml.transform.Source;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.sax.SAXSource;
+import javax.xml.transform.stax.StAXSource;
 import javax.xml.transform.stream.StreamSource;
 
-import static org.custommonkey.xmlunit.XMLAssert.*;
 import org.junit.Before;
 import org.junit.Test;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.xml.sax.InputSource;
 
+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;
 import org.springframework.util.FileCopyUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
 
-/** @author Arjen Poutsma */
-@SuppressWarnings("unchecked")
+/**
+ * @author Arjen Poutsma
+ */
 public class SourceHttpMessageConverterTests {
 
+	private static final String BODY = "<root>Hello World</root>";
+
 	private SourceHttpMessageConverter<Source> converter;
 
+	private String bodyExternal;
+
 	@Before
-	public void setUp() {
+	public void setUp() throws IOException {
 		converter = new SourceHttpMessageConverter<Source>();
+		Resource external = new ClassPathResource("external.txt", getClass());
+
+		bodyExternal = "<!DOCTYPE root [" +
+				"  <!ELEMENT root ANY >\n" +
+				"  <!ENTITY ext SYSTEM \"" + external.getURI() + "\" >]><root>&ext;</root>";
 	}
 
 	@Test
@@ -62,45 +87,100 @@ public class SourceHttpMessageConverterTests {
 
 	@Test
 	public void readDOMSource() throws Exception {
-		String body = "<root>Hello World</root>";
-		MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
+		MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8"));
+		inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
+		DOMSource result = (DOMSource) converter.read(DOMSource.class, inputMessage);
+		Document document = (Document) result.getNode();
+		assertEquals("Invalid result", "root", document.getDocumentElement().getLocalName());
+	}
+
+	@Test
+	public void readDOMSourceExternal() throws Exception {
+		MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes("UTF-8"));
 		inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
 		DOMSource result = (DOMSource) converter.read(DOMSource.class, inputMessage);
 		Document document = (Document) result.getNode();
 		assertEquals("Invalid result", "root", document.getDocumentElement().getLocalName());
+		assertNotEquals("Invalid result", "Foo Bar", document.getDocumentElement().getTextContent());
 	}
 
 	@Test
 	public void readSAXSource() throws Exception {
-		String body = "<root>Hello World</root>";
-		MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
+		MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8"));
 		inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
 		SAXSource result = (SAXSource) converter.read(SAXSource.class, inputMessage);
 		InputSource inputSource = result.getInputSource();
 		String s = FileCopyUtils.copyToString(new InputStreamReader(inputSource.getByteStream()));
-		assertXMLEqual("Invalid result", body, s);
+		assertXMLEqual("Invalid result", BODY, s);
 	}
 
 	@Test
+	public void readSAXSourceExternal() throws Exception {
+		MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes("UTF-8"));
+		inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
+		SAXSource result = (SAXSource) converter.read(SAXSource.class, inputMessage);
+		InputSource inputSource = result.getInputSource();
+		XMLReader reader = result.getXMLReader();
+		reader.setContentHandler(new DefaultHandler() {
+			@Override
+			public void characters(char[] ch, int start, int length) throws SAXException {
+				String s = new String(ch, start, length);
+				assertNotEquals("Invalid result", "Foo Bar", s);
+			}
+		});
+		reader.parse(inputSource);
+	}
+
+	@Test
+	public void readStAXSource() throws Exception {
+		MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8"));
+		inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
+		StAXSource result = (StAXSource) converter.read(StAXSource.class, inputMessage);
+		XMLStreamReader streamReader = result.getXMLStreamReader();
+		assertTrue(streamReader.hasNext());
+		streamReader.nextTag();
+		String s = streamReader.getLocalName();
+		assertEquals("root", s);
+		s = streamReader.getElementText();
+		assertEquals("Hello World", s);
+		streamReader.close();
+	}
+
+	@Test
+	public void readStAXSourceExternal() throws Exception {
+		MockHttpInputMessage inputMessage = new MockHttpInputMessage(bodyExternal.getBytes("UTF-8"));
+		inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
+		StAXSource result = (StAXSource) converter.read(StAXSource.class, inputMessage);
+		XMLStreamReader streamReader = result.getXMLStreamReader();
+		assertTrue(streamReader.hasNext());
+		streamReader.next();
+		streamReader.next();
+		String s = streamReader.getLocalName();
+		assertEquals("root", s);
+		s = streamReader.getElementText();
+		assertNotEquals("Foo Bar", s);
+		streamReader.close();
+	}
+
+
+	@Test
 	public void readStreamSource() throws Exception {
-		String body = "<root>Hello World</root>";
-		MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
+		MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8"));
 		inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
 		StreamSource result = (StreamSource) converter.read(StreamSource.class, inputMessage);
 		String s = FileCopyUtils.copyToString(new InputStreamReader(result.getInputStream()));
-		assertXMLEqual("Invalid result", body, s);
+		assertXMLEqual("Invalid result", BODY, s);
 	}
 
 	@Test
 	public void readSource() throws Exception {
-		String body = "<root>Hello World</root>";
-		MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes("UTF-8"));
+		MockHttpInputMessage inputMessage = new MockHttpInputMessage(BODY.getBytes("UTF-8"));
 		inputMessage.getHeaders().setContentType(new MediaType("application", "xml"));
 		converter.read(Source.class, inputMessage);
 	}
 
 	@Test
-	public void write() throws Exception {
+	public void writeDOMSource() throws Exception {
 		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
 		documentBuilderFactory.setNamespaceAware(true);
 		Document document = documentBuilderFactory.newDocumentBuilder().newDocument();
@@ -115,7 +195,34 @@ public class SourceHttpMessageConverterTests {
 				outputMessage.getBodyAsString(Charset.forName("UTF-8")));
 		assertEquals("Invalid content-type", new MediaType("application", "xml"),
 				outputMessage.getHeaders().getContentType());
+		assertEquals("Invalid content-length", outputMessage.getBodyAsBytes().length,
+				outputMessage.getHeaders().getContentLength());
 	}
 
+	@Test
+	public void writeSAXSource() throws Exception {
+		String xml = "<root>Hello World</root>";
+		SAXSource saxSource = new SAXSource(new InputSource(new StringReader(xml)));
+
+		MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
+		converter.write(saxSource, null, outputMessage);
+		assertXMLEqual("Invalid result", "<root>Hello World</root>",
+				outputMessage.getBodyAsString(Charset.forName("UTF-8")));
+		assertEquals("Invalid content-type", new MediaType("application", "xml"),
+				outputMessage.getHeaders().getContentType());
+	}
+
+	@Test
+	public void writeStreamSource() throws Exception {
+		String xml = "<root>Hello World</root>";
+		StreamSource streamSource = new StreamSource(new StringReader(xml));
+
+		MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
+		converter.write(streamSource, null, outputMessage);
+		assertXMLEqual("Invalid result", "<root>Hello World</root>",
+				outputMessage.getBodyAsString(Charset.forName("UTF-8")));
+		assertEquals("Invalid content-type", new MediaType("application", "xml"),
+				outputMessage.getHeaders().getContentType());
+	}
 
 }
diff --git a/projects/org.springframework.web/src/test/resources/org/springframework/http/converter/xml/external.txt b/projects/org.springframework.web/src/test/resources/org/springframework/http/converter/xml/external.txt
new file mode 100644
index 0000000..76c7ac2
--- /dev/null
+++ b/projects/org.springframework.web/src/test/resources/org/springframework/http/converter/xml/external.txt
@@ -0,0 +1 @@
+Foo Bar
