/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.wink.common.internal.providers.jaxb;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Providers;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.stream.StreamSource;
import org.apache.wink.common.RuntimeContext;
import org.apache.wink.common.internal.CaseInsensitiveMultivaluedMap;
import org.apache.wink.common.internal.WinkConfiguration;
import org.apache.wink.common.internal.contexts.MediaTypeCharsetAdjuster;
import org.apache.wink.common.internal.providers.entity.xml.JAXBXmlProvider;
import org.apache.wink.common.internal.providers.jaxb.jaxb1.AddNumbers;
import org.apache.wink.common.internal.providers.jaxb.jaxb1.MyPojo;
import org.apache.wink.common.internal.runtime.RuntimeContextTLS;
import org.apache.wink.common.model.JAXBUnmarshalOptions;
import org.apache.wink.common.model.XmlFormattingOptions;
import org.jmock.Expectations;
import org.jmock.integration.junit3.MockObjectTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class ProvidersJAXBTest extends MockObjectTestCase {
public class MyJAXBXmlProvider extends JAXBXmlProvider {
MyJAXBXmlProvider() {
super();
providers = ProvidersJAXBTest.this.providers;
}
/*
* simulate what would happen if application had supplied a JAXBContext provider
*/
@Override
protected JAXBContext getContext(Class<?> type, MediaType mediaType)
throws JAXBException {
// use JAXBContext.newInstance(String). The default in AbstractJAXBProvider is JAXBContext.newInstance(Class)
return JAXBContext.newInstance(type.getPackage().getName());
}
}
static final String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<ns2:addNumbers xmlns:ns2=\"http://org/apache/wink/common/internal/providers/jaxb/jaxb1\">" +
"<ns2:arg0>1</ns2:arg0>" +
"<ns2:arg1>2</ns2:arg1>" +
"</ns2:addNumbers>";
static final String expectedXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<addNumbers xmlns=\"http://org/apache/wink/common/internal/providers/jaxb/jaxb1\">" +
"<arg0>0</arg0>" +
"<arg1>0</arg1>" +
"</addNumbers>";
static String path = null;
static {
String classpath = System.getProperty("java.class.path");
StringTokenizer tokenizer = new StringTokenizer(classpath, System.getProperty("path.separator"));
while (tokenizer.hasMoreTokens()) {
path = tokenizer.nextToken();
if (path.endsWith("test-classes")) {
break;
}
}
// for windows:
int driveIndex = path.indexOf(":");
if(driveIndex != -1) {
path = path.substring(driveIndex + 1);
}
}
static final String xmlWithDTD = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<!DOCTYPE data [<!ENTITY file SYSTEM \"file:"+ path + "/etc/ProvidersJAXBTest.txt\">]>" +
"<ns2:addNumbers xmlns:ns2=\"http://org/apache/wink/common/internal/providers/jaxb/jaxb1\">" +
"<ns2:arg0>&file;</ns2:arg0>" +
"<ns2:arg1>2</ns2:arg1>" +
"</ns2:addNumbers>";
static final String xmlMyPojoWithDTD = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<!DOCTYPE data [<!ENTITY file SYSTEM \"file:"+ path +"/etc/ProvidersJAXBTest.txt\">]>" +
"<ns2:myPojo xmlns:ns2=\"http://org/apache/wink/common/internal/providers/jaxb/jaxb1\">" +
"<ns2:stringdata>&file;</ns2:stringdata>" +
"</ns2:myPojo>";
static final String xmlWithDTDEntityExpansionAttack = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<!DOCTYPE root [" +
"<!ENTITY % a \"x\">" +
"<!ENTITY % b \"%a;%a;\">" +
"]>" +
"<ns2:addNumbers xmlns:ns2=\"http://org/apache/wink/common/internal/providers/jaxb/jaxb1\">" +
"<ns2:arg0>&b;</ns2:arg0>" +
"<ns2:arg1>2</ns2:arg1>" +
"</ns2:addNumbers>";
static final String xmlWithDTDEntityExpansionAttack2 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<!DOCTYPE root [" +
"<!ENTITY x32 \"foobar\">" +
"<!ENTITY x31 \"&x32;&x32;\">" +
"<!ENTITY x30 \"&x31;&x31;\">" +
"<!ENTITY x29 \"&x30;&x30;\">" +
"<!ENTITY x28 \"&x29;&x29;\">" +
"<!ENTITY x27 \"&x28;&x28;\">" +
"<!ENTITY x26 \"&x27;&x27;\">" +
"<!ENTITY x25 \"&x26;&x26;\">" +
"<!ENTITY x24 \"&x25;&x25;\">" +
"<!ENTITY x23 \"&x24;&x24;\">" +
"<!ENTITY x22 \"&x23;&x23;\">" +
"<!ENTITY x21 \"&x22;&x22;\">" +
"<!ENTITY x20 \"&x21;&x21;\">" +
"<!ENTITY x19 \"&x20;&x20;\">" +
"<!ENTITY x18 \"&x19;&x19;\">" +
"<!ENTITY x17 \"&x18;&x18;\">" +
"<!ENTITY x16 \"&x17;&x17;\">" +
"<!ENTITY x15 \"&x16;&x16;\">" +
"<!ENTITY x14 \"&x15;&x15;\">" +
"<!ENTITY x13 \"&x14;&x14;\">" +
"<!ENTITY x12 \"&x13;&x13;\">" +
"<!ENTITY x11 \"&x12;&x12;\">" +
"<!ENTITY x10 \"&x11;&x11;\">" +
"<!ENTITY x9 \"&x10;&x10;\">" +
"<!ENTITY x8 \"&x9;&x9;\">" +
"<!ENTITY x7 \"&x8;&x8;\">" +
"<!ENTITY x6 \"&x7;&x7;\">" +
"<!ENTITY x5 \"&x6;&x6;\">" +
"<!ENTITY x4 \"&x5;&x5;\">" +
"<!ENTITY x3 \"&x4;&x4;\">" +
"<!ENTITY x2 \"&x3;&x3;\">" +
"<!ENTITY x1 \"&x2;&x2;\">" +
"]>" +
"<ns2:addNumbers xmlns:ns2=\"http://org/apache/wink/common/internal/providers/jaxb/jaxb1\">" +
"<ns2:arg0>&x1;</ns2:arg0>" +
"<ns2:arg1>2</ns2:arg1>" +
"</ns2:addNumbers>";
private WinkConfiguration winkConfiguration = null;
private MessageBodyReader jaxbProviderReader = null;
private MessageBodyWriter jaxbProviderWriter = null;
private Providers providers;
public class MyJAXBContextResolver implements ContextResolver<JAXBContext> {
public JAXBContext getContext(Class<?> arg0) {
try {
return JAXBContext.newInstance(arg0);
} catch (JAXBException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
@Before
public void setUp() {
providers = mock(Providers.class);
final RuntimeContext runtimeContext = mock(RuntimeContext.class);
winkConfiguration = mock(WinkConfiguration.class);
checking(new Expectations() {{
allowing(providers).getContextResolver(JAXBContext.class, MediaType.TEXT_XML_TYPE); will(returnValue(new MyJAXBContextResolver()));
allowing(providers).getContextResolver(XmlFormattingOptions.class, MediaType.TEXT_XML_TYPE); will(returnValue(null));
allowing(providers).getContextResolver(JAXBUnmarshalOptions.class, MediaType.TEXT_XML_TYPE); will(returnValue(null));
allowing(runtimeContext).getAttribute(MediaTypeCharsetAdjuster.class); will(returnValue(null));
allowing(runtimeContext).getAttribute(WinkConfiguration.class); will(returnValue(winkConfiguration));
}});
RuntimeContextTLS.setRuntimeContext(runtimeContext);
jaxbProviderReader = new MyJAXBXmlProvider();
jaxbProviderWriter = new MyJAXBXmlProvider();
}
@After
public void tearDown() {
// clean up the mess. :)
RuntimeContextTLS.setRuntimeContext(null);
}
@Test
public void testJAXBUnmarshallingWithAlternateContext1() throws Exception {
final Properties props = new Properties();
checking(new Expectations() {{
allowing(winkConfiguration).getProperties(); will(returnValue(props));
}});
// let's make sure our test string is unmarshallable, just for a good sanity check
JAXBContext testcontext = JAXBContext.newInstance(AddNumbers.class);
Unmarshaller testunmarshaller = testcontext.createUnmarshaller();
AddNumbers testresponse =
(AddNumbers)testunmarshaller.unmarshal(new ByteArrayInputStream(xml.getBytes()));
assertEquals("we could not unmarshal the test xml", 1, testresponse.getArg0());
assertTrue(jaxbProviderReader.isReadable(AddNumbers.class, null, null, MediaType.TEXT_XML_TYPE));
ByteArrayInputStream bais = new ByteArrayInputStream(xml.getBytes());
Object obj = jaxbProviderReader.readFrom(AddNumbers.class, null, null, MediaType.TEXT_XML_TYPE, null, bais);
assertTrue(obj instanceof AddNumbers);
assertEquals(1, ((AddNumbers)obj).getArg0());
assertEquals(2, ((AddNumbers)obj).getArg1());
}
@SuppressWarnings("unchecked")
@Test
public void testJAXBMarshalling() throws WebApplicationException, IOException {
assertTrue(jaxbProviderWriter.isWriteable(AddNumbers.class, null, null, MediaType.TEXT_XML_TYPE));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
jaxbProviderWriter.writeTo(new AddNumbers(), AddNumbers.class, null, null, MediaType.TEXT_XML_TYPE, null, baos);
assertEquals(expectedXml, baos.toString());
}
@SuppressWarnings("unchecked")
@Test
public void testJAXBMarshallingWithMap() throws WebApplicationException, IOException {
assertTrue(jaxbProviderWriter.isWriteable(AddNumbers.class, null, null, MediaType.TEXT_XML_TYPE));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
MultivaluedMap map = new CaseInsensitiveMultivaluedMap<Object>();
jaxbProviderWriter.writeTo(new AddNumbers(), AddNumbers.class, null, null, MediaType.TEXT_XML_TYPE, map, baos);
assertEquals(expectedXml, baos.toString());
}
@Test
public void testJAXBUnmarshallingWithDTD() throws Exception {
final Properties props = new Properties();
checking(new Expectations() {{
allowing(winkConfiguration).getProperties(); will(returnValue(props));
}});
Exception ex = null;
assertTrue(jaxbProviderReader.isReadable(AddNumbers.class, null, null, MediaType.TEXT_XML_TYPE));
try {
ByteArrayInputStream bais = new ByteArrayInputStream(xmlWithDTD.getBytes());
Object obj = jaxbProviderReader.readFrom(AddNumbers.class, null, null, MediaType.TEXT_XML_TYPE, null, bais);
fail("should have got an exception");
} catch (Exception e) {
ex = e;
}
assertTrue("expected an XMLStreamException", ex.getCause() instanceof XMLStreamException);
// let's make sure our test string is unmarshallable, just for a good sanity check
JAXBContext testcontext = JAXBContext.newInstance(AddNumbers.class);
Unmarshaller testunmarshaller = testcontext.createUnmarshaller();
AddNumbers testresponse =
(AddNumbers)testunmarshaller.unmarshal(new ByteArrayInputStream(xmlWithDTD.getBytes()));
assertEquals("we could not unmarshal the test xml", 99999999, testresponse.getArg0());
}
@Test
/**
* testing that supporting DTD expansion is configurable
*/
public void testJAXBUnmarshallingWithDTDServerConfigurable() throws Exception {
// pretend we're on the server:
final Properties props = new Properties();
props.put("wink.supportDTDEntityExpansion", "true");
checking(new Expectations() {{
allowing(winkConfiguration).getProperties(); will(returnValue(props));
}});
assertTrue(jaxbProviderReader.isReadable(AddNumbers.class, null, null, MediaType.TEXT_XML_TYPE));
ByteArrayInputStream bais = new ByteArrayInputStream(xmlWithDTD.getBytes());
Object obj = jaxbProviderReader.readFrom(AddNumbers.class, null, null, MediaType.TEXT_XML_TYPE, null, bais);
assertTrue(obj instanceof AddNumbers);
assertEquals(99999999, ((AddNumbers)obj).getArg0());
assertEquals(2, ((AddNumbers)obj).getArg1());
// let's make sure our test string is unmarshallable, just for a good sanity check
JAXBContext testcontext = JAXBContext.newInstance(AddNumbers.class);
Unmarshaller testunmarshaller = testcontext.createUnmarshaller();
AddNumbers testresponse =
(AddNumbers)testunmarshaller.unmarshal(new ByteArrayInputStream(xmlWithDTD.getBytes()));
assertEquals("we could not unmarshal the test xml", 99999999, testresponse.getArg0());
}
@Test
public void testJAXBUnmarshallingMyPojoWithDTD() throws Exception {
final Properties props = new Properties();
checking(new Expectations() {{
allowing(winkConfiguration).getProperties(); will(returnValue(props));
}});
Exception ex = null;
try {
assertTrue(jaxbProviderReader.isReadable(MyPojo.class, null, null, MediaType.TEXT_XML_TYPE));
ByteArrayInputStream bais = new ByteArrayInputStream(xmlMyPojoWithDTD.getBytes());
Object obj = jaxbProviderReader.readFrom(MyPojo.class, null, null, MediaType.TEXT_XML_TYPE, null, bais);
fail("should have got an exception");
} catch (Exception e) {
ex = e;
}
assertTrue("expected an XMLStreamException", ex.getCause() instanceof XMLStreamException);
// let's make sure our test string is unmarshallable, just for a good sanity check
JAXBContext testcontext = JAXBContext.newInstance(MyPojo.class);
Unmarshaller testunmarshaller = testcontext.createUnmarshaller();
JAXBElement<MyPojo> testresponse =
(JAXBElement<MyPojo>)testunmarshaller.unmarshal(new StreamSource(new ByteArrayInputStream(xmlMyPojoWithDTD.getBytes())), MyPojo.class);
MyPojo myPojo = testresponse.getValue();
assertEquals("we could not unmarshal the test xml", "99999999", myPojo.getStringdata().trim());
}
@Test
public void testEntityExpansionAttack() throws Exception {
final Properties props = new Properties();
checking(new Expectations() {{
allowing(winkConfiguration).getProperties(); will(returnValue(props));
}});
Exception ex = null;
try {
assertTrue(jaxbProviderReader.isReadable(MyPojo.class, null, null, MediaType.TEXT_XML_TYPE));
ByteArrayInputStream bais = new ByteArrayInputStream(xmlWithDTDEntityExpansionAttack.getBytes());
Object obj = jaxbProviderReader.readFrom(MyPojo.class, null, null, MediaType.TEXT_XML_TYPE, null, bais);
fail("should have got an exception");
} catch (Exception e) {
ex = e;
}
assertTrue("expected an XMLStreamException", ex.getCause() instanceof XMLStreamException);
}
@Test(timeout=2000)
public void testEntityExpansionAttack2() throws Exception {
final Properties props = new Properties();
checking(new Expectations() {{
allowing(winkConfiguration).getProperties(); will(returnValue(props));
}});
Exception ex = null;
try {
assertTrue(jaxbProviderReader.isReadable(MyPojo.class, null, null, MediaType.TEXT_XML_TYPE));
ByteArrayInputStream bais = new ByteArrayInputStream(xmlWithDTDEntityExpansionAttack2.getBytes());
Object obj = jaxbProviderReader.readFrom(MyPojo.class, null, null, MediaType.TEXT_XML_TYPE, null, bais);
fail("should have got an exception");
} catch (Exception e) {
ex = e;
}
assertTrue("expected an XMLStreamException", ex.getCause() instanceof XMLStreamException);
}
}