| /* |
| * 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.apache.commons.xml.JaxpSetters.setFeature; |
| import static org.apache.commons.xml.JaxpSetters.setOptionalFeature; |
| import static org.apache.commons.xml.JaxpSetters.trySetAttribute; |
| |
| import javax.xml.XMLConstants; |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| |
| import org.xml.sax.EntityResolver; |
| |
| /** |
| * Capability-driven hardening for any {@link DocumentBuilderFactory} on the classpath. |
| * |
| * <p>Rather than branching on the implementation class, {@link #harden(DocumentBuilderFactory)} 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>{@code XERCES_LOAD_EXTERNAL_DTD}</strong>: optional. Where supported, it skips the external DTD subset on non-validating parsers so a |
| * DOCTYPE-only document parses without a fetch attempt. If not supported, the fetch will throw instead, due to the following settings.</li> |
| * <li><strong>Limits</strong>: applied best-effort by {@link Limits#tryApply(DocumentBuilderFactory)}, which adapts to the JDK attribute limits or Xerces' |
| * {@code SecurityManager} as appropriate.</li> |
| * <li><strong>{@code ACCESS_EXTERNAL_DTD}</strong>: the dividing capability. Implementations that honour it (the JDK-internal Xerces) block external fetches |
| * through the JAXP 1.5 properties and are returned as-is. Implementations that reject it (the external Xerces distribution) are wrapped in a |
| * {@link HardeningDocumentBuilderFactory} so a deny-all {@link EntityResolver} floor is installed on every {@link DocumentBuilder} produced.</li> |
| * </ul> |
| */ |
| final class DocumentBuilderHardener { |
| |
| /** 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"; |
| |
| /** Xerces feature: load the external DTD subset for non-validating parsers. */ |
| private static final String XERCES_LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; |
| |
| 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); |
| // Optional, implementation-based: JDK attribute limits or Xerces' SecurityManager. |
| Limits.tryApply(factory); |
| // Optional: skip the external DTD subset on non-validating parsers so DOCTYPE-only documents parse without a blocked fetch attempt. |
| setOptionalFeature(factory, XERCES_LOAD_EXTERNAL_DTD, false); |
| // ACCESS_EXTERNAL_* support is the dividing capability between JAXP 1.5 implementations and older ones. |
| if (trySetAttribute(factory, XMLConstants.ACCESS_EXTERNAL_DTD, "") |
| && trySetAttribute(factory, XMLConstants.ACCESS_EXTERNAL_SCHEMA, "")) { |
| // Honoured: the JAXP 1.5 properties block external fetches, so the bare factory is already hardened. |
| return factory; |
| } |
| // Rejected: external Xerces ignores ACCESS_EXTERNAL_*; wrap every DocumentBuilder so a deny-all resolver floor blocks external fetches. |
| return new HardeningDocumentBuilderFactory(factory); |
| } |
| |
| private DocumentBuilderHardener() { |
| } |
| } |