ppkarwasz commented on code in PR #56:
URL: https://github.com/apache/commons-xml/pull/56#discussion_r3880004870


##########
src/main/java/org/apache/commons/xml/HardeningSAXParserFactory.java:
##########
@@ -41,6 +48,108 @@
  */
 public final class HardeningSAXParserFactory {
 
+    /** Class name of Android's Expat-backed {@link XMLReader}. */
+    private static final String ANDROID_EXPAT_READER = 
"org.apache.harmony.xml.ExpatReader";
+    /** Class name of Android's Harmony-based {@link SAXParserFactory}, backed 
by the native Expat parser. */
+    private static final String ANDROID_SAX_PARSER_FACTORY = 
"org.apache.harmony.xml.parsers.SAXParserFactoryImpl";
+
+    /**
+     * Capability-driven hardening for any {@link SAXParserFactory} on the 
classpath.
+     *
+     * <p>Rather than branching on the implementation class, this method 
probes what the parser supports and adapts. Because
+     * {@link SAXParserFactory} exposes only a feature API and no property 
API, the per-parse configuration runs on each {@link XMLReader} the factory 
produces,
+     * funnelled through the nested wrapper into {@link 
#harden(XMLReader)}:</p>
+     * <ul>
+     *     <li><strong>Android</strong> (Harmony / Expat): {@link 
XMLConstants#FEATURE_SECURE_PROCESSING FSP} and the JAXP 1.5 {@code 
ACCESS_EXTERNAL_*} properties
+     *         are not recognized, and libexpat enforces its own Billion 
Laughs check, so neither is applied. Two fixups are still needed: an ignore-all 
resolver
+     *         (Expat ignores external fetches silently when no resolver is 
set; the floor keeps that behavior non-bypassable, resolving anything 
unresolved to
+     *         empty), and a {@link HardeningExpatXMLReader} so the 
unsupported {@code namespace-prefixes} feature is rejected at
+     *         configuration time rather than mid-parse.</li>
+     *     <li><strong>FSP</strong>: required on every other reader. 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 reader is 
wrapped in a {@link HardeningXMLReader} that 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_*} properties 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; never {@code null}.
+     * @return a hardened factory.
+     */
+    static SAXParserFactory harden(final SAXParserFactory factory) {
+        // Required: enables the implementation's security manager, which 
carries the limits. Android's Expat rejects FSP, so it is skipped there.
+        if (!ANDROID_SAX_PARSER_FACTORY.equals(factory.getClass().getName())) {
+            setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        }
+        // The per-parse hardening (limits, entity blocking, Android fixups) 
lives in harden(XMLReader) because SAXParserFactory has no property API.
+        return new Wrapper(factory);
+    }
+
+    /**
+     * Rewrites a {@link Source} so that any SAX parsing it triggers runs 
through a hardened {@link XMLReader}.
+     * <p>
+     * Only a {@link StreamSource} or a {@link SAXSource} without a reader is 
enriched with a hardened, namespace-aware reader; other source kinds are 
returned
+     * as-is. Used by the TrAX and schema wrappers to route every source they 
parse through the SAX hardening path.
+     * </p>
+     *
+     * @param source the source to harden; never {@code null}.
+     * @return a hardened source.
+     * @throws TransformerConfigurationException if a hardened reader cannot 
be obtained.
+     * @throws FactoryConfigurationError         Thrown from a factory in case 
of a {@link java.util.ServiceConfigurationError service
+     *                                           configuration error} or if 
the implementation is not available or cannot be instantiated.
+     */
+    static Source harden(final Source source) throws 
TransformerConfigurationException {
+        if (source instanceof StreamSource || source instanceof SAXSource && 
((SAXSource) source).getXMLReader() == null) {
+            final InputSource inputSource = 
SAXSource.sourceToInputSource(source);
+            return inputSource == null ? source : new 
SAXSource(newHardenedReader(), inputSource);
+        }
+        return source;
+    }
+
+    /**
+     * Hardens an existing {@link XMLReader}.
+     *
+     * @param reader The reader to harden; never {@code null}.
+     * @return A hardened reader.
+     * @throws IllegalStateException if a required hardening setting cannot be 
applied to the underlying implementation.
+     */
+    static XMLReader harden(final XMLReader reader) {
+        if (reader instanceof HardeningXMLReader) {
+            // Already hardened (for example, a reader from a hardened factory 
passed back through harden(XMLReader)); the floor is already in place.
+            return reader;
+        }
+        if (ANDROID_EXPAT_READER.equals(reader.getClass().getName())) {
+            // Expat ignores external fetches when no resolver is set; the 
ignore-all floor keeps that behavior non-bypassable (routing a caller-set 
resolver,
+            // including SAXParser.parse's handler, through it and resolving 
anything unresolved to empty) and, via HardeningExpatXMLReader, rejects the
+            // unsupported namespace-prefixes feature eagerly rather than 
mid-parse.
+            return new HardeningExpatXMLReader(reader);
+        }
+        // Required: enables the JDK XMLSecurityManager / Xerces 
SecurityManager limits.
+        setFeature(reader, XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        // Required: HardeningXMLReader installs an ignore-all EntityResolver 
floor on the reader.
+        // That floor blocks external DTD, entity, schema and xi:include 
fetches in one place: no ACCESS_EXTERNAL_* properties are needed here.
+        // Callers can chain their resolvers, but not override the floor.
+        return new HardeningXMLReader(reader);
+    }
+
+    /**
+     * Creates a new hardened, namespace-aware {@link XMLReader} for the TrAX 
wrappers to parse sources with.
+     *
+     * @return a hardened reader.
+     * @throws TransformerConfigurationException if a hardened reader cannot 
be obtained.
+     * @throws FactoryConfigurationError Thrown from a factory in case of a 
{@link java.util.ServiceConfigurationError service
+     *                                   configuration error} or if the 
implementation is not available or cannot be instantiated.
+     */
+    static XMLReader newHardenedReader() throws 
TransformerConfigurationException {

Review Comment:
   Good point, go ahead! :100:



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to