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.lang.reflect.Field;
32
33 import javax.xml.XMLConstants;
34 import javax.xml.parsers.DocumentBuilder;
35 import javax.xml.parsers.DocumentBuilderFactory;
36 import javax.xml.parsers.ParserConfigurationException;
37
38 import org.junit.jupiter.api.Assumptions;
39 import org.junit.jupiter.api.Tag;
40 import org.junit.jupiter.api.Test;
41 import org.junit.jupiter.api.condition.DisabledInNativeImage;
42
43 @Tag("dom")
44 class SecureDocumentBuilderFactoryTest {
45
46
47
48
49 public static final class MockDocumentBuilderFactory extends DocumentBuilderFactory {
50
51 private static DocumentBuilderFactory delegate;
52
53 @Override
54 public Object getAttribute(final String name) {
55 return null;
56 }
57
58 @Override
59 public boolean getFeature(final String name) {
60 return false;
61 }
62
63 @Override
64 public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
65 return delegate.newDocumentBuilder();
66 }
67
68 @Override
69 public void setAttribute(final String name, final Object value) {
70
71 }
72
73 @Override
74 public void setFeature(final String name, final boolean value) {
75
76 }
77 }
78
79
80
81
82 private static final String FACTORY_ID = "javax.xml.parsers.DocumentBuilderFactory";
83
84
85
86
87
88
89
90 private static DocumentBuilderFactory getDelegate(final DocumentBuilderFactory factory) throws ReflectiveOperationException {
91 final Field delegate = factory.getClass().getDeclaredField("delegate");
92 delegate.setAccessible(true);
93 return (DocumentBuilderFactory) delegate.get(factory);
94 }
95
96
97
98
99
100
101
102 private static String setFactoryIdProperty(final String factoryClassName) {
103 final String previous = System.getProperty(FACTORY_ID);
104 if (factoryClassName == null) {
105 System.clearProperty(FACTORY_ID);
106 } else {
107 System.setProperty(FACTORY_ID, factoryClassName);
108 }
109 return previous;
110 }
111
112 @Test
113 void createsSecureBuildersFromEveryStaticEntryPoint() throws Exception {
114 Assumptions.assumeTrue(AttackTestSupport.DOM_RESOLVES_INTERNAL_ENTITIES, "the platform DOM is left unwrapped: it does not resolve user-defined entities");
115 assertInstanceOf(SecureDocumentBuilder.class, SecureDocumentBuilderFactory.newInstance().newDocumentBuilder());
116 assertInstanceOf(SecureDocumentBuilder.class, SecureDocumentBuilderFactory.newDefaultInstance().newDocumentBuilder());
117 assertInstanceOf(SecureDocumentBuilder.class, SecureDocumentBuilderFactory.newNSInstance().newDocumentBuilder());
118 assertInstanceOf(SecureDocumentBuilder.class, SecureDocumentBuilderFactory.newDefaultNSInstance().newDocumentBuilder());
119 }
120
121 @Test
122 void createsBuildersDirectly() {
123 final DocumentBuilder builder = SecureDocumentBuilderFactory.newNSDocumentBuilder();
124 assertTrue(builder.isNamespaceAware());
125 if (AttackTestSupport.DOM_RESOLVES_INTERNAL_ENTITIES) {
126 assertInstanceOf(SecureDocumentBuilder.class, builder);
127 }
128 }
129
130 @Test
131
132 @DisabledInNativeImage
133 void newDocumentBuilderWrapsDeclaredExceptions() throws Exception {
134 Assumptions.assumeFalse(AttackTestSupport.IS_ANDROID, "Skipped on Android: parser selection is pinned to the platform implementation");
135 final String previous = setFactoryIdProperty(MockDocumentBuilderFactory.class.getName());
136 try {
137 final ParserConfigurationException cause = new ParserConfigurationException("test");
138 MockDocumentBuilderFactory.delegate = mock(DocumentBuilderFactory.class);
139 when(MockDocumentBuilderFactory.delegate.newDocumentBuilder()).thenThrow(cause);
140 assertSame(cause, assertThrows(IllegalStateException.class, SecureDocumentBuilderFactory::newNSDocumentBuilder).getCause());
141 } finally {
142 setFactoryIdProperty(previous);
143 MockDocumentBuilderFactory.delegate = null;
144 }
145 }
146
147 @Test
148 void explicitFactoryClassSelectsThatImplementation() throws Exception {
149 Assumptions.assumeFalse(AttackTestSupport.IS_ANDROID, "Skipped on Android: the platform factory is used unwrapped");
150 final Class<?> discovered = DocumentBuilderFactory.newInstance().getClass();
151 final DocumentBuilderFactory factory = SecureDocumentBuilderFactory.newNSInstance(discovered.getName(), null);
152 assertEquals(discovered, getDelegate(factory).getClass());
153 assertTrue(factory.isNamespaceAware());
154 }
155
156 @Test
157 void forwardsEverySupportedFactoryConfiguration() throws Exception {
158 Assumptions.assumeTrue(AttackTestSupport.DOM_RESOLVES_INTERNAL_ENTITIES, "the platform DOM is left unwrapped: it does not resolve user-defined entities");
159 final DocumentBuilderFactory factory = SecureDocumentBuilderFactory.newInstance();
160 factory.setCoalescing(true);
161 factory.setExpandEntityReferences(false);
162 factory.setIgnoringComments(true);
163 factory.setIgnoringElementContentWhitespace(true);
164 factory.setNamespaceAware(true);
165 factory.setValidating(false);
166 factory.setXIncludeAware(false);
167 factory.setSchema(null);
168 factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
169 factory.setAttribute(TestConstants.JAXP_SCHEMA_LANGUAGE, XMLConstants.W3C_XML_SCHEMA_NS_URI);
170 assertEquals(XMLConstants.W3C_XML_SCHEMA_NS_URI, factory.getAttribute(TestConstants.JAXP_SCHEMA_LANGUAGE));
171 assertTrue(factory.isCoalescing());
172 assertFalse(factory.isExpandEntityReferences());
173 assertTrue(factory.isIgnoringComments());
174 assertTrue(factory.isIgnoringElementContentWhitespace());
175 assertTrue(factory.isNamespaceAware());
176 assertFalse(factory.isValidating());
177 assertFalse(factory.isXIncludeAware());
178 assertNull(factory.getSchema());
179 assertTrue(factory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING));
180 assertNotNull(factory.newDocumentBuilder());
181 }
182
183 @Test
184 void newNSInstanceFollowsParserSelection() throws Exception {
185 Assumptions.assumeFalse(AttackTestSupport.IS_ANDROID, "Skipped on Android: the platform factory is used unwrapped");
186 final Class<?> discovered = DocumentBuilderFactory.newInstance().getClass();
187
188 assertEquals(SecureDocumentBuilderFactory.JDK_DOCUMENT_BUILDER_FACTORY,
189 getDelegate(SecureDocumentBuilderFactory.newNSInstance(false)).getClass().getName());
190 assertEquals(discovered, getDelegate(SecureDocumentBuilderFactory.newNSInstance(true)).getClass());
191
192 final String previous = setFactoryIdProperty(discovered.getName());
193 try {
194 assertEquals(discovered, getDelegate(SecureDocumentBuilderFactory.newNSInstance(false)).getClass());
195 assertEquals(discovered, getDelegate(SecureDocumentBuilderFactory.newNSInstance(true)).getClass());
196 } finally {
197 setFactoryIdProperty(previous);
198 }
199 }
200 }