View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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.assertInstanceOf;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertSame;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  
27  import java.io.StringReader;
28  import java.util.HashMap;
29  import java.util.Map;
30  import java.util.Properties;
31  
32  import javax.xml.XMLConstants;
33  import javax.xml.parsers.DocumentBuilderFactory;
34  import javax.xml.transform.ErrorListener;
35  import javax.xml.transform.Source;
36  import javax.xml.transform.Templates;
37  import javax.xml.transform.Transformer;
38  import javax.xml.transform.TransformerConfigurationException;
39  import javax.xml.transform.TransformerFactory;
40  import javax.xml.transform.URIResolver;
41  import javax.xml.transform.dom.DOMSource;
42  import javax.xml.transform.sax.SAXSource;
43  import javax.xml.transform.sax.SAXTransformerFactory;
44  import javax.xml.transform.sax.TemplatesHandler;
45  import javax.xml.transform.sax.TransformerHandler;
46  import javax.xml.transform.stream.StreamSource;
47  
48  import org.junit.jupiter.api.Tag;
49  import org.junit.jupiter.api.Test;
50  import org.xml.sax.InputSource;
51  import org.xml.sax.XMLFilter;
52  
53  /**
54   * Tests {@link SecureTransformerFactory}.
55   */
56  @Tag("trax")
57  class SecureTransformerFactoryTest {
58  
59      private static class NullProductsFactory extends SAXTransformerFactory {
60  
61          private final SAXTransformerFactory delegate = (SAXTransformerFactory) TransformerFactory.newInstance();
62  
63          private final Map<String, Object> attributes = new HashMap<>();
64  
65          @Override
66          public Source getAssociatedStylesheet(final Source source, final String media, final String title, final String charset)
67                  throws TransformerConfigurationException {
68              return delegate.getAssociatedStylesheet(source, media, title, charset);
69          }
70  
71          @Override
72          public Object getAttribute(final String name) {
73              return attributes.get(name);
74          }
75  
76          @Override
77          public ErrorListener getErrorListener() {
78              return delegate.getErrorListener();
79          }
80  
81          @Override
82          public boolean getFeature(final String name) {
83              return delegate.getFeature(name);
84          }
85  
86          @Override
87          public URIResolver getURIResolver() {
88              return delegate.getURIResolver();
89          }
90  
91          @Override
92          public Templates newTemplates(final Source source) {
93              return null;
94          }
95  
96          @Override
97          public TemplatesHandler newTemplatesHandler() {
98              return null;
99          }
100 
101         @Override
102         public Transformer newTransformer() {
103             return null;
104         }
105 
106         @Override
107         public Transformer newTransformer(final Source source) {
108             return null;
109         }
110 
111         @Override
112         public TransformerHandler newTransformerHandler() {
113             return null;
114         }
115 
116         @Override
117         public TransformerHandler newTransformerHandler(final Source source) {
118             return null;
119         }
120 
121         @Override
122         public TransformerHandler newTransformerHandler(final Templates templates) {
123             return null;
124         }
125 
126         @Override
127         public XMLFilter newXMLFilter(final Source source) {
128             return null;
129         }
130 
131         @Override
132         public XMLFilter newXMLFilter(final Templates templates) {
133             return null;
134         }
135 
136         @Override
137         public void setAttribute(final String name, final Object value) {
138             attributes.put(name, value);
139         }
140 
141         @Override
142         public void setErrorListener(final ErrorListener listener) {
143             delegate.setErrorListener(listener);
144         }
145 
146         @Override
147         public void setFeature(final String name, final boolean value) throws TransformerConfigurationException {
148             delegate.setFeature(name, value);
149         }
150 
151         @Override
152         public void setURIResolver(final URIResolver resolver) {
153             delegate.setURIResolver(resolver);
154         }
155     }
156 
157     private static final class RejectingFeatureFactory extends NullProductsFactory {
158 
159         /**
160          * Always throws {@link TransformerConfigurationException}.
161          *
162          * @throws TransformerConfigurationException Thrown on every invocation.
163          */
164         @Override
165         public void setFeature(final String name, final boolean value) throws TransformerConfigurationException {
166             throw new TransformerConfigurationException(name);
167         }
168     }
169 
170     private static void associatedStylesheet(final SAXTransformerFactory factory, final Source source) {
171         try {
172             factory.getAssociatedStylesheet(source, null, null, null);
173         } catch (final TransformerConfigurationException | NullPointerException expected) {
174             // Saxon signals no matching PI with TransformerConfigurationException; Xalan rejects a source without an InputSource.
175         }
176     }
177 
178     private static StreamSource stylesheet() {
179         return new StreamSource(new StringReader(
180                 "<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>" + "<xsl:template match='/'/></xsl:stylesheet>"));
181     }
182 
183     @Test
184     void preservesNullResultsFromEveryWrappableProduct() throws Exception {
185         final SAXTransformerFactory factory = (SAXTransformerFactory) SecureTransformerFactory.secure(new NullProductsFactory());
186         final Templates templates = TransformerFactory.newInstance().newTemplates(stylesheet());
187         assertNull(factory.newTemplates(stylesheet()));
188         assertNull(factory.newTemplatesHandler());
189         assertNull(factory.newTransformer());
190         assertNull(factory.newTransformer(stylesheet()));
191         assertNull(factory.newTransformerHandler());
192         assertNull(factory.newTransformerHandler(stylesheet()));
193         assertNull(factory.newTransformerHandler(templates));
194         // A filter has no null to hand back: a null in this contract would mean the factory has no filters at all.
195         assertThrows(TransformerConfigurationException.class, () -> factory.newXMLFilter(stylesheet()));
196         factory.setAttribute("test", "value");
197         assertEquals("value", factory.getAttribute("test"));
198         factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
199     }
200 
201     @Test
202     void rejectsDelegatesThatCannotEnableSecureProcessing() {
203         assertThrows(SecureException.class, () -> SecureTransformerFactory.secure(new RejectingFeatureFactory()));
204     }
205 
206     @Test
207     void rejectsForeignTemplatesFromNewTransformerHandler() throws Exception {
208         final SAXTransformerFactory factory = (SAXTransformerFactory) SecureTransformerFactory.newInstance();
209         final Templates own = factory.newTemplates(stylesheet());
210         // A caller's own Templates wrapper, the shape a framework uses to carry parameters onto the Transformer it hands out.
211         final Templates foreign = new Templates() {
212 
213             @Override
214             public Properties getOutputProperties() {
215                 return own.getOutputProperties();
216             }
217 
218             @Override
219             public Transformer newTransformer() throws TransformerConfigurationException {
220                 return own.newTransformer();
221             }
222         };
223         // Xalan and XSLTC reject a Templates they did not compile with an undeclared ClassCastException, where Saxon uses the TrAX shape.
224         final TransformerConfigurationException exception = assertThrows(TransformerConfigurationException.class,
225                 () -> factory.newTransformerHandler(foreign));
226         // Saxon reports it in its own words; where the ClassCastException escaped instead, it must arrive as the cause, under a message naming the Templates.
227         if (exception.getCause() instanceof ClassCastException) {
228             assertTrue(exception.getMessage().contains(foreign.getClass().getName()), exception.getMessage());
229         }
230     }
231 
232     @Test
233     void rejectsNullInputOnEveryFactoryMethod() {
234         final SAXTransformerFactory factory = (SAXTransformerFactory) SecureTransformerFactory.newInstance();
235         assertThrows(NullPointerException.class, () -> factory.newTemplates(null));
236         assertThrows(NullPointerException.class, () -> factory.newTransformer(null));
237         assertThrows(NullPointerException.class, () -> factory.newTransformerHandler((Source) null));
238         assertThrows(NullPointerException.class, () -> factory.newTransformerHandler((Templates) null));
239         assertThrows(NullPointerException.class, () -> factory.newXMLFilter((Source) null));
240         assertThrows(NullPointerException.class, () -> factory.newXMLFilter((Templates) null));
241     }
242 
243     @Test
244     void rejectsTemplatesThatProduceNoTransformer() {
245         final Templates templates = new Templates() {
246 
247             @Override
248             public Properties getOutputProperties() {
249                 return new Properties();
250             }
251 
252             @Override
253             public Transformer newTransformer() {
254                 // Xalan hands back null instead of throwing when the stylesheet failed to compile: XALANJ-2410.
255                 return null;
256             }
257         };
258         final SAXTransformerFactory factory = (SAXTransformerFactory) SecureTransformerFactory.newInstance();
259         // A filter has no null to hand back, so the null the wrappers preserve from Templates is reported in the TrAX shape instead.
260         assertThrows(TransformerConfigurationException.class, () -> factory.newXMLFilter(templates));
261     }
262 
263     @Test
264     void securesAssociatedStylesheetSourcesOfEverySupportedShape() throws Exception {
265         final SAXTransformerFactory factory = (SAXTransformerFactory) SecureTransformerFactory.newInstance();
266         associatedStylesheet(factory, new StreamSource(new StringReader("<root/>")));
267         associatedStylesheet(factory, new StreamSource(new StringReader("<root>")));
268         associatedStylesheet(factory, new SAXSource(new InputSource(new StringReader("<root/>"))));
269         associatedStylesheet(factory, new SAXSource());
270         associatedStylesheet(factory,
271                 new SAXSource(SecureSAXParserFactory.newXMLReader(false), new InputSource(new StringReader("<root/>"))));
272         associatedStylesheet(factory,
273                 new DOMSource(DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()));
274     }
275 
276     @Test
277     void wrapsClassCastExceptionFromNewTransformerHandler() throws Exception {
278         final ClassCastException cause = new ClassCastException("Unsupported Templates implementation");
279         final SAXTransformerFactory factory = (SAXTransformerFactory) SecureTransformerFactory.secure(new NullProductsFactory() {
280 
281             /**
282              * Always throws {@link ClassCastException}.
283              *
284              * @throws ClassCastException Thrown on every invocation.
285              */
286             @Override
287             public TransformerHandler newTransformerHandler(final Templates templates) {
288                 throw cause;
289             }
290         });
291         final Templates templates = TransformerFactory.newInstance().newTemplates(stylesheet());
292         final TransformerConfigurationException exception = assertThrows(TransformerConfigurationException.class,
293                 () -> factory.newTransformerHandler(templates));
294         assertSame(cause, exception.getCause());
295         assertTrue(exception.getMessage().contains(templates.getClass().getName()), exception.getMessage());
296     }
297 
298     @Test
299     void wrapsEveryStandardAndSaxFactoryProduct() throws Exception {
300         final SAXTransformerFactory factory = (SAXTransformerFactory) SecureTransformerFactory.newInstance();
301         final URIResolver resolver = (href, base) -> null;
302         factory.setURIResolver(resolver);
303         assertSame(resolver, factory.getURIResolver());
304         factory.setErrorListener(factory.getErrorListener());
305         try {
306             factory.setAttribute("indent-number", 2);
307             // XSLTC quirk: the attribute is settable but not readable.
308             assertThrows(IllegalArgumentException.class, () -> factory.getAttribute("indent-number"));
309         } catch (final IllegalArgumentException e) {
310             // Saxon and Xalan reject the XSLTC-only attribute at set time; the delegate's rejection is itself the forwarding proof.
311         }
312         final Templates templates = factory.newTemplates(stylesheet());
313         assertInstanceOf(SecureTemplates.class, templates);
314         assertInstanceOf(SecureTransformer.class, factory.newTransformer());
315         assertInstanceOf(SecureTransformer.class, factory.newTransformer(stylesheet()));
316         assertInstanceOf(SecureTemplatesHandler.class, factory.newTemplatesHandler());
317         assertInstanceOf(SecureTransformerHandler.class, factory.newTransformerHandler());
318         assertInstanceOf(SecureTransformerHandler.class, factory.newTransformerHandler(stylesheet()));
319         assertInstanceOf(SecureTransformerHandler.class, factory.newTransformerHandler(templates));
320         assertInstanceOf(SecureXMLFilter.class, factory.newXMLFilter(stylesheet()));
321         assertInstanceOf(SecureXMLFilter.class, factory.newXMLFilter(templates));
322         final Templates rawTemplates = TransformerFactory.newInstance().newTemplates(stylesheet());
323         assertInstanceOf(SecureXMLFilter.class, factory.newXMLFilter(rawTemplates));
324         associatedStylesheet(factory, new StreamSource(new StringReader("<root/>")));
325         assertInstanceOf(SecureTransformer.class, SecureTransformerFactory.newInstance().newTransformer());
326     }
327 }