This is an automated email from the ASF dual-hosted git repository. ppkarwasz pushed a commit to branch feature/reduce-shade-footprint in repository https://gitbox.apache.org/repos/asf/commons-xml.git
commit 66be10519977c6a89e9334eaa30b91f23d71f610 Author: Piotr P. Karwasz <[email protected]> AuthorDate: Wed Jul 8 10:52:46 2026 +0200 Split the resolver floors into top-level classes Promote the five nested Resolvers.Fallback* floors to top-level, package-private classes and delete the Resolvers container. The floors were bound together only by the shared forbiddenMessage, now on HardeningException (Phase 1), and by being nested in one class, which maven-shade minimizeJar keeps together. Splitting them means shading one hardener pulls only the floor(s) it uses: - DocumentBuilderHardener: 10 -> 5 classes, 19265 -> 12161 bytes - SAXParserHardener: 13 -> 8 classes, 25721 -> 18242 bytes - StaxHardener: 10 -> 6 classes, 20547 -> 13433 bytes The EntityResolver2 floor is named FallbackDenyEntityResolver2; it also carries the shared "floor" overview the container used to hold. Update ShadingFootprintTest's expected sets and exclude that JVM-only test (jdependency) from the Android test compile. Assisted-By: Claude Opus 4.8 <[email protected]> --- android-tests/build.gradle.kts | 5 + .../commons/xml/FallbackDenyEntityResolver2.java | 133 +++++++++ .../xml/FallbackDenyLSResourceResolver.java | 55 ++++ .../commons/xml/FallbackDenyURIResolver.java | 56 ++++ .../commons/xml/FallbackDenyXMLResolver.java | 84 ++++++ .../commons/xml/FallbackIgnoreXMLResolver.java | 47 ++++ .../commons/xml/HardeningDocumentBuilder.java | 4 +- .../apache/commons/xml/HardeningSchemaFactory.java | 4 +- .../apache/commons/xml/HardeningTransformer.java | 4 +- .../commons/xml/HardeningTransformerFactory.java | 2 +- .../org/apache/commons/xml/HardeningValidator.java | 2 +- .../commons/xml/HardeningValidatorHandler.java | 4 +- .../commons/xml/HardeningXMLInputFactory.java | 18 +- .../org/apache/commons/xml/HardeningXMLReader.java | 12 +- .../java/org/apache/commons/xml/Resolvers.java | 308 --------------------- .../org/apache/commons/xml/SAXParserHardener.java | 6 +- .../java/org/apache/commons/xml/StaxHardener.java | 12 +- .../apache/commons/xml/TransformerHardener.java | 2 +- .../commons/xml/EntityResolverFloorTest.java | 4 +- .../apache/commons/xml/ShadingFootprintTest.java | 11 +- 20 files changed, 421 insertions(+), 352 deletions(-) diff --git a/android-tests/build.gradle.kts b/android-tests/build.gradle.kts index a8938b9..7a42244 100644 --- a/android-tests/build.gradle.kts +++ b/android-tests/build.gradle.kts @@ -64,6 +64,11 @@ android { } } +// ShadingFootprintTest is a JVM-only build check (it uses jdependency to read target/classes); exclude it from the Android test compile. +tasks.withType<JavaCompile>().configureEach { + exclude("**/ShadingFootprintTest.java") +} + // Skip JAXP groups whose factories Android does not ship junitPlatform { filters { diff --git a/src/main/java/org/apache/commons/xml/FallbackDenyEntityResolver2.java b/src/main/java/org/apache/commons/xml/FallbackDenyEntityResolver2.java new file mode 100644 index 0000000..92ccf4f --- /dev/null +++ b/src/main/java/org/apache/commons/xml/FallbackDenyEntityResolver2.java @@ -0,0 +1,133 @@ +/* + * 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 java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; + +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.ext.DefaultHandler2; +import org.xml.sax.ext.EntityResolver2; + +/** + * Entity resolver that consults an optional caller-supplied resolver and denies (throws) whatever the caller does not resolve. + * + * <p>The canonical hardening floor, and the entity-resolution counterpart of the JAXP 1.5 {@code ACCESS_EXTERNAL_*} properties. Every floor + * ({@link FallbackDenyLSResourceResolver}, {@link FallbackDenyURIResolver}, {@link FallbackDenyXMLResolver} and its {@link FallbackIgnoreXMLResolver} variant) + * shares two defining properties:</p> + * <ol> + * <li><strong>Non-removable, and it wraps the resolver the caller sets.</strong> The hardened wrappers install one and route a caller-set resolver through + * {@code setDelegate} rather than letting it replace the floor, so the caller's resolver is consulted first but cannot remove the floor underneath it.</li> + * <li><strong>It supplies the default action for a lookup the caller's resolver does not resolve</strong> (a {@code null} return, or no caller resolver at + * all). This is where a floor departs from stock JAXP: normally an unresolved lookup falls back to the processor's built-in resolution and the resource is + * <em>fetched</em>; a floor instead <em>denies</em> it (throws).</li> + * </ol> + * + * <p>The hardened DOM and SAX wrappers install one of these and, when the caller sets their own {@link EntityResolver}, route it through {@link #setDelegate} + * rather than letting it replace the floor. A caller therefore opts a specific resource in by returning a non-{@code null} {@link InputSource} from their + * resolver; anything they leave unresolved (a {@code null} return, or no caller resolver at all) goes to {@link #onUnresolved}, which denies by default.</p> + * + * <p>It extends {@link DefaultHandler2} so it is also usable as a {@link org.xml.sax.ext.LexicalHandler} (see {@code SAXParserHardener}'s Android subclass, + * which needs {@code startDTD}/{@code endDTD}); {@link #getExternalSubset} therefore inherits the {@code DefaultHandler2} "no synthetic subset" default. Only + * {@link #resolveEntity(String, String, String, String) resolveEntity} (the actual external fetch) reaches the deny fallback.</p> + */ +class FallbackDenyEntityResolver2 extends DefaultHandler2 { + + /** + * Caller-supplied resolver consulted first, or {@code null} for a pure deny-all floor. + */ + private EntityResolver delegate; + + /** + * Resolves {@code systemId} against {@code baseURI}. + * + * @param baseURI The absolute base URI to resolve against, or {@code null} if none is available. + * @param systemId The system identifier, possibly relative to {@code baseURI}. + * @return The absolutized system identifier, or {@code systemId} unchanged when it cannot or need not be resolved. + */ + private static String absolutize(final String baseURI, final String systemId) { + if (systemId == null || baseURI == null) { + return systemId; + } + try { + final URI system = new URI(systemId); + return system.isAbsolute() ? systemId : new URI(baseURI).resolve(system).toString(); + } catch (final URISyntaxException e) { + return systemId; + } + } + + FallbackDenyEntityResolver2(final EntityResolver delegate) { + this.delegate = delegate; + } + + /** + * Replaces the caller resolver consulted ahead of the floor; lets a single floor instance back successive {@code setEntityResolver} calls. + * + * @param delegate The caller-supplied resolver, or {@code null} for a pure deny-all floor. + */ + final void setDelegate(final EntityResolver delegate) { + this.delegate = delegate; + } + + final EntityResolver getDelegate() { + return delegate; + } + + @Override + public final InputSource resolveEntity(final String publicId, final String systemId) throws SAXException, IOException { + return resolveEntity(null, publicId, null, systemId); + } + + @Override + public final InputSource resolveEntity(final String name, final String publicId, final String baseURI, final String systemId) + throws SAXException, IOException { + final InputSource resolved = resolveWithDelegate(name, publicId, baseURI, systemId); + return resolved != null ? resolved : onUnresolved(name, publicId, baseURI, systemId); + } + + /** + * Outcome when neither the caller delegate nor this resolver provides the entity. Denies by default; a subclass may permit specific lookups (e.g. the + * external DTD subset) by returning {@code null} or an {@link InputSource} instead of calling {@code super}. + * + * @param name The entity name, or {@code null} on the 2-arg resolution path. + * @param publicId The public identifier, or {@code null} if none. + * @param baseURI The base URI for relative resolution, or {@code null}. + * @param systemId The system identifier of the unresolved entity. + * @return An {@link InputSource} to permit the lookup, or {@code null} to skip it silently; the default implementation never returns normally. + * @throws SAXException to deny the lookup (the default behavior). + * @throws IOException if a subclass opens a stream that fails. + */ + protected InputSource onUnresolved(final String name, final String publicId, final String baseURI, final String systemId) + throws SAXException, IOException { + throw new SAXException(HardeningException.forbidden(name, null, publicId, systemId, baseURI)); + } + + private InputSource resolveWithDelegate(final String name, final String publicId, final String baseURI, + final String systemId) throws SAXException, IOException { + if (delegate != null) { + return delegate instanceof EntityResolver2 ? ((EntityResolver2) delegate).resolveEntity(name, publicId, baseURI, systemId) : + // We need to resolve the systemId against baseURI, because a plain EntityResolver expects an absolute URI. + delegate.resolveEntity(publicId, absolutize(baseURI, systemId)); + } + return null; + } +} diff --git a/src/main/java/org/apache/commons/xml/FallbackDenyLSResourceResolver.java b/src/main/java/org/apache/commons/xml/FallbackDenyLSResourceResolver.java new file mode 100644 index 0000000..60ab7b5 --- /dev/null +++ b/src/main/java/org/apache/commons/xml/FallbackDenyLSResourceResolver.java @@ -0,0 +1,55 @@ +/* + * 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 org.w3c.dom.ls.LSInput; +import org.w3c.dom.ls.LSResourceResolver; + +/** + * {@link LSResourceResolver} floor: consults an optional caller-supplied resolver and denies (throws) whatever the caller does not resolve. + * + * <p>The schema-compile counterpart of {@link FallbackDenyEntityResolver2}. The hardened {@link javax.xml.validation.SchemaFactory}, {@link + * javax.xml.validation.Validator} and {@link javax.xml.validation.ValidatorHandler} wrappers install one of these and route a caller-set resolver through + * {@link #setDelegate} rather than letting it replace the floor. A caller opts a specific resource in by returning a non-{@code null} {@link LSInput}; + * anything left unresolved is denied.</p> + */ +final class FallbackDenyLSResourceResolver implements LSResourceResolver { + + private LSResourceResolver delegate; + + FallbackDenyLSResourceResolver(final LSResourceResolver delegate) { + this.delegate = delegate; + } + + void setDelegate(final LSResourceResolver delegate) { + this.delegate = delegate; + } + + LSResourceResolver getDelegate() { + return delegate; + } + + @Override + public LSInput resolveResource(final String type, final String namespaceURI, final String publicId, final String systemId, final String baseURI) { + final LSInput resolved = delegate != null ? delegate.resolveResource(type, namespaceURI, publicId, systemId, baseURI) : null; + if (resolved != null) { + return resolved; + } + throw new SecurityException(HardeningException.forbidden(type, namespaceURI, publicId, systemId, baseURI)); + } +} diff --git a/src/main/java/org/apache/commons/xml/FallbackDenyURIResolver.java b/src/main/java/org/apache/commons/xml/FallbackDenyURIResolver.java new file mode 100644 index 0000000..8f5acfa --- /dev/null +++ b/src/main/java/org/apache/commons/xml/FallbackDenyURIResolver.java @@ -0,0 +1,56 @@ +/* + * 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 javax.xml.transform.Source; +import javax.xml.transform.TransformerException; +import javax.xml.transform.URIResolver; + +/** + * {@link URIResolver} floor: consults an optional caller-supplied resolver and denies (throws) whatever the caller does not resolve. + * + * <p>The XSLT counterpart of {@link FallbackDenyEntityResolver2}, guarding {@code xsl:import}/{@code xsl:include} at compile time and {@code document()} at + * transform time. The hardened {@link javax.xml.transform.TransformerFactory} and {@link javax.xml.transform.Transformer} wrappers install one of these and + * route a caller-set resolver through {@link #setDelegate} rather than letting it replace the floor. A caller opts a specific URI in by returning a + * non-{@code null} {@link Source}; anything left unresolved is denied.</p> + */ +final class FallbackDenyURIResolver implements URIResolver { + + private URIResolver delegate; + + FallbackDenyURIResolver(final URIResolver delegate) { + this.delegate = delegate; + } + + void setDelegate(final URIResolver delegate) { + this.delegate = delegate; + } + + URIResolver getDelegate() { + return delegate; + } + + @Override + public Source resolve(final String href, final String base) throws TransformerException { + final Source resolved = delegate != null ? delegate.resolve(href, base) : null; + if (resolved != null) { + return resolved; + } + throw new TransformerException(HardeningException.forbidden("uri", null, null, href, base)); + } +} diff --git a/src/main/java/org/apache/commons/xml/FallbackDenyXMLResolver.java b/src/main/java/org/apache/commons/xml/FallbackDenyXMLResolver.java new file mode 100644 index 0000000..b5e1812 --- /dev/null +++ b/src/main/java/org/apache/commons/xml/FallbackDenyXMLResolver.java @@ -0,0 +1,84 @@ +/* + * 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 javax.xml.stream.XMLResolver; +import javax.xml.stream.XMLStreamException; +import javax.xml.transform.Source; + +/** + * {@link XMLResolver} floor: consults an optional caller-supplied resolver and denies (throws) whatever the caller does not resolve. + * + * <p>The StAX counterpart of {@link FallbackDenyEntityResolver2}, installed on each entity-resolution hook. The hardened {@link javax.xml.stream.XMLInputFactory} + * wrapper routes a caller-set resolver through {@link #setDelegate} rather than letting it replace the floor. A caller opts a specific entity in by returning + * a non-{@code null} result; anything left unresolved goes to {@link #onUnresolved}, which denies by default. Subclasses override {@code onUnresolved} to give + * a hook a different unresolved policy (e.g. return an empty input for the external DTD subset, or for undeclared entities) while keeping the caller-delegate + * behavior.</p> + */ +class FallbackDenyXMLResolver implements XMLResolver { + + private XMLResolver delegate; + + FallbackDenyXMLResolver(final XMLResolver delegate) { + this.delegate = delegate; + } + + final void setDelegate(final XMLResolver delegate) { + this.delegate = delegate; + } + + final XMLResolver getDelegate() { + return delegate; + } + + @Override + public final Object resolveEntity(final String publicID, final String systemID, final String baseURI, final String namespace) throws XMLStreamException { + final Object resolved = delegate != null ? delegate.resolveEntity(publicID, systemID, baseURI, namespace) : null; + return resolved != null ? resolved : onUnresolved(publicID, systemID, baseURI, namespace); + } + + /** + * Outcome when the caller delegate does not resolve the entity. Denies by default; a subclass may return an {@link java.io.InputStream}, {@link Source} or + * other {@link XMLResolver}-supported value (for example an empty input) instead of calling {@code super}, or {@code throw} + * {@link #denied(String, String, String, String)} to deny only some lookups. + * + * @param publicID The public identifier, or {@code null} if none. + * @param systemID The system identifier of the unresolved entity. + * @param baseURI The base URI for relative resolution, or {@code null}. + * @param namespace The namespace (or, for Woodstox, the entity name), or {@code null}. + * @return The replacement input, or a value the caller's parser accepts; the default implementation never returns normally. + * @throws XMLStreamException to deny the lookup (the default behavior). + */ + protected Object onUnresolved(final String publicID, final String systemID, final String baseURI, final String namespace) throws XMLStreamException { + throw denied(publicID, systemID, baseURI, namespace); + } + + /** + * Builds the standard "forbidden by hardening" exception for a denied lookup, so a subclass with a mixed policy can reuse the deny outcome for the + * lookups it refuses. + * + * @param publicID The public identifier, or {@code null} if none. + * @param systemID The system identifier of the unresolved entity. + * @param baseURI The base URI for relative resolution, or {@code null}. + * @param namespace The namespace (or, for Woodstox, the entity name), or {@code null}. + * @return The exception to throw. + */ + protected final XMLStreamException denied(final String publicID, final String systemID, final String baseURI, final String namespace) { + return new XMLStreamException(HardeningException.forbidden(null, namespace, publicID, systemID, baseURI)); + } +} diff --git a/src/main/java/org/apache/commons/xml/FallbackIgnoreXMLResolver.java b/src/main/java/org/apache/commons/xml/FallbackIgnoreXMLResolver.java new file mode 100644 index 0000000..38f1a0c --- /dev/null +++ b/src/main/java/org/apache/commons/xml/FallbackIgnoreXMLResolver.java @@ -0,0 +1,47 @@ +/* + * 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 java.io.ByteArrayInputStream; +import java.io.InputStream; + +import javax.xml.stream.XMLResolver; +import javax.xml.stream.XMLStreamException; + +/** + * {@link FallbackDenyXMLResolver} variant whose unresolved policy returns an empty input instead of throwing, so the parse continues with no replacement + * content. Used on Woodstox's DTD-subset and undeclared-entity hooks (where a missing resource must be skipped, not denied), while still consulting an + * optional caller-supplied resolver first. + */ +class FallbackIgnoreXMLResolver extends FallbackDenyXMLResolver { + + /** + * Empty {@link ByteArrayInputStream} shared across every call. {@code read()} on a zero-length array always returns {@code -1}, so reusing the instance + * is safe even though the type is technically stateful. + */ + private static final InputStream EMPTY = new ByteArrayInputStream(new byte[0]); + + FallbackIgnoreXMLResolver(final XMLResolver delegate) { + super(delegate); + } + + @Override + protected Object onUnresolved(final String publicID, final String systemID, final String baseURI, final String namespace) throws XMLStreamException { + return EMPTY; + } +} diff --git a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilder.java b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilder.java index bb0b6a8..44a3ef3 100644 --- a/src/main/java/org/apache/commons/xml/HardeningDocumentBuilder.java +++ b/src/main/java/org/apache/commons/xml/HardeningDocumentBuilder.java @@ -32,7 +32,7 @@ /** * {@link DocumentBuilder} wrapper that keeps a deny-all {@link EntityResolver} as a non-overridable floor. * - * <p>A caller-set resolver is sandwiched inside a {@link Resolvers.FallbackDenyResolver} instead of replacing the deny-all one, so an external lookup the + * <p>A caller-set resolver is sandwiched inside a {@link FallbackDenyEntityResolver2} instead of replacing the deny-all one, so an external lookup the * caller's resolver does not satisfy is denied rather than fetched. {@link #reset()} re-establishes the bare deny-all floor, matching the just-constructed * state.</p> */ @@ -40,7 +40,7 @@ final class HardeningDocumentBuilder extends DocumentBuilder { private final DocumentBuilder delegate; - private final Resolvers.FallbackDenyResolver floor = new Resolvers.FallbackDenyResolver(null); + private final FallbackDenyEntityResolver2 floor = new FallbackDenyEntityResolver2(null); HardeningDocumentBuilder(final DocumentBuilder delegate) { this.delegate = delegate; diff --git a/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java b/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java index 537f40e..dcdbb54 100644 --- a/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java +++ b/src/main/java/org/apache/commons/xml/HardeningSchemaFactory.java @@ -36,7 +36,7 @@ * * <p>Three layers cooperate:</p> * <ol> - * <li>{@link HardeningSchemaFactory} installs a deny-all {@link Resolvers.FallbackDenyLSResourceResolver} floor on the factory (blocking + * <li>{@link HardeningSchemaFactory} installs a deny-all {@link FallbackDenyLSResourceResolver} floor on the factory (blocking * {@code xs:import}/{@code xs:include}/{@code xs:redefine} at compile time) and rewrites the Source on every {@code newSchema(Source[])} entry point * through {@link XmlFactories#harden(Source)}.</li> * <li>{@link HardeningSchema} wraps every Validator/ValidatorHandler the inner Schema produces and re-installs the floor on each (blocking @@ -55,7 +55,7 @@ final class HardeningSchemaFactory extends SchemaFactory { private final SchemaFactory delegate; - private final Resolvers.FallbackDenyLSResourceResolver floor = new Resolvers.FallbackDenyLSResourceResolver(null); + private final FallbackDenyLSResourceResolver floor = new FallbackDenyLSResourceResolver(null); HardeningSchemaFactory(final SchemaFactory delegate) { this.delegate = delegate; diff --git a/src/main/java/org/apache/commons/xml/HardeningTransformer.java b/src/main/java/org/apache/commons/xml/HardeningTransformer.java index 577051b..de18485 100644 --- a/src/main/java/org/apache/commons/xml/HardeningTransformer.java +++ b/src/main/java/org/apache/commons/xml/HardeningTransformer.java @@ -39,11 +39,11 @@ final class HardeningTransformer extends Transformer { private final Transformer delegate; - private final Resolvers.FallbackDenyURIResolver floor; + private final FallbackDenyURIResolver floor; HardeningTransformer(final Transformer delegate, final URIResolver uriResolver) { this.delegate = delegate; - this.floor = new Resolvers.FallbackDenyURIResolver(uriResolver); + this.floor = new FallbackDenyURIResolver(uriResolver); delegate.setURIResolver(floor); } diff --git a/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java b/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java index 8d61bc6..b12a1a4 100644 --- a/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java +++ b/src/main/java/org/apache/commons/xml/HardeningTransformerFactory.java @@ -60,7 +60,7 @@ final class HardeningTransformerFactory extends SAXTransformerFactory { private final SAXTransformerFactory delegate; - private final Resolvers.FallbackDenyURIResolver floor = new Resolvers.FallbackDenyURIResolver(null); + private final FallbackDenyURIResolver floor = new FallbackDenyURIResolver(null); HardeningTransformerFactory(final SAXTransformerFactory delegate) { this.delegate = delegate; diff --git a/src/main/java/org/apache/commons/xml/HardeningValidator.java b/src/main/java/org/apache/commons/xml/HardeningValidator.java index 8eaa4b2..b3790c0 100644 --- a/src/main/java/org/apache/commons/xml/HardeningValidator.java +++ b/src/main/java/org/apache/commons/xml/HardeningValidator.java @@ -39,7 +39,7 @@ final class HardeningValidator extends Validator { private final Validator delegate; - private final Resolvers.FallbackDenyLSResourceResolver floor = new Resolvers.FallbackDenyLSResourceResolver(null); + private final FallbackDenyLSResourceResolver floor = new FallbackDenyLSResourceResolver(null); HardeningValidator(final Validator delegate) { this.delegate = delegate; diff --git a/src/main/java/org/apache/commons/xml/HardeningValidatorHandler.java b/src/main/java/org/apache/commons/xml/HardeningValidatorHandler.java index aa56dd8..f06c418 100644 --- a/src/main/java/org/apache/commons/xml/HardeningValidatorHandler.java +++ b/src/main/java/org/apache/commons/xml/HardeningValidatorHandler.java @@ -33,13 +33,13 @@ * {@link ValidatorHandler} wrapper that keeps a deny-all {@link LSResourceResolver} floor a caller cannot remove. * * <p>Blocks {@code xsi:schemaLocation} resolution during SAX-driven validation. A caller-set resolver is routed through a {@link - * Resolvers.FallbackDenyLSResourceResolver} rather than replacing the floor, so a schema the caller does not resolve is denied instead of fetched.</p> + * FallbackDenyLSResourceResolver} rather than replacing the floor, so a schema the caller does not resolve is denied instead of fetched.</p> */ final class HardeningValidatorHandler extends ValidatorHandler { private final ValidatorHandler delegate; - private final Resolvers.FallbackDenyLSResourceResolver floor = new Resolvers.FallbackDenyLSResourceResolver(null); + private final FallbackDenyLSResourceResolver floor = new FallbackDenyLSResourceResolver(null); HardeningValidatorHandler(final ValidatorHandler delegate) { this.delegate = delegate; diff --git a/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java b/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java index fe63e35..66eee4d 100644 --- a/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java +++ b/src/main/java/org/apache/commons/xml/HardeningXMLInputFactory.java @@ -32,13 +32,13 @@ import javax.xml.transform.Source; /** - * {@link XMLInputFactory} wrapper that keeps the {@link Resolvers.FallbackDenyXMLResolver} floors {@link StaxHardener} installs on the entity-resolution hooks + * {@link XMLInputFactory} wrapper that keeps the {@link FallbackDenyXMLResolver} floors {@link StaxHardener} installs on the entity-resolution hooks * non-removable by the caller. * * <p>Every resolver-valued entry point ({@link #setXMLResolver(XMLResolver)}, {@code setProperty(XMLInputFactory.RESOLVER, ...)} and the Woodstox - * {@code com.ctc.wstx.*Resolver} keys) is routed uniformly: a caller who supplies their own {@link Resolvers.FallbackDenyXMLResolver} takes control and it is + * {@code com.ctc.wstx.*Resolver} keys) is routed uniformly: a caller who supplies their own {@link FallbackDenyXMLResolver} takes control and it is * passed straight to the delegate; otherwise the current resolver on that hook is read, and if it is one of our floors the caller's resolver is set as its - * {@link Resolvers.FallbackDenyXMLResolver#setDelegate delegate} (an opt-in the floor cannot be removed by), or, if the hook is empty, the caller's resolver is + * {@link FallbackDenyXMLResolver#setDelegate delegate} (an opt-in the floor cannot be removed by), or, if the hook is empty, the caller's resolver is * wrapped in a fresh floor. This matters because Woodstox does not chain resolvers: when a resolver returns {@code null}, {@code DefaultInputResolver} falls * through to fetching the systemId URL itself, so a caller-set resolver that returns {@code null} must still land behind the floor. {@link #getXMLResolver()} and * {@code getProperty} report the caller's resolver unwrapped.</p> @@ -83,18 +83,18 @@ public Object getProperty(final String name) { * Routes a caller-set resolver for the property {@code name} behind the floor currently installed on that hook. * * @param name The resolver-valued property being set. - * @param resolver The caller's resolver, or their own {@link Resolvers.FallbackDenyXMLResolver} to take control. + * @param resolver The caller's resolver, or their own {@link FallbackDenyXMLResolver} to take control. */ private void setResolverProperty(final String name, final XMLResolver resolver) { - if (resolver instanceof Resolvers.FallbackDenyXMLResolver) { + if (resolver instanceof FallbackDenyXMLResolver) { // The caller supplies their own floor: hand it to the delegate as-is. delegate.setProperty(name, resolver); } else { final Object current = delegate.getProperty(name); - if (current instanceof Resolvers.FallbackDenyXMLResolver) { - ((Resolvers.FallbackDenyXMLResolver) current).setDelegate(resolver); + if (current instanceof FallbackDenyXMLResolver) { + ((FallbackDenyXMLResolver) current).setDelegate(resolver); } else { - delegate.setProperty(name, new Resolvers.FallbackDenyXMLResolver(resolver)); + delegate.setProperty(name, new FallbackDenyXMLResolver(resolver)); } } } @@ -107,7 +107,7 @@ private static boolean isResolverProperty(final String name) { } private static XMLResolver unwrap(final XMLResolver resolver) { - return resolver instanceof Resolvers.FallbackDenyXMLResolver ? ((Resolvers.FallbackDenyXMLResolver) resolver).getDelegate() : resolver; + return resolver instanceof FallbackDenyXMLResolver ? ((FallbackDenyXMLResolver) resolver).getDelegate() : resolver; } // <editor-fold defaultstate="collapsed" desc="Trivial delegation"> diff --git a/src/main/java/org/apache/commons/xml/HardeningXMLReader.java b/src/main/java/org/apache/commons/xml/HardeningXMLReader.java index 763f05a..666ccf2 100644 --- a/src/main/java/org/apache/commons/xml/HardeningXMLReader.java +++ b/src/main/java/org/apache/commons/xml/HardeningXMLReader.java @@ -30,14 +30,14 @@ import org.xml.sax.XMLReader; /** - * {@link XMLReader} wrapper that keeps a {@link Resolvers.FallbackDenyResolver} floor as the reader's entity resolver, non-overridable by the caller. + * {@link XMLReader} wrapper that keeps a {@link FallbackDenyEntityResolver2} floor as the reader's entity resolver, non-overridable by the caller. * * <p>The floor is installed once and stays the reader's entity resolver for the wrapper's lifetime; {@link #setEntityResolver(EntityResolver)} routes the - * caller's resolver through {@link Resolvers.FallbackDenyResolver#setDelegate} instead of replacing it. This includes the {@code DefaultHandler} that + * caller's resolver through {@link FallbackDenyEntityResolver2#setDelegate} instead of replacing it. This includes the {@code DefaultHandler} that * {@link javax.xml.parsers.SAXParser#parse(org.xml.sax.InputSource, org.xml.sax.helpers.DefaultHandler) SAXParser.parse(source, handler)} installs as the * reader's entity resolver, which would otherwise silently replace the floor. {@link #getEntityResolver()} reports the caller's resolver unwrapped.</p> * - * <p>A path that needs a non-deny floor (e.g. one that also permits the external DTD subset) passes a {@link Resolvers.FallbackDenyResolver} subclass to the + * <p>A path that needs a non-deny floor (e.g. one that also permits the external DTD subset) passes a {@link FallbackDenyEntityResolver2} subclass to the * two-argument constructor; a single stable floor instance also lets that subclass double as a {@link org.xml.sax.ext.LexicalHandler}. Every other method * forwards to the wrapped delegate; subclasses (e.g. {@code HardeningExpatXMLReader}) add per-implementation fixups on top of the floor.</p> */ @@ -45,13 +45,13 @@ class HardeningXMLReader implements XMLReader { private final XMLReader delegate; - private final Resolvers.FallbackDenyResolver floor; + private final FallbackDenyEntityResolver2 floor; HardeningXMLReader(final XMLReader delegate) { - this(delegate, new Resolvers.FallbackDenyResolver(null)); + this(delegate, new FallbackDenyEntityResolver2(null)); } - HardeningXMLReader(final XMLReader delegate, final Resolvers.FallbackDenyResolver floor) { + HardeningXMLReader(final XMLReader delegate, final FallbackDenyEntityResolver2 floor) { this.delegate = delegate; this.floor = floor; delegate.setEntityResolver(floor); diff --git a/src/main/java/org/apache/commons/xml/Resolvers.java b/src/main/java/org/apache/commons/xml/Resolvers.java deleted file mode 100644 index 3a7d92e..0000000 --- a/src/main/java/org/apache/commons/xml/Resolvers.java +++ /dev/null @@ -1,308 +0,0 @@ -/* - * 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 java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; - -import javax.xml.stream.XMLResolver; -import javax.xml.stream.XMLStreamException; -import javax.xml.transform.Source; -import javax.xml.transform.TransformerException; -import javax.xml.transform.URIResolver; - -import org.w3c.dom.ls.LSInput; -import org.w3c.dom.ls.LSResourceResolver; -import org.xml.sax.EntityResolver; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.ext.DefaultHandler2; -import org.xml.sax.ext.EntityResolver2; - -/** - * Policy resolvers that fix the outcome of every external lookup. Each member is a floor with two defining properties: - * - * <ol> - * <li><strong>Non-removable, and it wraps the resolver the caller sets.</strong> The hardened wrappers install one and route a caller-set resolver through - * {@code setDelegate} rather than letting it replace the floor, so the caller's resolver is consulted first but cannot remove the floor underneath it.</li> - * <li><strong>It supplies the default action for a lookup the caller's resolver does not resolve</strong> (a {@code null} return, or no caller resolver at - * all). This is where a floor departs from stock JAXP: normally an unresolved lookup falls back to the processor's built-in resolution and the resource is - * <em>fetched</em>; a floor instead <em>denies</em> it (throws).</li> - * </ol> - * - * <p>The deny members are {@link FallbackDenyResolver} (for {@code EntityResolver}), {@link FallbackDenyLSResourceResolver} (for {@link LSResourceResolver}), - * {@link FallbackDenyURIResolver} (for {@link URIResolver}) and {@link FallbackDenyXMLResolver} (for {@link XMLResolver}). {@link FallbackIgnoreXMLResolver} is a - * variant whose default action returns an empty input instead of throwing, for the Woodstox DTD-subset and undeclared-entity hooks where a missing resource must - * be skipped rather than denied.</p> - */ -final class Resolvers { - - /** - * Entity resolver that consults an optional caller-supplied resolver and denies (throws) whatever the caller does not resolve. - * - * <p>This is the entity-resolution counterpart of the JAXP 1.5 {@code ACCESS_EXTERNAL_*} properties: a non-overridable floor. The hardened DOM and SAX - * wrappers install one of these and, when the caller sets their own {@link EntityResolver}, route it through {@link #setDelegate} rather than letting it - * replace the floor. A caller therefore opts a specific resource in by returning a non-{@code null} {@link InputSource} from their resolver; anything they - * leave unresolved (a {@code null} return, or no caller resolver at all) goes to {@link #onUnresolved}, which denies by default.</p> - * - * <p>It extends {@link DefaultHandler2} so it is also usable as a {@link org.xml.sax.ext.LexicalHandler} (see {@code SAXParserHardener}'s Android subclass, - * which needs {@code startDTD}/{@code endDTD}); {@link #getExternalSubset} therefore inherits the {@code DefaultHandler2} "no synthetic subset" default. Only - * {@link #resolveEntity(String, String, String, String) resolveEntity} (the actual external fetch) reaches the deny fallback.</p> - */ - static class FallbackDenyResolver extends DefaultHandler2 { - - /** - * Caller-supplied resolver consulted first, or {@code null} for a pure deny-all floor. - */ - private EntityResolver delegate; - - /** - * Resolves {@code systemId} against {@code baseURI}. - * - * @param baseURI The absolute base URI to resolve against, or {@code null} if none is available. - * @param systemId The system identifier, possibly relative to {@code baseURI}. - * @return The absolutized system identifier, or {@code systemId} unchanged when it cannot or need not be resolved. - */ - private static String absolutize(final String baseURI, final String systemId) { - if (systemId == null || baseURI == null) { - return systemId; - } - try { - final URI system = new URI(systemId); - return system.isAbsolute() ? systemId : new URI(baseURI).resolve(system).toString(); - } catch (final URISyntaxException e) { - return systemId; - } - } - - FallbackDenyResolver(final EntityResolver delegate) { - this.delegate = delegate; - } - - /** - * Replaces the caller resolver consulted ahead of the floor; lets a single floor instance back successive {@code setEntityResolver} calls. - * - * @param delegate The caller-supplied resolver, or {@code null} for a pure deny-all floor. - */ - final void setDelegate(final EntityResolver delegate) { - this.delegate = delegate; - } - - final EntityResolver getDelegate() { - return delegate; - } - - @Override - public final InputSource resolveEntity(final String publicId, final String systemId) throws SAXException, IOException { - return resolveEntity(null, publicId, null, systemId); - } - - @Override - public final InputSource resolveEntity(final String name, final String publicId, final String baseURI, final String systemId) - throws SAXException, IOException { - final InputSource resolved = resolveWithDelegate(name, publicId, baseURI, systemId); - return resolved != null ? resolved : onUnresolved(name, publicId, baseURI, systemId); - } - - /** - * Outcome when neither the caller delegate nor this resolver provides the entity. Denies by default; a subclass may permit specific lookups (e.g. the - * external DTD subset) by returning {@code null} or an {@link InputSource} instead of calling {@code super}. - * - * @param name The entity name, or {@code null} on the 2-arg resolution path. - * @param publicId The public identifier, or {@code null} if none. - * @param baseURI The base URI for relative resolution, or {@code null}. - * @param systemId The system identifier of the unresolved entity. - * @return An {@link InputSource} to permit the lookup, or {@code null} to skip it silently; the default implementation never returns normally. - * @throws SAXException to deny the lookup (the default behavior). - * @throws IOException if a subclass opens a stream that fails. - */ - protected InputSource onUnresolved(final String name, final String publicId, final String baseURI, final String systemId) - throws SAXException, IOException { - throw new SAXException(HardeningException.forbidden(name, null, publicId, systemId, baseURI)); - } - - private InputSource resolveWithDelegate(final String name, final String publicId, final String baseURI, - final String systemId) throws SAXException, IOException { - if (delegate != null) { - return delegate instanceof EntityResolver2 ? ((EntityResolver2) delegate).resolveEntity(name, publicId, baseURI, systemId) : - // We need to resolve the systemId against baseURI, because a plain EntityResolver expects an absolute URI. - delegate.resolveEntity(publicId, absolutize(baseURI, systemId)); - } - return null; - } - } - - /** - * {@link LSResourceResolver} floor: consults an optional caller-supplied resolver and denies (throws) whatever the caller does not resolve. - * - * <p>The schema-compile counterpart of {@link FallbackDenyResolver}. The hardened {@link javax.xml.validation.SchemaFactory}, {@link - * javax.xml.validation.Validator} and {@link javax.xml.validation.ValidatorHandler} wrappers install one of these and route a caller-set resolver through - * {@link #setDelegate} rather than letting it replace the floor. A caller opts a specific resource in by returning a non-{@code null} {@link LSInput}; - * anything left unresolved is denied.</p> - */ - static final class FallbackDenyLSResourceResolver implements LSResourceResolver { - - private LSResourceResolver delegate; - - FallbackDenyLSResourceResolver(final LSResourceResolver delegate) { - this.delegate = delegate; - } - - void setDelegate(final LSResourceResolver delegate) { - this.delegate = delegate; - } - - LSResourceResolver getDelegate() { - return delegate; - } - - @Override - public LSInput resolveResource(final String type, final String namespaceURI, final String publicId, final String systemId, final String baseURI) { - final LSInput resolved = delegate != null ? delegate.resolveResource(type, namespaceURI, publicId, systemId, baseURI) : null; - if (resolved != null) { - return resolved; - } - throw new SecurityException(HardeningException.forbidden(type, namespaceURI, publicId, systemId, baseURI)); - } - } - - /** - * {@link URIResolver} floor: consults an optional caller-supplied resolver and denies (throws) whatever the caller does not resolve. - * - * <p>The XSLT counterpart of {@link FallbackDenyResolver}, guarding {@code xsl:import}/{@code xsl:include} at compile time and {@code document()} at - * transform time. The hardened {@link javax.xml.transform.TransformerFactory} and {@link javax.xml.transform.Transformer} wrappers install one of these and - * route a caller-set resolver through {@link #setDelegate} rather than letting it replace the floor. A caller opts a specific URI in by returning a - * non-{@code null} {@link Source}; anything left unresolved is denied.</p> - */ - static final class FallbackDenyURIResolver implements URIResolver { - - private URIResolver delegate; - - FallbackDenyURIResolver(final URIResolver delegate) { - this.delegate = delegate; - } - - void setDelegate(final URIResolver delegate) { - this.delegate = delegate; - } - - URIResolver getDelegate() { - return delegate; - } - - @Override - public Source resolve(final String href, final String base) throws TransformerException { - final Source resolved = delegate != null ? delegate.resolve(href, base) : null; - if (resolved != null) { - return resolved; - } - throw new TransformerException(HardeningException.forbidden("uri", null, null, href, base)); - } - } - - /** - * {@link XMLResolver} floor: consults an optional caller-supplied resolver and denies (throws) whatever the caller does not resolve. - * - * <p>The StAX counterpart of {@link FallbackDenyResolver}, installed on each entity-resolution hook. The hardened {@link javax.xml.stream.XMLInputFactory} - * wrapper routes a caller-set resolver through {@link #setDelegate} rather than letting it replace the floor. A caller opts a specific entity in by returning - * a non-{@code null} result; anything left unresolved goes to {@link #onUnresolved}, which denies by default. Subclasses override {@code onUnresolved} to give - * a hook a different unresolved policy (e.g. return an empty input for the external DTD subset, or for undeclared entities) while keeping the caller-delegate - * behavior.</p> - */ - static class FallbackDenyXMLResolver implements XMLResolver { - - private XMLResolver delegate; - - FallbackDenyXMLResolver(final XMLResolver delegate) { - this.delegate = delegate; - } - - final void setDelegate(final XMLResolver delegate) { - this.delegate = delegate; - } - - final XMLResolver getDelegate() { - return delegate; - } - - @Override - public final Object resolveEntity(final String publicID, final String systemID, final String baseURI, final String namespace) throws XMLStreamException { - final Object resolved = delegate != null ? delegate.resolveEntity(publicID, systemID, baseURI, namespace) : null; - return resolved != null ? resolved : onUnresolved(publicID, systemID, baseURI, namespace); - } - - /** - * Outcome when the caller delegate does not resolve the entity. Denies by default; a subclass may return an {@link java.io.InputStream}, {@link Source} or - * other {@link XMLResolver}-supported value (for example an empty input) instead of calling {@code super}, or {@code throw} - * {@link #denied(String, String, String, String)} to deny only some lookups. - * - * @param publicID The public identifier, or {@code null} if none. - * @param systemID The system identifier of the unresolved entity. - * @param baseURI The base URI for relative resolution, or {@code null}. - * @param namespace The namespace (or, for Woodstox, the entity name), or {@code null}. - * @return The replacement input, or a value the caller's parser accepts; the default implementation never returns normally. - * @throws XMLStreamException to deny the lookup (the default behavior). - */ - protected Object onUnresolved(final String publicID, final String systemID, final String baseURI, final String namespace) throws XMLStreamException { - throw denied(publicID, systemID, baseURI, namespace); - } - - /** - * Builds the standard "forbidden by hardening" exception for a denied lookup, so a subclass with a mixed policy can reuse the deny outcome for the - * lookups it refuses. - * - * @param publicID The public identifier, or {@code null} if none. - * @param systemID The system identifier of the unresolved entity. - * @param baseURI The base URI for relative resolution, or {@code null}. - * @param namespace The namespace (or, for Woodstox, the entity name), or {@code null}. - * @return The exception to throw. - */ - protected final XMLStreamException denied(final String publicID, final String systemID, final String baseURI, final String namespace) { - return new XMLStreamException(HardeningException.forbidden(null, namespace, publicID, systemID, baseURI)); - } - } - - /** - * {@link FallbackDenyXMLResolver} variant whose unresolved policy returns an empty input instead of throwing, so the parse continues with no replacement - * content. Used on Woodstox's DTD-subset and undeclared-entity hooks (where a missing resource must be skipped, not denied), while still consulting an - * optional caller-supplied resolver first. - */ - static class FallbackIgnoreXMLResolver extends FallbackDenyXMLResolver { - - /** - * Empty {@link ByteArrayInputStream} shared across every call. {@code read()} on a zero-length array always returns {@code -1}, so reusing the instance - * is safe even though the type is technically stateful. - */ - private static final InputStream EMPTY = new ByteArrayInputStream(new byte[0]); - - FallbackIgnoreXMLResolver(final XMLResolver delegate) { - super(delegate); - } - - @Override - protected Object onUnresolved(final String publicID, final String systemID, final String baseURI, final String namespace) throws XMLStreamException { - return EMPTY; - } - } - - private Resolvers() { - } -} diff --git a/src/main/java/org/apache/commons/xml/SAXParserHardener.java b/src/main/java/org/apache/commons/xml/SAXParserHardener.java index 5e0d67c..cbc4b5d 100644 --- a/src/main/java/org/apache/commons/xml/SAXParserHardener.java +++ b/src/main/java/org/apache/commons/xml/SAXParserHardener.java @@ -61,11 +61,11 @@ final class SAXParserHardener { * * <p>Android's Expat routes every external fetch (subset, DOCTYPE {@code SYSTEM}, general/parameter entity) through the 2-arg * {@link EntityResolver#resolveEntity(String, String)}; a deny-all resolver there would also reject a DOCTYPE that merely <em>names</em> an unused external - * subset. As a {@link Resolvers.FallbackDenyResolver} it consults the caller's resolver first; as a {@link LexicalHandler} (via {@code DefaultHandler2}) it + * subset. As a {@link FallbackDenyEntityResolver2} it consults the caller's resolver first; as a {@link LexicalHandler} (via {@code DefaultHandler2}) it * tracks the declared subset's identifiers so {@link #onUnresolved} can tell the subset apart from a forbidden external general or parameter entity. It is * stateful, so a fresh instance is installed per reader.</p> */ - private static final class DtdAwareDenyResolver extends Resolvers.FallbackDenyResolver { + private static final class DtdAwareDenyResolver extends FallbackDenyEntityResolver2 { private String dtdPublicId; private String dtdSystemId; @@ -110,7 +110,7 @@ static final class HardeningExpatXMLReader extends HardeningXMLReader { private static final String NAMESPACE_PREFIXES_FEATURE = "http://xml.org/sax/features/namespace-prefixes"; - HardeningExpatXMLReader(final XMLReader delegate, final Resolvers.FallbackDenyResolver floor) { + HardeningExpatXMLReader(final XMLReader delegate, final FallbackDenyEntityResolver2 floor) { super(delegate, floor); } diff --git a/src/main/java/org/apache/commons/xml/StaxHardener.java b/src/main/java/org/apache/commons/xml/StaxHardener.java index 128b44a..57739d6 100644 --- a/src/main/java/org/apache/commons/xml/StaxHardener.java +++ b/src/main/java/org/apache/commons/xml/StaxHardener.java @@ -28,7 +28,7 @@ * <ul> * <li><strong>External DTD subset</strong>: skipped via Zephyr's {@value #ZEPHYR_IGNORE_EXTERNAL_DTD} (best-effort), so a DOCTYPE-only document parses * without a fetch attempt instead of tripping the deny-all resolver below. Woodstox skips it through {@value #WSTX_DTD_RESOLVER} instead.</li> - * <li><strong>External entities</strong>: denied through a non-removable {@link Resolvers.FallbackDenyXMLResolver} floor on the entity-resolution hook, + * <li><strong>External entities</strong>: denied through a non-removable {@link FallbackDenyXMLResolver} floor on the entity-resolution hook, * leaving the standard {@code SUPPORT_DTD} / {@code IS_SUPPORTING_EXTERNAL_ENTITIES} defaults untouched. Woodstox exposes fine-grained hooks, so when all * three apply the factory is Woodstox: {@value #WSTX_DTD_RESOLVER} (empty external subset, but a thrown error on external parameter entities, which share * that hook), {@value #WSTX_ENTITY_RESOLVER} (the floor, denying declared external general entities) and {@value #WSTX_UNDECLARED_ENTITY_RESOLVER} @@ -52,14 +52,14 @@ final class StaxHardener { private static final String ZEPHYR_IGNORE_EXTERNAL_DTD = "http://java.sun.com/xml/stream/properties/ignore-external-dtd"; /** - * Woodstox DTD-subset floor: a {@link Resolvers.FallbackIgnoreXMLResolver} that returns the empty input for the external DTD subset (its inherited policy) + * Woodstox DTD-subset floor: a {@link FallbackIgnoreXMLResolver} that returns the empty input for the external DTD subset (its inherited policy) * but throws on external parameter entities. * * <p>Woodstox calls this hook with {@code entityName == null} for the subset and {@code entityName != null} for parameter-entity expansion (that * discriminator is the 4th {@code resolveEntity} argument; the JDK Zephyr always passes {@code null} there). Applied best-effort, ignored by implementations * that do not recognize the property.</p> */ - private static final class DtdSubsetFloor extends Resolvers.FallbackIgnoreXMLResolver { + private static final class DtdSubsetFloor extends FallbackIgnoreXMLResolver { DtdSubsetFloor() { super(null); @@ -84,10 +84,10 @@ static XMLInputFactory harden(final XMLInputFactory factory) { // HardeningXMLInputFactory, which routes a caller-set resolver into the floor rather than replacing it). The DTD-subset and undeclared-entity hooks skip // (empty input) rather than deny on an unresolved lookup, so a DOCTYPE-only document still parses. if (!(trySetProperty(factory, WSTX_DTD_RESOLVER, new DtdSubsetFloor()) - && trySetProperty(factory, WSTX_ENTITY_RESOLVER, new Resolvers.FallbackDenyXMLResolver(null)) - && trySetProperty(factory, WSTX_UNDECLARED_ENTITY_RESOLVER, new Resolvers.FallbackIgnoreXMLResolver(null)))) { + && trySetProperty(factory, WSTX_ENTITY_RESOLVER, new FallbackDenyXMLResolver(null)) + && trySetProperty(factory, WSTX_UNDECLARED_ENTITY_RESOLVER, new FallbackIgnoreXMLResolver(null)))) { // Fallback (JDK Zephyr or unrecognized): the single resolver carries the deny-all floor. - factory.setXMLResolver(new Resolvers.FallbackDenyXMLResolver(null)); + factory.setXMLResolver(new FallbackDenyXMLResolver(null)); } return new HardeningXMLInputFactory(factory); } diff --git a/src/main/java/org/apache/commons/xml/TransformerHardener.java b/src/main/java/org/apache/commons/xml/TransformerHardener.java index b0d1b30..493e5bb 100644 --- a/src/main/java/org/apache/commons/xml/TransformerHardener.java +++ b/src/main/java/org/apache/commons/xml/TransformerHardener.java @@ -41,7 +41,7 @@ * hardening surface is reachable only through a vendor API.</li> * <li><strong>FSP</strong> ({@link XMLConstants#FEATURE_SECURE_PROCESSING}): required. On XSLTC it enables the runtime evaluator limits; on Xalan it disables * reflection-based extension functions.</li> - * <li><strong>{@link Resolvers.FallbackDenyURIResolver} floor</strong>: required. A deny-all {@link URIResolver} floor, installed by + * <li><strong>{@link FallbackDenyURIResolver} floor</strong>: required. A deny-all {@link URIResolver} floor, installed by * {@link HardeningTransformerFactory} and carried onto every produced {@link Transformer}, blocks {@code xsl:import}/{@code xsl:include} at compile time * and {@code document()} at runtime, the one channel both XSLTC and Xalan route through. A caller-set {@link URIResolver} is routed through the floor * rather than replacing it, so a caller can opt a specific URI in but cannot drop the block.</li> diff --git a/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java b/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java index 42a473f..2762c5c 100644 --- a/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java +++ b/src/test/java/org/apache/commons/xml/EntityResolverFloorTest.java @@ -60,7 +60,7 @@ * * <p>The observable contract on every hardened factory is the same: a resource the caller resolves (returns a non-null value) is allowed, but anything the * caller does not resolve is denied instead of fetched, so a resolver that resolves nothing leaves the block in place. Most factories enforce this with a - * {@link Resolvers.FallbackDenyResolver}-style floor that consults the caller and denies on a {@code null} return; Saxon enforces the equivalent through its + * {@link FallbackDenyEntityResolver2}-style floor that consults the caller and denies on a {@code null} return; Saxon enforces the equivalent through its * {@code ALLOWED_PROTOCOLS} restrictor. Every resolver channel is exercised: the SAX/DOM {@link EntityResolver}, the StAX {@link XMLResolver}, the schema * {@link LSResourceResolver} and the XSLT {@link URIResolver}.</p> */ @@ -364,7 +364,7 @@ void transformerDeniesUnlisted() { /** * A hardened {@link TransformerFactory} with a re-throwing error listener. XSLTC and Xalan enforce the deny through the - * {@link Resolvers.FallbackDenyURIResolver} floor; Saxon enforces it through its {@code ALLOWED_PROTOCOLS} restrictor. Either way a caller-set resolver that + * {@link FallbackDenyURIResolver} floor; Saxon enforces it through its {@code ALLOWED_PROTOCOLS} restrictor. Either way a caller-set resolver that * returns {@code null} cannot re-open the fetch. The strict listener is required because interpretive Xalan routes a blocked {@code xsl:import} through the * error listener and would otherwise recover and compile instead of throwing (XSLTC and Saxon throw regardless). */ diff --git a/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java b/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java index 237d93e..80dbbc9 100644 --- a/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java +++ b/src/test/java/org/apache/commons/xml/ShadingFootprintTest.java @@ -53,22 +53,19 @@ class ShadingFootprintTest { private static final Set<String> DOCUMENT_BUILDER_HARDENER = set( "DocumentBuilderHardener", "HardeningDocumentBuilder", "HardeningDocumentBuilderFactory", CORE, - "Resolvers", "Resolvers$FallbackDenyResolver", "Resolvers$FallbackDenyLSResourceResolver", "Resolvers$FallbackDenyURIResolver", - "Resolvers$FallbackDenyXMLResolver", "Resolvers$FallbackIgnoreXMLResolver"); + "FallbackDenyEntityResolver2"); private static final Set<String> SAX_PARSER_HARDENER = set( "SAXParserHardener", "SAXParserHardener$DtdAwareDenyResolver", "SAXParserHardener$HardeningExpatXMLReader", "HardeningSAXParser", "HardeningSAXParserFactory", "HardeningXMLReader", CORE, - "Resolvers", "Resolvers$FallbackDenyResolver", "Resolvers$FallbackDenyLSResourceResolver", "Resolvers$FallbackDenyURIResolver", - "Resolvers$FallbackDenyXMLResolver", "Resolvers$FallbackIgnoreXMLResolver"); + "FallbackDenyEntityResolver2"); private static final Set<String> STAX_HARDENER = set( "StaxHardener", "StaxHardener$DtdSubsetFloor", "HardeningXMLInputFactory", CORE, - "Resolvers", "Resolvers$FallbackDenyResolver", "Resolvers$FallbackDenyLSResourceResolver", "Resolvers$FallbackDenyURIResolver", - "Resolvers$FallbackDenyXMLResolver", "Resolvers$FallbackIgnoreXMLResolver"); + "FallbackDenyXMLResolver", "FallbackIgnoreXMLResolver"); /** The TrAX/XPath/schema entry points all pull the whole library through {@link XmlFactories}; this is its class count (Phase 4 territory to reduce). */ - private static final int WHOLE_LIBRARY_SIZE = 33; + private static final int WHOLE_LIBRARY_SIZE = 32; /** Entry points reported by the {@link #reportFootprint()} diagnostic, most-focused first, ending with the whole library. */ private static final String[] REPORTED = {
