Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions src/conf/spotbugs-exclude-filter.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,4 @@
xmlns="https://github.com/spotbugs/filter/3.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://github.com/spotbugs/filter/3.0.0 https://raw.githubusercontent.com/spotbugs/spotbugs/3.1.0/spotbugs/etc/findbugsfilter.xsd">
<!-- Looks like an NPE: the DTD_SUBSET_ONLY lambda forwards a known-null entityName to the ignore-all resolver. -->
<Match>
<Class name="org.apache.commons.xml.StaxHardener" />
<Method name="lambda$static$0" />
<Bug pattern="NP_LOAD_OF_KNOWN_NULL_VALUE" />
</Match>
</FindBugsFilter>
86 changes: 0 additions & 86 deletions src/main/java/org/apache/commons/xml/DocumentBuilderHardener.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public Source resolve(final String href, final String base) throws TransformerEx
final Source resolved = delegate != null ? delegate.resolve(href, base) : null;
if (resolved != null) {
// The implementation parses the opted-in handle with an internal reader at its own defaults; the rewrite hands it a hardened reader instead.
return SAXParserHardener.hardenSource(resolved);
return HardeningSAXParserFactory.harden(resolved);
}
if (HardeningException.throwOnUnresolved()) {
throw new TransformerException(HardeningException.forbidden("uri", null, null, href, base));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,41 @@
*/
public final class HardeningDocumentBuilderFactory {

/** Class name of Android's Harmony-based {@link DocumentBuilderFactory}, which exposes no hardening surface. */
private static final String ANDROID_DOCUMENT_BUILDER_FACTORY = "org.apache.harmony.xml.parsers.DocumentBuilderFactoryImpl";

/**
* Capability-driven hardening for any {@link DocumentBuilderFactory} on the classpath.
*
* <p>Rather than branching on the implementation class, this method probes what the factory supports and adapts:</p>
* <ul>
* <li><strong>Android</strong> (Harmony / KXmlParser): recognized by class name and left untouched. It exposes no {@link XMLConstants#FEATURE_SECURE_PROCESSING
* FSP}, no JAXP 1.5 {@code ACCESS_EXTERNAL_*} and no attribute API at all, while KXmlParser silently drops user-defined entities, so there is nothing to
* apply.</li>
* <li><strong>FSP</strong>: required. It switches on the implementation's built-in security manager, which is what carries the processing limits.</li>
* <li><strong>Ignore-all resolver floor</strong>: every produced {@link DocumentBuilder} is wrapped by the nested wrapper, which keeps an
* ignore-all {@link EntityResolver} floor. That floor blocks external DTD, entity, schema and {@code xi:include} fetches in one place: the stock JDK's
* XInclude processor ignores {@code ACCESS_EXTERNAL_*} and consults the {@link EntityResolver} instead, so no {@code ACCESS_EXTERNAL_*} attributes are
* needed here. A caller can chain its own resolver onto the floor to allow-list resources, but cannot remove it.</li>
* </ul>
*
* @param factory The factory to harden.
* @return A new hardened factory or the original factory, as-is, if it is a known Android factory.
* @throws HardeningException Thrown if a (non-Andoid) factory cannot support the secure processing feature {@link XMLConstants#FEATURE_SECURE_PROCESSING}.
*/
static DocumentBuilderFactory harden(final DocumentBuilderFactory factory) {
// Android exposes no FSP, ACCESS_EXTERNAL_* or attribute API, and KXmlParser drops user-defined entities; nothing to apply.
if (ANDROID_DOCUMENT_BUILDER_FACTORY.equals(factory.getClass().getName())) {
return factory;
}
// Required: enables the implementation's security manager, which carries the limits.
setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
// Required: the wrapper installs an ignore-all EntityResolver floor on every DocumentBuilder.
// That floor blocks external DTD, entity, schema and xi:include fetches in one place: no ACCESS_EXTERNAL_* attributes are needed here.
// Callers can chain their resolvers, but not override the floor.
return new Wrapper(factory);
}

/**
* Returns a new, hardened {@link DocumentBuilderFactory}.
* <p>
Expand All @@ -57,17 +92,24 @@ public final class HardeningDocumentBuilderFactory {
* implementation is not available or cannot be instantiated.
*/
public static DocumentBuilderFactory newInstance() {
return DocumentBuilderHardener.harden(DocumentBuilderFactory.newInstance());
return harden(DocumentBuilderFactory.newInstance());
}

/**
* Wraps a prepared delegate in the hardening wrapper; called by the hardener once the required settings are applied.
* Sets a feature on the given factory, throwing a {@link HardeningException} if the implementation does not recognize it.
*
* @param delegate the delegate to wrap; must not be {@code null}.
* @return The hardened factory.
* @param factory The factory to harden.
* @param feature The feature to set.
* @param value The value to set.
* @throws HardeningException Thrown if this factory or the {@code XPath}s it creates cannot support this feature.
* @throws NullPointerException Thrown if the {@code feature} parameter is null.
*/
static DocumentBuilderFactory wrap(final DocumentBuilderFactory delegate) {
return new Wrapper(delegate);
private static void setFeature(final DocumentBuilderFactory factory, final String feature, final boolean value) {
try {
factory.setFeature(feature, value);
} catch (final ParserConfigurationException e) {
throw HardeningException.settingFailed("feature", feature, factory, e);
}
}

private HardeningDocumentBuilderFactory() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public Schema getSchema() {
@Override
public XMLReader getXMLReader() throws SAXException {
if (hardenedReader == null) {
hardenedReader = SAXParserHardener.hardenReader(delegate.getXMLReader());
hardenedReader = HardeningSAXParserFactory.harden(delegate.getXMLReader());
}
return hardenedReader;
}
Expand Down
Loading
Loading