1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.xml.secure;
19
20 import static org.junit.jupiter.api.Assertions.assertEquals;
21 import static org.junit.jupiter.api.Assertions.assertFalse;
22 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
23 import static org.junit.jupiter.api.Assertions.assertNotNull;
24 import static org.junit.jupiter.api.Assertions.assertNull;
25 import static org.junit.jupiter.api.Assertions.assertSame;
26 import static org.junit.jupiter.api.Assertions.assertThrows;
27 import static org.junit.jupiter.api.Assertions.assertTrue;
28 import static org.mockito.Mockito.mock;
29 import static org.mockito.Mockito.when;
30
31 import java.io.StringReader;
32 import java.lang.reflect.Field;
33
34 import javax.xml.XMLConstants;
35 import javax.xml.parsers.ParserConfigurationException;
36 import javax.xml.parsers.SAXParser;
37 import javax.xml.parsers.SAXParserFactory;
38 import javax.xml.transform.Source;
39 import javax.xml.transform.dom.DOMSource;
40 import javax.xml.transform.sax.SAXSource;
41 import javax.xml.transform.stream.StreamSource;
42
43 import org.junit.jupiter.api.Assumptions;
44 import org.junit.jupiter.api.Tag;
45 import org.junit.jupiter.api.Test;
46 import org.junit.jupiter.api.condition.DisabledInNativeImage;
47 import org.xml.sax.InputSource;
48 import org.xml.sax.SAXException;
49 import org.xml.sax.XMLReader;
50 import org.xml.sax.helpers.DefaultHandler;
51
52 @Tag("sax")
53 public class SecureSAXParserFactoryTest {
54
55
56
57
58 public static final class MockSAXParserFactory extends SAXParserFactory {
59
60 private static SAXParserFactory delegate;
61
62 @Override
63 public boolean getFeature(final String name) {
64 return false;
65 }
66
67 @Override
68 public SAXParser newSAXParser() throws ParserConfigurationException, org.xml.sax.SAXException {
69 return delegate.newSAXParser();
70 }
71
72 @Override
73 public void setFeature(final String name, final boolean value) {
74
75 }
76 }
77
78
79
80
81 private static final String FACTORY_ID = "javax.xml.parsers.SAXParserFactory";
82
83 private static final String NAMESPACES_FEATURE = "http://xml.org/sax/features/namespaces";
84
85
86
87
88
89
90
91 private static void assertNewXmlReaderWraps(final Exception cause, final SAXParserFactory delegate) {
92 MockSAXParserFactory.delegate = delegate;
93 assertSame(cause, assertThrows(IllegalStateException.class, () -> SecureSAXParserFactory.newXMLReader(false)).getCause());
94 assertSame(cause, assertThrows(IllegalStateException.class, () -> SecureSAXParserFactory.newNSXMLReader()).getCause());
95 assertSame(cause, assertThrows(IllegalStateException.class, () -> SecureSAXParserFactory.newNSXMLReader(null)).getCause());
96 }
97
98
99
100
101
102
103
104 private static SAXParserFactory getDelegate(final SAXParserFactory factory) throws ReflectiveOperationException {
105 final Field delegate = factory.getClass().getDeclaredField("delegate");
106 delegate.setAccessible(true);
107 return (SAXParserFactory) delegate.get(factory);
108 }
109
110
111
112
113
114
115
116 private static String setFactoryIdProperty(final String factoryClassName) {
117 final String previous = System.getProperty(FACTORY_ID);
118 if (factoryClassName == null) {
119 System.clearProperty(FACTORY_ID);
120 } else {
121 System.setProperty(FACTORY_ID, factoryClassName);
122 }
123 return previous;
124 }
125
126 @Test
127 void createsSecureParsersFromEveryStaticEntryPoint() throws Exception {
128 assertNotNull(SecureSAXParserFactory.newInstance().newSAXParser());
129 assertNotNull(SecureSAXParserFactory.newDefaultInstance().newSAXParser());
130 assertNotNull(SecureSAXParserFactory.newNSInstance().newSAXParser());
131 assertNotNull(SecureSAXParserFactory.newDefaultNSInstance().newSAXParser());
132 }
133
134 @Test
135 void createsParsersAndReadersDirectly() throws Exception {
136 final SAXParser parser = SecureSAXParserFactory.newNSSAXParser();
137 assertInstanceOf(SecureSAXParser.class, parser);
138 assertTrue(parser.isNamespaceAware());
139 final DefaultHandler handler = new DefaultHandler();
140 final XMLReader reader = SecureSAXParserFactory.newNSXMLReader(handler);
141 assertInstanceOf(SecureXMLReader.class, reader);
142 assertSame(handler, reader.getContentHandler());
143 assertTrue(reader.getFeature(NAMESPACES_FEATURE));
144 final XMLReader noHandlerReader = SecureSAXParserFactory.newNSXMLReader();
145 assertInstanceOf(SecureXMLReader.class, noHandlerReader);
146 assertNull(noHandlerReader.getContentHandler());
147 assertTrue(noHandlerReader.getFeature(NAMESPACES_FEATURE));
148 assertNull(SecureSAXParserFactory.newNSXMLReader(null).getContentHandler());
149 }
150
151 @Test
152 void forwardsFactoryConfigurationAndCreatesNamespaceAwareParsers() throws Exception {
153 final SAXParserFactory factory = SecureSAXParserFactory.newInstance();
154 factory.setNamespaceAware(true);
155 factory.setValidating(false);
156 if (AttackTestSupport.SAX_SUPPORTS_XINCLUDE) {
157 factory.setXIncludeAware(false);
158 assertFalse(factory.isXIncludeAware());
159 }
160 if (AttackTestSupport.SAX_SUPPORTS_SCHEMA) {
161 factory.setSchema(null);
162 assertNull(factory.getSchema());
163 }
164 if (AttackTestSupport.SAX_SUPPORTS_SECURE_PROCESSING) {
165 factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
166 assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
167 }
168 assertTrue(factory.isNamespaceAware());
169 assertFalse(factory.isValidating());
170 assertInstanceOf(SecureSAXParser.class, factory.newSAXParser());
171 }
172
173 @Test
174 void leavesReadersSecureOnlyOnce() {
175 final XMLReader reader = SecureSAXParserFactory.newXMLReader(false);
176 assertSame(reader, SecureSAXParserFactory.secure(reader));
177 }
178
179 @Test
180 void newNSInstanceFollowsParserSelection() throws Exception {
181 Assumptions.assumeFalse(AttackTestSupport.IS_ANDROID, "Skipped on Android: parser selection is pinned to the platform implementation");
182 final Class<?> discovered = SAXParserFactory.newInstance().getClass();
183
184 assertEquals(SecureSAXParserFactory.JDK_SAX_PARSER_FACTORY, getDelegate(SecureSAXParserFactory.newNSInstance(false)).getClass().getName());
185 assertEquals(discovered, getDelegate(SecureSAXParserFactory.newNSInstance(true)).getClass());
186
187 final String previous = setFactoryIdProperty(discovered.getName());
188 try {
189 assertEquals(discovered, getDelegate(SecureSAXParserFactory.newNSInstance(false)).getClass());
190 assertEquals(discovered, getDelegate(SecureSAXParserFactory.newNSInstance(true)).getClass());
191 } finally {
192 setFactoryIdProperty(previous);
193 }
194 }
195
196 @Test
197 void newXmlReaderFollowsParserSelection() throws Exception {
198 Assumptions.assumeFalse(AttackTestSupport.IS_ANDROID, "Skipped on Android: parser selection is pinned to the platform implementation");
199 final Class<?> discovered = SAXParserFactory.newInstance().newSAXParser().getXMLReader().getClass();
200 assertEquals(discovered, ((SecureXMLReader) SecureSAXParserFactory.newXMLReader(true)).getDelegate().getClass());
201 final Class<?> jdkReader =
202 SAXParserFactory.newInstance(SecureSAXParserFactory.JDK_SAX_PARSER_FACTORY, null).newSAXParser().getXMLReader().getClass();
203 assertEquals(jdkReader, ((SecureXMLReader) SecureSAXParserFactory.newXMLReader(false)).getDelegate().getClass());
204 final String previous = setFactoryIdProperty(SAXParserFactory.newInstance().getClass().getName());
205 try {
206 assertEquals(discovered, ((SecureXMLReader) SecureSAXParserFactory.newXMLReader(false)).getDelegate().getClass());
207 } finally {
208 setFactoryIdProperty(previous);
209 }
210 }
211
212 @Test
213
214
215 @DisabledInNativeImage
216 void newXmlReaderWrapsDeclaredExceptions() throws Exception {
217 Assumptions.assumeFalse(AttackTestSupport.IS_ANDROID, "Skipped on Android: parser selection is pinned to the platform implementation");
218 final String previous = setFactoryIdProperty(MockSAXParserFactory.class.getName());
219 try {
220
221 final ParserConfigurationException notConfigurable = new ParserConfigurationException("test");
222 SAXParserFactory factory = mock(SAXParserFactory.class);
223 when(factory.newSAXParser()).thenThrow(notConfigurable);
224 assertNewXmlReaderWraps(notConfigurable, factory);
225 assertSame(notConfigurable, assertThrows(IllegalStateException.class, SecureSAXParserFactory::newNSSAXParser).getCause());
226 final SAXException noParser = new SAXException("test");
227 factory = mock(SAXParserFactory.class);
228 when(factory.newSAXParser()).thenThrow(noParser);
229 assertNewXmlReaderWraps(noParser, factory);
230 assertSame(noParser, assertThrows(IllegalStateException.class, SecureSAXParserFactory::newNSSAXParser).getCause());
231
232 final SAXException noReader = new SAXException("test");
233 factory = mock(SAXParserFactory.class);
234 final SAXParser parser = mock(SAXParser.class);
235 when(factory.newSAXParser()).thenReturn(parser);
236 when(parser.getXMLReader()).thenThrow(noReader);
237 assertNewXmlReaderWraps(noReader, factory);
238 } finally {
239 setFactoryIdProperty(previous);
240 MockSAXParserFactory.delegate = null;
241 }
242 }
243
244 @Test
245 void securesOnlySourcesThatNeedAReader() {
246 final StreamSource stream = new StreamSource(new StringReader("<root/>"));
247 final Source securedStream = SecureSAXParserFactory.secure(stream, false);
248 assertInstanceOf(SAXSource.class, securedStream);
249 assertInstanceOf(SecureXMLReader.class, ((SAXSource) securedStream).getXMLReader());
250 final SAXSource readerless = new SAXSource(new InputSource(new StringReader("<root/>")));
251 assertInstanceOf(SAXSource.class, SecureSAXParserFactory.secure(readerless, true));
252 final SAXSource empty = new SAXSource();
253 assertSame(empty, SecureSAXParserFactory.secure(empty, false));
254 final DOMSource dom = new DOMSource();
255 assertSame(dom, SecureSAXParserFactory.secure(dom, false));
256 final SAXSource suppliedReader = new SAXSource(SecureSAXParserFactory.newXMLReader(false), new InputSource());
257 assertSame(suppliedReader, SecureSAXParserFactory.secure(suppliedReader, false));
258 }
259 }