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.assertNotNull;
23  import static org.junit.jupiter.api.Assertions.assertNull;
24  import static org.junit.jupiter.api.Assertions.assertSame;
25  import static org.junit.jupiter.api.Assertions.assertThrows;
26  import static org.junit.jupiter.api.Assertions.assertTrue;
27  
28  import javax.xml.XMLConstants;
29  import javax.xml.validation.SchemaFactory;
30  import javax.xml.validation.TypeInfoProvider;
31  import javax.xml.validation.ValidatorHandler;
32  
33  import org.junit.jupiter.api.Tag;
34  import org.junit.jupiter.api.Test;
35  import org.w3c.dom.ls.LSResourceResolver;
36  import org.xml.sax.Attributes;
37  import org.xml.sax.ContentHandler;
38  import org.xml.sax.ErrorHandler;
39  import org.xml.sax.Locator;
40  import org.xml.sax.SAXNotRecognizedException;
41  import org.xml.sax.SAXNotSupportedException;
42  import org.xml.sax.SAXParseException;
43  import org.xml.sax.helpers.AttributesImpl;
44  import org.xml.sax.helpers.DefaultHandler;
45  
46  @Tag("schema")
47  class SecureValidatorHandlerTest {
48  
49      /**
50       * Minimal recording ValidatorHandler used to verify forwarding without triggering real validation.
51       */
52      private static final class RecordingValidatorHandler extends ValidatorHandler {
53  
54          boolean startDocumentCalled;
55  
56          boolean endDocumentCalled;
57  
58          boolean startPrefixMappingCalled;
59  
60          String startPrefixMappingPrefix;
61  
62          String startPrefixMappingUri;
63  
64          boolean endPrefixMappingCalled;
65  
66          String endPrefixMappingPrefix;
67  
68          boolean startElementCalled;
69  
70          String startElementUri;
71  
72          String startElementLocalName;
73  
74          String startElementQName;
75  
76          Attributes startElementAttrs;
77  
78          boolean charactersCalled;
79  
80          char[] charactersChars;
81  
82          int charactersStart;
83  
84          int charactersLength;
85  
86          boolean ignorableWhitespaceCalled;
87  
88          boolean processingInstructionCalled;
89  
90          String piTarget;
91  
92          String piData;
93  
94          boolean endElementCalled;
95  
96          String endElementUri;
97  
98          String endElementLocalName;
99  
100         String endElementQName;
101 
102         boolean skippedEntityCalled;
103 
104         String skippedEntityName;
105 
106         boolean setDocumentLocatorCalled;
107 
108         boolean setContentHandlerCalled;
109 
110         boolean setErrorHandlerCalled;
111 
112         boolean setFeatureCalled;
113 
114         String setFeatureName;
115 
116         boolean setFeatureValue;
117 
118         boolean setPropertyCalled;
119 
120         String setPropertyName;
121 
122         Object setPropertyValue;
123 
124         ContentHandler contentHandler;
125 
126         ErrorHandler errorHandler;
127 
128         LSResourceResolver resourceResolver;
129 
130         @Override
131         public void characters(final char[] ch, final int start, final int length) {
132             charactersCalled = true;
133             charactersChars = ch.clone();
134             charactersStart = start;
135             charactersLength = length;
136         }
137 
138         @Override
139         public void endDocument() {
140             endDocumentCalled = true;
141         }
142 
143         @Override
144         public void endElement(final String uri, final String localName, final String qName) {
145             endElementCalled = true;
146             endElementUri = uri;
147             endElementLocalName = localName;
148             endElementQName = qName;
149         }
150 
151         @Override
152         public void endPrefixMapping(final String prefix) {
153             endPrefixMappingCalled = true;
154             endPrefixMappingPrefix = prefix;
155         }
156 
157         @Override
158         public ContentHandler getContentHandler() {
159             return contentHandler;
160         }
161 
162         @Override
163         public ErrorHandler getErrorHandler() {
164             return errorHandler;
165         }
166 
167         @Override
168         public boolean getFeature(final String name) {
169             return false;
170         }
171 
172         @Override
173         public Object getProperty(final String name) {
174             return null;
175         }
176 
177         @Override
178         public LSResourceResolver getResourceResolver() {
179             return resourceResolver;
180         }
181 
182         @Override
183         public TypeInfoProvider getTypeInfoProvider() {
184             return null;
185         }
186 
187         @Override
188         public void ignorableWhitespace(final char[] ch, final int start, final int length) {
189             ignorableWhitespaceCalled = true;
190         }
191 
192         @Override
193         public void processingInstruction(final String target, final String data) {
194             processingInstructionCalled = true;
195             piTarget = target;
196             piData = data;
197         }
198 
199         @Override
200         public void setContentHandler(final ContentHandler handler) {
201             setContentHandlerCalled = true;
202             contentHandler = handler;
203         }
204 
205         @Override
206         public void setDocumentLocator(final Locator locator) {
207             setDocumentLocatorCalled = true;
208         }
209 
210         @Override
211         public void setErrorHandler(final ErrorHandler handler) {
212             setErrorHandlerCalled = true;
213             errorHandler = handler;
214         }
215 
216         @Override
217         public void setFeature(final String name, final boolean value) {
218             setFeatureCalled = true;
219             setFeatureName = name;
220             setFeatureValue = value;
221         }
222 
223         @Override
224         public void setProperty(final String name, final Object value) {
225             setPropertyCalled = true;
226             setPropertyName = name;
227             setPropertyValue = value;
228         }
229 
230         @Override
231         public void setResourceResolver(final LSResourceResolver resolver) {
232             resourceResolver = resolver;
233         }
234 
235         @Override
236         public void skippedEntity(final String name) {
237             skippedEntityCalled = true;
238             skippedEntityName = name;
239         }
240 
241         @Override
242         public void startDocument() {
243             startDocumentCalled = true;
244         }
245 
246         @Override
247         public void startElement(final String uri, final String localName, final String qName, final Attributes atts) {
248             startElementCalled = true;
249             startElementUri = uri;
250             startElementLocalName = localName;
251             startElementQName = qName;
252             startElementAttrs = atts;
253         }
254 
255         @Override
256         public void startPrefixMapping(final String prefix, final String uri) {
257             startPrefixMappingCalled = true;
258             startPrefixMappingPrefix = prefix;
259             startPrefixMappingUri = uri;
260         }
261     }
262 
263     private static ValidatorHandler newValidatorHandler() throws Exception {
264         final SchemaFactory factory = SecureSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
265         return factory.newSchema().newValidatorHandler();
266     }
267 
268     @Test
269     void constructorInstallsFloorOnDelegate() throws Exception {
270         final ValidatorHandler delegate = newValidatorHandler();
271         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
272         // The constructor must install the ignore-all floor on the delegate
273         assertNotNull(delegate.getResourceResolver(), "delegate resource resolver must be set to the floor");
274         assertInstanceOf(FallbackIgnoreLSResourceResolver.class, delegate.getResourceResolver(),
275           "delegate resolver must be a FallbackIgnoreLSResourceResolver");
276         // getResourceResolver on the wrapper returns the floor's delegate, which is null initially
277         assertNull(handler.getResourceResolver());
278     }
279 
280     @Test
281     void constructorRejectsNullDelegate() {
282         assertThrows(NullPointerException.class, () -> new SecureValidatorHandler(null));
283     }
284 
285     @Test
286     void delegatesFeature() throws Exception {
287         final ValidatorHandler delegate = newValidatorHandler();
288         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
289         // Verify delegation of getFeature; setFeature may be unsupported on this implementation
290         final String feature = XMLConstants.FEATURE_SECURE_PROCESSING;
291         final boolean delegateValue;
292         try {
293             delegateValue = delegate.getFeature(feature);
294         } catch (SAXNotRecognizedException | SAXNotSupportedException e) {
295             // If the feature is not recognized, both delegate and wrapper should behave the same way
296             assertThrows(SAXNotRecognizedException.class, () -> handler.getFeature(feature));
297             return;
298         }
299         assertEquals(delegateValue, handler.getFeature(feature));
300     }
301 
302     @Test
303     void delegatesGetContentHandler() throws Exception {
304         final ValidatorHandler delegate = newValidatorHandler();
305         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
306         final ContentHandler ch = new DefaultHandler();
307         delegate.setContentHandler(ch);
308         assertSame(ch, handler.getContentHandler());
309     }
310 
311     @Test
312     void delegatesGetErrorHandler() throws Exception {
313         final ValidatorHandler delegate = newValidatorHandler();
314         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
315         final ErrorHandler eh = new ErrorHandler() {
316 
317             @Override
318             public void error(final SAXParseException e) {
319             }
320 
321             @Override
322             public void fatalError(final SAXParseException e) {
323             }
324 
325             @Override
326             public void warning(final SAXParseException e) {
327             }
328         };
329         delegate.setErrorHandler(eh);
330         assertSame(eh, handler.getErrorHandler());
331     }
332 
333     @Test
334     void delegatesProperty() throws Exception {
335         final ValidatorHandler delegate = newValidatorHandler();
336         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
337         // Verify delegation of getProperty; setProperty may be unsupported on this implementation
338         // Spelled out because Android's XMLConstants predates JAXP 1.5 and lacks ACCESS_EXTERNAL_DTD; this file also compiles in android-tests.
339         final String property = "http://XMLConstants/property/accessExternalDTD";
340         final Object delegateValue;
341         try {
342             delegateValue = delegate.getProperty(property);
343         } catch (SAXNotRecognizedException | SAXNotSupportedException e) {
344             assertThrows(SAXNotRecognizedException.class, () -> handler.getProperty(property));
345             return;
346         }
347         assertSame(delegateValue, handler.getProperty(property));
348     }
349 
350     @Test
351     void delegatesSetContentHandler() throws Exception {
352         final ValidatorHandler delegate = newValidatorHandler();
353         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
354         final ContentHandler ch = new DefaultHandler();
355         handler.setContentHandler(ch);
356         assertSame(ch, delegate.getContentHandler());
357     }
358 
359     @Test
360     void delegatesSetDocumentLocator() throws Exception {
361         final ValidatorHandler delegate = newValidatorHandler();
362         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
363         final Locator locator = new Locator() {
364 
365             @Override
366             public int getColumnNumber() {
367                 return 0;
368             }
369 
370             @Override
371             public int getLineNumber() {
372                 return 0;
373             }
374 
375             @Override
376             public String getPublicId() {
377                 return null;
378             }
379 
380             @Override
381             public String getSystemId() {
382                 return null;
383             }
384         };
385         handler.setDocumentLocator(locator);
386         // No exception means forwarding works; we cannot easily verify locator on delegate without exposing it
387     }
388 
389     @Test
390     void delegatesSetErrorHandler() throws Exception {
391         final ValidatorHandler delegate = newValidatorHandler();
392         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
393         final ErrorHandler eh = new ErrorHandler() {
394 
395             @Override
396             public void error(final SAXParseException e) {
397             }
398 
399             @Override
400             public void fatalError(final SAXParseException e) {
401             }
402 
403             @Override
404             public void warning(final SAXParseException e) {
405             }
406         };
407         handler.setErrorHandler(eh);
408         assertSame(eh, delegate.getErrorHandler());
409     }
410 
411     @Test
412     void delegatesTypeInfoProvider() throws Exception {
413         final ValidatorHandler delegate = newValidatorHandler();
414         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
415         assertSame(delegate.getTypeInfoProvider(), handler.getTypeInfoProvider());
416     }
417 
418     @Test
419     void forwardsCharacters() throws Exception {
420         final RecordingValidatorHandler delegate = new RecordingValidatorHandler();
421         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
422         final char[] ch = { 'a', 'b' };
423         handler.characters(ch, 0, 2);
424         assertTrue(delegate.charactersCalled);
425         assertEquals(0, delegate.charactersStart);
426         assertEquals(2, delegate.charactersLength);
427     }
428 
429     @Test
430     void forwardsEndDocument() throws Exception {
431         final RecordingValidatorHandler delegate = new RecordingValidatorHandler();
432         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
433         handler.endDocument();
434         assertTrue(delegate.endDocumentCalled);
435     }
436 
437     @Test
438     void forwardsEndElement() throws Exception {
439         final RecordingValidatorHandler delegate = new RecordingValidatorHandler();
440         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
441         handler.endElement("uri", "local", "qName");
442         assertTrue(delegate.endElementCalled);
443         assertEquals("uri", delegate.endElementUri);
444         assertEquals("local", delegate.endElementLocalName);
445         assertEquals("qName", delegate.endElementQName);
446     }
447 
448     @Test
449     void forwardsEndPrefixMapping() throws Exception {
450         final RecordingValidatorHandler delegate = new RecordingValidatorHandler();
451         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
452         handler.endPrefixMapping("p");
453         assertTrue(delegate.endPrefixMappingCalled);
454         assertEquals("p", delegate.endPrefixMappingPrefix);
455     }
456 
457     @Test
458     void forwardsGetProperty() throws Exception {
459         final RecordingValidatorHandler delegate = new RecordingValidatorHandler();
460         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
461         // getProperty is delegated; RecordingValidatorHandler returns null
462         assertNull(handler.getProperty("any"));
463     }
464 
465     @Test
466     void forwardsIgnorableWhitespace() throws Exception {
467         final RecordingValidatorHandler delegate = new RecordingValidatorHandler();
468         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
469         final char[] ch = { ' ' };
470         handler.ignorableWhitespace(ch, 0, 1);
471         assertTrue(delegate.ignorableWhitespaceCalled);
472     }
473 
474     @Test
475     void forwardsProcessingInstruction() throws Exception {
476         final RecordingValidatorHandler delegate = new RecordingValidatorHandler();
477         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
478         handler.processingInstruction("target", "data");
479         assertTrue(delegate.processingInstructionCalled);
480         assertEquals("target", delegate.piTarget);
481         assertEquals("data", delegate.piData);
482     }
483 
484     @Test
485     void forwardsSetFeature() throws Exception {
486         final RecordingValidatorHandler delegate = new RecordingValidatorHandler();
487         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
488         handler.setFeature("f", true);
489         assertTrue(delegate.setFeatureCalled);
490         assertEquals("f", delegate.setFeatureName);
491         assertTrue(delegate.setFeatureValue);
492     }
493 
494     @Test
495     void forwardsSetProperty() throws Exception {
496         final RecordingValidatorHandler delegate = new RecordingValidatorHandler();
497         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
498         final Object value = new Object();
499         handler.setProperty("p", value);
500         assertTrue(delegate.setPropertyCalled);
501         assertEquals("p", delegate.setPropertyName);
502         assertSame(value, delegate.setPropertyValue);
503     }
504 
505     @Test
506     void forwardsSkippedEntity() throws Exception {
507         final RecordingValidatorHandler delegate = new RecordingValidatorHandler();
508         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
509         handler.skippedEntity("name");
510         assertTrue(delegate.skippedEntityCalled);
511         assertEquals("name", delegate.skippedEntityName);
512     }
513 
514     @Test
515     void forwardsStartDocument() throws Exception {
516         final RecordingValidatorHandler delegate = new RecordingValidatorHandler();
517         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
518         handler.startDocument();
519         assertTrue(delegate.startDocumentCalled);
520     }
521 
522     @Test
523     void forwardsStartElement() throws Exception {
524         final RecordingValidatorHandler delegate = new RecordingValidatorHandler();
525         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
526         final Attributes attrs = new AttributesImpl();
527         handler.startElement("uri", "local", "qName", attrs);
528         assertTrue(delegate.startElementCalled);
529         assertEquals("uri", delegate.startElementUri);
530         assertEquals("local", delegate.startElementLocalName);
531         assertEquals("qName", delegate.startElementQName);
532         assertSame(attrs, delegate.startElementAttrs);
533     }
534 
535     @Test
536     void forwardsStartPrefixMapping() throws Exception {
537         final RecordingValidatorHandler delegate = new RecordingValidatorHandler();
538         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
539         handler.startPrefixMapping("p", "u");
540         assertTrue(delegate.startPrefixMappingCalled);
541         assertEquals("p", delegate.startPrefixMappingPrefix);
542         assertEquals("u", delegate.startPrefixMappingUri);
543     }
544 
545     @Test
546     void getResourceResolverReturnsDelegateAfterSet() throws Exception {
547         final ValidatorHandler delegate = newValidatorHandler();
548         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
549         final LSResourceResolver resolver = (type, namespaceURI, publicId, systemId, baseURI) -> null;
550         handler.setResourceResolver(resolver);
551         assertSame(resolver, handler.getResourceResolver(), "getResourceResolver must return the caller-supplied resolver");
552     }
553 
554     @Test
555     void setResourceResolverDoesNotReplaceFloorOnDelegate() throws Exception {
556         final ValidatorHandler delegate = newValidatorHandler();
557         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
558         final LSResourceResolver resolver = (type, namespaceURI, publicId, systemId, baseURI) -> null;
559         final LSResourceResolver before = delegate.getResourceResolver();
560         handler.setResourceResolver(resolver);
561         // The delegate's resolver must remain the floor, not the caller-supplied resolver
562         assertSame(before, delegate.getResourceResolver(), "delegate resolver must stay the floor");
563         assertSame(resolver, handler.getResourceResolver(), "wrapper must expose caller resolver");
564     }
565 
566     @Test
567     void setResourceResolverNullClearsDelegate() throws Exception {
568         final ValidatorHandler delegate = newValidatorHandler();
569         final SecureValidatorHandler handler = new SecureValidatorHandler(delegate);
570         final LSResourceResolver resolver = (type, namespaceURI, publicId, systemId, baseURI) -> null;
571         handler.setResourceResolver(resolver);
572         assertSame(resolver, handler.getResourceResolver());
573         handler.setResourceResolver(null);
574         assertNull(handler.getResourceResolver(), "null resolver must clear the floor delegate");
575     }
576     // --- Forwarding tests using RecordingValidatorHandler ---
577 }