DATACMNS-1292 - Improved default setup of XMLBeam.

We now explicitly disable entity expansion in the DocumentBuilderFactory used by XMLBeam.

Introduced constructor in XmlBeamHttpMessageConverter to allow dedicated configuration of an XBProjector instance in case the defaults need tweaking and augmented the web configuration setup to automatically pick up a custom XmlBeamHttpMessageConverter bean instead of our own default if present.
This commit is contained in:
Oliver Gierke
2018-04-11 17:21:46 +02:00
parent 73415d0593
commit 11d0aa1b39
4 changed files with 83 additions and 5 deletions

View File

@@ -18,6 +18,8 @@ package org.springframework.data.web;
import java.io.IOException;
import java.util.Map;
import javax.xml.parsers.DocumentBuilderFactory;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage;
@@ -27,8 +29,11 @@ import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.xml.sax.SAXParseException;
import org.xmlbeam.XBProjector;
import org.xmlbeam.config.DefaultXMLFactoriesConfig;
/**
* A read-only {@link HttpMessageConverter} to create XMLBeam-based projection instances for interfaces.
@@ -47,9 +52,39 @@ public class XmlBeamHttpMessageConverter extends AbstractHttpMessageConverter<Ob
*/
public XmlBeamHttpMessageConverter() {
this(new XBProjector(new DefaultXMLFactoriesConfig() {
private static final long serialVersionUID = -1324345769124477493L;
/*
* (non-Javadoc)
* @see org.xmlbeam.config.DefaultXMLFactoriesConfig#createDocumentBuilderFactory()
*/
@Override
public DocumentBuilderFactory createDocumentBuilderFactory() {
DocumentBuilderFactory factory = super.createDocumentBuilderFactory();
factory.setAttribute("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setAttribute("http://xml.org/sax/features/external-general-entities", false);
return factory;
}
}));
}
/**
* Creates a new {@link XmlBeamHttpMessageConverter} using the given {@link XBProjector}.
*
* @param projector must not be {@literal null}.
*/
public XmlBeamHttpMessageConverter(XBProjector projector) {
super(MediaType.APPLICATION_XML, MediaType.parseMediaType("application/*+xml"));
this.projectionFactory = new XBProjector();
Assert.notNull(projector, "XBProjector must not be null!");
this.projectionFactory = projector;
}
/*
@@ -89,7 +124,21 @@ public class XmlBeamHttpMessageConverter extends AbstractHttpMessageConverter<Ob
@Override
protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return projectionFactory.io().stream(inputMessage.getBody()).read(clazz);
try {
return projectionFactory.io().stream(inputMessage.getBody()).read(clazz);
} catch (RuntimeException o_O) {
Throwable cause = o_O.getCause();
if (SAXParseException.class.isInstance(cause)) {
throw new HttpMessageNotReadableException("Cannot read input message!", cause);
} else {
throw o_O;
}
}
}
/*

View File

@@ -53,8 +53,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
@Configuration
public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter {
@Autowired private ApplicationContext context;
@Autowired @Qualifier("mvcConversionService") ObjectFactory<ConversionService> conversionService;
private @Autowired ApplicationContext context;
private @Autowired @Qualifier("mvcConversionService") ObjectFactory<ConversionService> conversionService;
private @Autowired(required = false) XmlBeamHttpMessageConverter xmlBeamHttpMessageConverter;
/*
* (non-Javadoc)
@@ -133,7 +134,12 @@ public class SpringDataWebConfiguration extends WebMvcConfigurerAdapter {
}
if (ClassUtils.isPresent("org.xmlbeam.XBProjector", context.getClassLoader())) {
converters.add(0, new XmlBeamHttpMessageConverter());
XmlBeamHttpMessageConverter converter = xmlBeamHttpMessageConverter == null //
? new XmlBeamHttpMessageConverter() //
: xmlBeamHttpMessageConverter;
converters.add(0, converter);
}
}

View File

@@ -22,13 +22,18 @@ import static org.mockito.Mockito.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.web.ProjectingJackson2HttpMessageConverterUnitTests.UnannotatedInterface;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.xml.sax.SAXParseException;
import org.xmlbeam.annotation.XBRead;
/**
@@ -40,6 +45,8 @@ import org.xmlbeam.annotation.XBRead;
@RunWith(MockitoJUnitRunner.class)
public class XmlBeamHttpMessageConverterUnitTests {
public @Rule ExpectedException exception = ExpectedException.none();
XmlBeamHttpMessageConverter converter = new XmlBeamHttpMessageConverter();
@Mock HttpInputMessage message;
@@ -88,6 +95,20 @@ public class XmlBeamHttpMessageConverterUnitTests {
assertThat(converter.canRead(Customer.class, MediaType.APPLICATION_XML), is(true));
}
@Test // DATACMNS-1292
public void doesNotSupportEntityExpansion() throws Exception {
preparePayload("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" //
+ "<!DOCTYPE foo [\n" //
+ "<!ELEMENT foo ANY >\n" //
+ "<!ENTITY xxe \"Bar\" >]><user><firstname>&xxe;</firstname><lastname>Matthews</lastname></user>");
exception.expect(HttpMessageNotReadableException.class);
exception.expectCause(is(Matchers.<Throwable> instanceOf(SAXParseException.class)));
converter.read(Customer.class, message);
}
private void preparePayload(String payload) throws IOException {
when(message.getBody()).thenReturn(new ByteArrayInputStream(payload.getBytes()));
}

View File

@@ -20,6 +20,7 @@ Import-Template:
javax.servlet.*;version="[2.5.0, 4.0.0)";resolution:=optional,
javax.xml.bind.*;version="0";resolution:=optional,
javax.xml.transform.*;version="0";resolution:=optional,
javax.xml.parsers.*;version="0";resolution:=optional,
net.minidev.json.*;version="${jsonpath:[=.=.=,+1.0.0)}";resolution:=optional,
org.aopalliance.*;version="[1.0.0, 2.0.0)";resolution:=optional,
org.joda.time.*;version="${jodatime:[=.=.=,+1.0.0)}";resolution:=optional,
@@ -42,6 +43,7 @@ Import-Template:
org.slf4j.*;version="${slf4j:[=.=.=,+1.0.0)}",
org.threeten.bp.*;version="${threetenbp:[=.=.=,+1.0.0)}";resolution:=optional,
org.w3c.dom.*;version="0",
org.xml.sax.*;version="0";resolution:=optional,
org.xmlbeam.*;version="${xmlbeam:[=.=.=,+1.0.0)}";resolution:=optional,
scala.*;version="${scala:[=.=.=,+1.0.0)}";resolution:=optional
DynamicImport-Package: *