3

I call an old web service provided by a third party. I am using Spring RestTemplate:

HttpEntity<MyRequest> requestHttpEntity = new HttpEntity<>(requestBody, headers);
MyResponse response = restTemplate.postForEntity(url, requestHttpEntity, MyResponse.class);

I receive an XML (which format I cannot influence, it's a third party service) as a response:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE MyResponse SYSTEM "http://example.com:8080/some/path/MyResponse.dtd">

<MyResponse>
    ...
</MyResponse>

The postForEntity() method throws the exception

org.springframework.web.client.RestClientException: 
    Error while extracting response for type [class com.example.MyResponse] and content type [text/xml;charset=ISO-8859-1];
nested exception is org.springframework.http.converter.HttpMessageNotReadableException: 
    Could not unmarshal to [class com.example.MyResponse]: null;
nested exception is javax.xml.bind.UnmarshalException

- with linked exception:
     [org.xml.sax.SAXParseException; lineNumber: 2; columnNumber: 10;
     DOCTYPE is disallowed when the feature
     "http://apache.org/xml/features/disallow-doctype-decl" set to true.]

I found the only sensible reference to the http://apache.org/xml/features/disallow-doctype-decl feature here: https://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl

Question: How can I customize the unmarshaling without completely avoiding the automagic behavior of Spring RestTemplate? I want to force the unmarshaler to accept XML containing element with DTD reference.

This question is strongly related to my other question How to customize automatic marshaling in Spring RestTemplate to produce/modify XML headers (encoding, DOCTYPE), but the solution proposed there is not easily applicable here.

Honza Zidek
  • 9,204
  • 4
  • 72
  • 118
  • 1
    Just set that property to false... You can configure the `HttpMessageConverter` to do so https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/converter/xml/Jaxb2RootElementHttpMessageConverter.html#setSupportDtd-boolean- . By default this property is set to `false` disabling DTD support, this has to do with security implications when enabling DTD support. – M. Deinum Dec 17 '17 at 10:18
  • @M.Deinum It works like magic (supposed you meant set that property to *true* :) ), thanks! I have not noticed the method before. Please make it an answer, I am keen to accept it! And do you have also such a simple answer to my related question https://stackoverflow.com/q/47836310/2886891? The Alex Savitsky's answer *does* work, but it looks more like a hack to me. – Honza Zidek Dec 17 '17 at 10:35
  • See the answer here, especially the second part that should also answer your other question. – M. Deinum Dec 18 '17 at 07:33

1 Answers1

6

By default the Jaxb2RootElementHttpMessageConverter disables the DTD support (and with that XML Entity support). The reason for this is that it has security implications, see SPR-11376.

To enable it on the Jaxb2RootElementHttpMessageConverter you can set the supportDtd property to true to enable it again. But be aware this will also open up some potential security issues!.

@Bean
public Jaxb2RootElementHttpMessageConverter jaxb2RootElementHttpMessageConverter() {
    Jaxb2RootElementHttpMessageConverter converter = new Jaxb2RootElementHttpMessageConverter();
    converter.setSupportDtd(true);
    return converter;
}

This should be enough to (re)configure support without needing to add any additional configuration. One thing to remember is that this will configure the globally available Jaxb2RootElementHttpMessageConverter and as such will impact all controllers and RestTemplates you might want to use.

Instead of doing this you could also use the RestTemplateBuilder which you should use when creating an instance of the RestTemplate to only influence that specific RestTemplate.

@Bean
public RestTemplate yourRestTemplate(RestTemplateBuilder builder) {
    Jaxb2RootElementHttpMessageConverter converter = new Jaxb2RootElementHttpMessageConverter();
    converter.setSupportDtd(true);

    return builder.messageConverters(converter).build()
}

This way you can configure it specific for that instance of the RestTemplate and configure what you like.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • Could you please now make a kind of a summary in your answer and we will both delete our comments from here so we keep SO clear for the other readers. I will then accept your answer, which helped me a lot, thanks! – Honza Zidek Dec 19 '17 at 08:39
  • What should I modify... The answer, as is, is correct. Your results differ because you are using the `RestTemplateBuilder` in a way you shouldn't be using it. – M. Deinum Dec 19 '17 at 08:40
  • Maybe add some notes about how the behaviour differs if you use `RestTemplateBuilder` as a (default) bean or if you instantiate it manually. Because I did not know there are methods like `additionalMessageConverters()` and `messageConverters()` and `defaultMessageConverters()` and others may benefit from it, too. – Honza Zidek Dec 19 '17 at 08:43