This is an automated email from the ASF dual-hosted git repository. garydgregory pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/commons-xml.git
commit db5ad1d952ebb074ac17503c12bb559280511bee Author: Gary Gregory <[email protected]> AuthorDate: Sat Aug 29 08:01:54 2026 -0400 Add missing test: SecureValidatorHandler (100% coverage). --- .../commons/xml/SecureValidatorHandlerTest.java | 572 +++++++++++++++++++++ 1 file changed, 572 insertions(+) diff --git a/src/test/java/org/apache/commons/xml/SecureValidatorHandlerTest.java b/src/test/java/org/apache/commons/xml/SecureValidatorHandlerTest.java new file mode 100644 index 0000000..6c3a03c --- /dev/null +++ b/src/test/java/org/apache/commons/xml/SecureValidatorHandlerTest.java @@ -0,0 +1,572 @@ +/* + * 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 + * + * https://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.commons.xml; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import javax.xml.XMLConstants; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.ValidatorHandler; + +import org.junit.jupiter.api.Test; +import org.w3c.dom.ls.LSResourceResolver; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.ErrorHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.DefaultHandler; +import javax.xml.validation.TypeInfoProvider; + +class SecureValidatorHandlerTest { + + /** + * Minimal recording ValidatorHandler used to verify forwarding without triggering real validation. + */ + private static final class RecordingValidatorHandler extends ValidatorHandler { + + boolean startDocumentCalled; + + boolean endDocumentCalled; + + boolean startPrefixMappingCalled; + + String startPrefixMappingPrefix; + + String startPrefixMappingUri; + + boolean endPrefixMappingCalled; + + String endPrefixMappingPrefix; + + boolean startElementCalled; + + String startElementUri; + + String startElementLocalName; + + String startElementQName; + + Attributes startElementAttrs; + + boolean charactersCalled; + + char[] charactersChars; + + int charactersStart; + + int charactersLength; + + boolean ignorableWhitespaceCalled; + + boolean processingInstructionCalled; + + String piTarget; + + String piData; + + boolean endElementCalled; + + String endElementUri; + + String endElementLocalName; + + String endElementQName; + + boolean skippedEntityCalled; + + String skippedEntityName; + + boolean setDocumentLocatorCalled; + + boolean setContentHandlerCalled; + + boolean setErrorHandlerCalled; + + boolean setFeatureCalled; + + String setFeatureName; + + boolean setFeatureValue; + + boolean setPropertyCalled; + + String setPropertyName; + + Object setPropertyValue; + + ContentHandler contentHandler; + + ErrorHandler errorHandler; + + LSResourceResolver resourceResolver; + + @Override + public void characters(char[] ch, int start, int length) { + charactersCalled = true; + charactersChars = ch.clone(); + charactersStart = start; + charactersLength = length; + } + + @Override + public void endDocument() { + endDocumentCalled = true; + } + + @Override + public void endElement(String uri, String localName, String qName) { + endElementCalled = true; + endElementUri = uri; + endElementLocalName = localName; + endElementQName = qName; + } + + @Override + public void endPrefixMapping(String prefix) { + endPrefixMappingCalled = true; + endPrefixMappingPrefix = prefix; + } + + @Override + public ContentHandler getContentHandler() { + return contentHandler; + } + + @Override + public ErrorHandler getErrorHandler() { + return errorHandler; + } + + @Override + public boolean getFeature(String name) { + return false; + } + + @Override + public Object getProperty(String name) { + return null; + } + + @Override + public LSResourceResolver getResourceResolver() { + return resourceResolver; + } + + @Override + public TypeInfoProvider getTypeInfoProvider() { + return null; + } + + @Override + public void ignorableWhitespace(char[] ch, int start, int length) { + ignorableWhitespaceCalled = true; + } + + @Override + public void processingInstruction(String target, String data) { + processingInstructionCalled = true; + piTarget = target; + piData = data; + } + + @Override + public void setContentHandler(ContentHandler handler) { + setContentHandlerCalled = true; + contentHandler = handler; + } + + @Override + public void setDocumentLocator(Locator locator) { + setDocumentLocatorCalled = true; + } + + @Override + public void setErrorHandler(ErrorHandler handler) { + setErrorHandlerCalled = true; + errorHandler = handler; + } + + @Override + public void setFeature(String name, boolean value) { + setFeatureCalled = true; + setFeatureName = name; + setFeatureValue = value; + } + + @Override + public void setProperty(String name, Object value) { + setPropertyCalled = true; + setPropertyName = name; + setPropertyValue = value; + } + + @Override + public void setResourceResolver(LSResourceResolver resolver) { + resourceResolver = resolver; + } + + @Override + public void skippedEntity(String name) { + skippedEntityCalled = true; + skippedEntityName = name; + } + + @Override + public void startDocument() { + startDocumentCalled = true; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes atts) { + startElementCalled = true; + startElementUri = uri; + startElementLocalName = localName; + startElementQName = qName; + startElementAttrs = atts; + } + + @Override + public void startPrefixMapping(String prefix, String uri) { + startPrefixMappingCalled = true; + startPrefixMappingPrefix = prefix; + startPrefixMappingUri = uri; + } + } + + private static ValidatorHandler newValidatorHandler() throws Exception { + final SchemaFactory factory = SecureSchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + return factory.newSchema().newValidatorHandler(); + } + + @Test + void constructorInstallsFloorOnDelegate() throws Exception { + final ValidatorHandler delegate = newValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + // The constructor must install the ignore-all floor on the delegate + assertNotNull(delegate.getResourceResolver(), "delegate resource resolver must be set to the floor"); + assertTrue(delegate.getResourceResolver() instanceof FallbackIgnoreLSResourceResolver, "delegate resolver must be a FallbackIgnoreLSResourceResolver"); + // getResourceResolver on the wrapper returns the floor's delegate, which is null initially + assertNull(handler.getResourceResolver()); + } + + @Test + void constructorRejectsNullDelegate() { + assertThrows(NullPointerException.class, () -> new SecureValidatorHandler(null)); + } + + @Test + void delegatesFeature() throws Exception { + final ValidatorHandler delegate = newValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + // Verify delegation of getFeature; setFeature may be unsupported on this implementation + final String feature = XMLConstants.FEATURE_SECURE_PROCESSING; + final boolean delegateValue; + try { + delegateValue = delegate.getFeature(feature); + } catch (SAXNotRecognizedException | SAXNotSupportedException e) { + // If the feature is not recognized, both delegate and wrapper should behave the same way + assertThrows(SAXNotRecognizedException.class, () -> handler.getFeature(feature)); + return; + } + assertEquals(delegateValue, handler.getFeature(feature)); + } + + @Test + void delegatesGetContentHandler() throws Exception { + final ValidatorHandler delegate = newValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + final ContentHandler ch = new DefaultHandler(); + delegate.setContentHandler(ch); + assertSame(ch, handler.getContentHandler()); + } + + @Test + void delegatesGetErrorHandler() throws Exception { + final ValidatorHandler delegate = newValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + final ErrorHandler eh = new ErrorHandler() { + + @Override + public void error(final SAXParseException e) { + } + + @Override + public void fatalError(final SAXParseException e) { + } + + @Override + public void warning(final SAXParseException e) { + } + }; + delegate.setErrorHandler(eh); + assertSame(eh, handler.getErrorHandler()); + } + + @Test + void delegatesProperty() throws Exception { + final ValidatorHandler delegate = newValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + // Verify delegation of getProperty; setProperty may be unsupported on this implementation + final String property = XMLConstants.ACCESS_EXTERNAL_DTD; + final Object delegateValue; + try { + delegateValue = delegate.getProperty(property); + } catch (SAXNotRecognizedException | SAXNotSupportedException e) { + assertThrows(SAXNotRecognizedException.class, () -> handler.getProperty(property)); + return; + } + assertSame(delegateValue, handler.getProperty(property)); + } + + @Test + void delegatesSetContentHandler() throws Exception { + final ValidatorHandler delegate = newValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + final ContentHandler ch = new DefaultHandler(); + handler.setContentHandler(ch); + assertSame(ch, delegate.getContentHandler()); + } + + @Test + void delegatesSetDocumentLocator() throws Exception { + final ValidatorHandler delegate = newValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + final Locator locator = new org.xml.sax.Locator() { + + @Override + public int getColumnNumber() { + return 0; + } + + @Override + public int getLineNumber() { + return 0; + } + + @Override + public String getPublicId() { + return null; + } + + @Override + public String getSystemId() { + return null; + } + }; + handler.setDocumentLocator(locator); + // No exception means forwarding works; we cannot easily verify locator on delegate without exposing it + } + + @Test + void delegatesSetErrorHandler() throws Exception { + final ValidatorHandler delegate = newValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + final ErrorHandler eh = new ErrorHandler() { + + @Override + public void error(final SAXParseException e) { + } + + @Override + public void fatalError(final SAXParseException e) { + } + + @Override + public void warning(final SAXParseException e) { + } + }; + handler.setErrorHandler(eh); + assertSame(eh, delegate.getErrorHandler()); + } + + @Test + void delegatesTypeInfoProvider() throws Exception { + final ValidatorHandler delegate = newValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + assertSame(delegate.getTypeInfoProvider(), handler.getTypeInfoProvider()); + } + + @Test + void forwardsCharacters() throws Exception { + final RecordingValidatorHandler delegate = new RecordingValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + final char[] ch = { 'a', 'b' }; + handler.characters(ch, 0, 2); + assertTrue(delegate.charactersCalled); + assertEquals(0, delegate.charactersStart); + assertEquals(2, delegate.charactersLength); + } + + @Test + void forwardsEndDocument() throws Exception { + final RecordingValidatorHandler delegate = new RecordingValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + handler.endDocument(); + assertTrue(delegate.endDocumentCalled); + } + + @Test + void forwardsEndElement() throws Exception { + final RecordingValidatorHandler delegate = new RecordingValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + handler.endElement("uri", "local", "qName"); + assertTrue(delegate.endElementCalled); + assertEquals("uri", delegate.endElementUri); + assertEquals("local", delegate.endElementLocalName); + assertEquals("qName", delegate.endElementQName); + } + + @Test + void forwardsEndPrefixMapping() throws Exception { + final RecordingValidatorHandler delegate = new RecordingValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + handler.endPrefixMapping("p"); + assertTrue(delegate.endPrefixMappingCalled); + assertEquals("p", delegate.endPrefixMappingPrefix); + } + + @Test + void forwardsGetProperty() throws Exception { + final RecordingValidatorHandler delegate = new RecordingValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + // getProperty is delegated; RecordingValidatorHandler returns null + assertNull(handler.getProperty("any")); + } + + @Test + void forwardsIgnorableWhitespace() throws Exception { + final RecordingValidatorHandler delegate = new RecordingValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + final char[] ch = { ' ' }; + handler.ignorableWhitespace(ch, 0, 1); + assertTrue(delegate.ignorableWhitespaceCalled); + } + + @Test + void forwardsProcessingInstruction() throws Exception { + final RecordingValidatorHandler delegate = new RecordingValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + handler.processingInstruction("target", "data"); + assertTrue(delegate.processingInstructionCalled); + assertEquals("target", delegate.piTarget); + assertEquals("data", delegate.piData); + } + + @Test + void forwardsSetFeature() throws Exception { + final RecordingValidatorHandler delegate = new RecordingValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + handler.setFeature("f", true); + assertTrue(delegate.setFeatureCalled); + assertEquals("f", delegate.setFeatureName); + assertTrue(delegate.setFeatureValue); + } + + @Test + void forwardsSetProperty() throws Exception { + final RecordingValidatorHandler delegate = new RecordingValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + final Object value = new Object(); + handler.setProperty("p", value); + assertTrue(delegate.setPropertyCalled); + assertEquals("p", delegate.setPropertyName); + assertSame(value, delegate.setPropertyValue); + } + + @Test + void forwardsSkippedEntity() throws Exception { + final RecordingValidatorHandler delegate = new RecordingValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + handler.skippedEntity("name"); + assertTrue(delegate.skippedEntityCalled); + assertEquals("name", delegate.skippedEntityName); + } + + @Test + void forwardsStartDocument() throws Exception { + final RecordingValidatorHandler delegate = new RecordingValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + handler.startDocument(); + assertTrue(delegate.startDocumentCalled); + } + + @Test + void forwardsStartElement() throws Exception { + final RecordingValidatorHandler delegate = new RecordingValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + final Attributes attrs = new AttributesImpl(); + handler.startElement("uri", "local", "qName", attrs); + assertTrue(delegate.startElementCalled); + assertEquals("uri", delegate.startElementUri); + assertEquals("local", delegate.startElementLocalName); + assertEquals("qName", delegate.startElementQName); + assertSame(attrs, delegate.startElementAttrs); + } + + @Test + void forwardsStartPrefixMapping() throws Exception { + final RecordingValidatorHandler delegate = new RecordingValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + handler.startPrefixMapping("p", "u"); + assertTrue(delegate.startPrefixMappingCalled); + assertEquals("p", delegate.startPrefixMappingPrefix); + assertEquals("u", delegate.startPrefixMappingUri); + } + + @Test + void getResourceResolverReturnsDelegateAfterSet() throws Exception { + final ValidatorHandler delegate = newValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + final LSResourceResolver resolver = (type, namespaceURI, publicId, systemId, baseURI) -> null; + handler.setResourceResolver(resolver); + assertSame(resolver, handler.getResourceResolver(), "getResourceResolver must return the caller-supplied resolver"); + } + + @Test + void setResourceResolverDoesNotReplaceFloorOnDelegate() throws Exception { + final ValidatorHandler delegate = newValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + final LSResourceResolver resolver = (type, namespaceURI, publicId, systemId, baseURI) -> null; + final LSResourceResolver before = delegate.getResourceResolver(); + handler.setResourceResolver(resolver); + // The delegate's resolver must remain the floor, not the caller-supplied resolver + assertSame(before, delegate.getResourceResolver(), "delegate resolver must stay the floor"); + assertSame(resolver, handler.getResourceResolver(), "wrapper must expose caller resolver"); + } + + @Test + void setResourceResolverNullClearsDelegate() throws Exception { + final ValidatorHandler delegate = newValidatorHandler(); + final SecureValidatorHandler handler = new SecureValidatorHandler(delegate); + final LSResourceResolver resolver = (type, namespaceURI, publicId, systemId, baseURI) -> null; + handler.setResourceResolver(resolver); + assertSame(resolver, handler.getResourceResolver()); + handler.setResourceResolver(null); + assertNull(handler.getResourceResolver(), "null resolver must clear the floor delegate"); + } + // --- Forwarding tests using RecordingValidatorHandler --- +}
