/* * Copyright 2002-2016 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.http.converter.xml; import java.lang.reflect.Type; import java.util.Collection; import java.util.List; import java.util.Set; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; import javax.xml.stream.XMLInputFactory; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.http.MockHttpInputMessage; import org.springframework.http.converter.HttpMessageNotReadableException; import static org.junit.Assert.*; /** * Test fixture for {@link Jaxb2CollectionHttpMessageConverter}. * * @author Arjen Poutsma * @author Rossen Stoyanchev */ public class Jaxb2CollectionHttpMessageConverterTests { private Jaxb2CollectionHttpMessageConverter<?> converter; private Type rootElementListType; private Type rootElementSetType; private Type typeListType; private Type typeSetType; @Rule public ExpectedException thrown = ExpectedException.none(); @Before public void setUp() { converter = new Jaxb2CollectionHttpMessageConverter<Collection<Object>>(); rootElementListType = new ParameterizedTypeReference<List<RootElement>>() {}.getType(); rootElementSetType = new ParameterizedTypeReference<Set<RootElement>>() {}.getType(); typeListType = new ParameterizedTypeReference<List<TestType>>() {}.getType(); typeSetType = new ParameterizedTypeReference<Set<TestType>>() {}.getType(); } @Test public void canRead() throws Exception { assertTrue(converter.canRead(rootElementListType, null, null)); assertTrue(converter.canRead(rootElementSetType, null, null)); assertTrue(converter.canRead(typeSetType, null, null)); } @Test @SuppressWarnings("unchecked") public void readXmlRootElementList() throws Exception { String content = "<list><rootElement><type s=\"1\"/></rootElement><rootElement><type s=\"2\"/></rootElement></list>"; MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); List<RootElement> result = (List<RootElement>) converter.read(rootElementListType, null, inputMessage); assertEquals("Invalid result", 2, result.size()); assertEquals("Invalid result", "1", result.get(0).type.s); assertEquals("Invalid result", "2", result.get(1).type.s); } @Test @SuppressWarnings("unchecked") public void readXmlRootElementSet() throws Exception { String content = "<set><rootElement><type s=\"1\"/></rootElement><rootElement><type s=\"2\"/></rootElement></set>"; MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); Set<RootElement> result = (Set<RootElement>) converter.read(rootElementSetType, null, inputMessage); assertEquals("Invalid result", 2, result.size()); assertTrue("Invalid result", result.contains(new RootElement("1"))); assertTrue("Invalid result", result.contains(new RootElement("2"))); } @Test @SuppressWarnings("unchecked") public void readXmlTypeList() throws Exception { String content = "<list><foo s=\"1\"/><bar s=\"2\"/></list>"; MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); List<TestType> result = (List<TestType>) converter.read(typeListType, null, inputMessage); assertEquals("Invalid result", 2, result.size()); assertEquals("Invalid result", "1", result.get(0).s); assertEquals("Invalid result", "2", result.get(1).s); } @Test @SuppressWarnings("unchecked") public void readXmlTypeSet() throws Exception { String content = "<set><foo s=\"1\"/><bar s=\"2\"/></set>"; MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); Set<TestType> result = (Set<TestType>) converter.read(typeSetType, null, inputMessage); assertEquals("Invalid result", 2, result.size()); assertTrue("Invalid result", result.contains(new TestType("1"))); assertTrue("Invalid result", result.contains(new TestType("2"))); } @Test @SuppressWarnings("unchecked") public void readXmlRootElementExternalEntityDisabled() throws Exception { Resource external = new ClassPathResource("external.txt", getClass()); String content = "<!DOCTYPE root [" + " <!ELEMENT external ANY >\n" + " <!ENTITY ext SYSTEM \"" + external.getURI() + "\" >]>" + " <list><rootElement><type s=\"1\"/><external>&ext;</external></rootElement></list>"; MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); converter = new Jaxb2CollectionHttpMessageConverter<Collection<Object>>() { @Override protected XMLInputFactory createXmlInputFactory() { XMLInputFactory inputFactory = super.createXmlInputFactory(); inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, true); return inputFactory; } }; try { Collection<RootElement> result = converter.read(rootElementListType, null, inputMessage); assertEquals(1, result.size()); assertEquals("", result.iterator().next().external); } catch (HttpMessageNotReadableException ex) { // Some parsers raise an exception } } @Test @SuppressWarnings("unchecked") public void readXmlRootElementExternalEntityEnabled() throws Exception { Resource external = new ClassPathResource("external.txt", getClass()); String content = "<!DOCTYPE root [" + " <!ELEMENT external ANY >\n" + " <!ENTITY ext SYSTEM \"" + external.getURI() + "\" >]>" + " <list><rootElement><type s=\"1\"/><external>&ext;</external></rootElement></list>"; MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); Jaxb2CollectionHttpMessageConverter<?> c = new Jaxb2CollectionHttpMessageConverter<Collection<Object>>() { @Override protected XMLInputFactory createXmlInputFactory() { XMLInputFactory inputFactory = XMLInputFactory.newInstance(); inputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true); return inputFactory; } }; Collection<RootElement> result = c.read(rootElementListType, null, inputMessage); assertEquals(1, result.size()); assertEquals("Foo Bar", result.iterator().next().external); } @Test public void testXmlBomb() throws Exception { // https://en.wikipedia.org/wiki/Billion_laughs // https://msdn.microsoft.com/en-us/magazine/ee335713.aspx String content = "<?xml version=\"1.0\"?>\n" + "<!DOCTYPE lolz [\n" + " <!ENTITY lol \"lol\">\n" + " <!ELEMENT lolz (#PCDATA)>\n" + " <!ENTITY lol1 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\">\n" + " <!ENTITY lol2 \"&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;\">\n" + " <!ENTITY lol3 \"&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;\">\n" + " <!ENTITY lol4 \"&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;\">\n" + " <!ENTITY lol5 \"&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;\">\n" + " <!ENTITY lol6 \"&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;\">\n" + " <!ENTITY lol7 \"&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;\">\n" + " <!ENTITY lol8 \"&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;\">\n" + " <!ENTITY lol9 \"&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;\">\n" + "]>\n" + "<list><rootElement><external>&lol9;</external></rootElement></list>"; MockHttpInputMessage inputMessage = new MockHttpInputMessage(content.getBytes("UTF-8")); this.thrown.expect(HttpMessageNotReadableException.class); this.thrown.expectMessage("\"lol9\""); this.converter.read(this.rootElementListType, null, inputMessage); } @XmlRootElement public static class RootElement { public RootElement() { } public RootElement(String s) { this.type = new TestType(s); } @XmlElement public TestType type = new TestType(); @XmlElement(required=false) public String external; @Override public boolean equals(Object o) { if (this == o) { return true; } if (o instanceof RootElement) { RootElement other = (RootElement) o; return this.type.equals(other.type); } return false; } @Override public int hashCode() { return type.hashCode(); } } @XmlType public static class TestType { public TestType() { } public TestType(String s) { this.s = s; } @XmlAttribute public String s = "Hello World"; @Override public boolean equals(Object o) { if (this == o) { return true; } if (o instanceof TestType) { TestType other = (TestType) o; return this.s.equals(other.s); } return false; } @Override public int hashCode() { return s.hashCode(); } } }