blob: c1f9a8f290597277b4fcda40211abed2ad9d1fed [file]
/*
* 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.setAttribute;
import static org.apache.commons.xml.JaxpSetters.setProperty;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.IntSupplier;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.stream.XMLInputFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.validation.SchemaFactory;
import org.xml.sax.XMLReader;
/**
* Processing limits applied to the XML parsers configured by this library.
*
* <p>The numeric defaults below are mirrored from the {@code secureValue} column of {@code jdk.xml.internal.XMLSecurityManager.Limit} in
* <strong>JDK 25</strong>.</p>
*
* <p>Each limit can be overridden at runtime via the same JDK system property the JDK itself honours, so applications already tuning the JDK parser get the
* same value applied to the bundled Xerces and Woodstox parsers without further configuration.</p>
*/
final class Limits {
/**
* Maximum number of attributes on a single XML element; JDK 25 secure value.
*
* <p>Override with system property {@value #SP_ELEMENT_ATTRIBUTE_LIMIT}.</p>
*
* <p>JDK 8 through 21 used a less restrictive value of {@code 10000}.</p>
*
* <p>Applied to: stock JDK and Woodstox (via {@value #WSTX_MAX_ATTRIBUTES_PER_ELEMENT}). External Xerces' {@code SecurityManager} does not expose a setter
* for this limit.</p>
*/
private static final int DEFAULT_ELEMENT_ATTRIBUTE_LIMIT = 200;
/**
* Maximum number of entity expansions in a single parse; JDK 25 secure value.
*
* <p>Override with system property {@value #SP_ENTITY_EXPANSION_LIMIT}.</p>
*
* <p>JDK 8 through 21 used a less restrictive value of {@code 64000}.</p>
*
* <p>Applied to: stock JDK, external Xerces (via {@code SecurityManager.setEntityExpansionLimit}) and Woodstox (via {@value #WSTX_MAX_ENTITY_COUNT}).</p>
*/
private static final int DEFAULT_ENTITY_EXPANSION_LIMIT = 2500;
/**
* Cumulative number of replacement characters generated by entity expansions in a single parse; JDK 25 secure value.
*
* <p>Override with system property {@value #SP_ENTITY_REPLACEMENT_LIMIT}.</p>
*
* <p>JDK 8 through 21 used a less restrictive value of {@code 3000000}.</p>
*
* <p>Applied to: stock JDK only. External Xerces' {@code SecurityManager} and Woodstox have no equivalent.</p>
*/
private static final int DEFAULT_ENTITY_REPLACEMENT_LIMIT = 100000;
/**
* Maximum size, in characters, of a single general entity replacement; JDK 25 secure value.
*
* <p>Override with system property {@value #SP_GENERAL_ENTITY_SIZE_LIMIT}.</p>
*
* <p>JDK 8 through 21 imposed no limit ({@code 0}).</p>
*
* <p>Applied to: stock JDK only. External Xerces' {@code SecurityManager} and Woodstox have no equivalent.</p>
*/
private static final int DEFAULT_GENERAL_ENTITY_SIZE_LIMIT = 100000;
/**
* Maximum nesting depth of XML elements; JDK 25 secure value.
*
* <p>Override with system property {@value #SP_MAX_ELEMENT_DEPTH}.</p>
*
* <p>JDK 8 through 21 imposed no limit ({@code 0}).</p>
*
* <p>Applied to: stock JDK and Woodstox (via {@value #WSTX_MAX_ELEMENT_DEPTH}). External Xerces' {@code SecurityManager} does not expose a setter for this
* limit.</p>
*/
private static final int DEFAULT_MAX_ELEMENT_DEPTH = 100;
/**
* Maximum length, in characters, of an XML name (element name, attribute name, namespace prefix, etc); JDK 25 secure value.
*
* <p>Override with system property {@value #SP_MAX_NAME_LIMIT}.</p>
*
* <p>Applied to: stock JDK only. External Xerces' {@code SecurityManager} and Woodstox have no equivalent.</p>
*/
private static final int DEFAULT_MAX_NAME_LIMIT = 1000;
/**
* Maximum number of XSD particles a {@code maxOccurs} attribute may be expanded to; JDK 25 secure value.
*
* <p>Override with system property {@value #SP_MAX_OCCUR_LIMIT}.</p>
*
* <p>Applied to: external Xerces (via {@code SecurityManager.setMaxOccurNodeLimit}). The stock JDK enforces this XSD limit internally via its own
* {@code XMLSecurityManager}; Woodstox is StAX-only and has no schema engine.</p>
*/
private static final int DEFAULT_MAX_OCCUR_LIMIT = 5000;
/**
* Maximum size, in characters, of a single parameter entity replacement (DTD only); JDK 25 secure value.
*
* <p>Override with system property {@value #SP_PARAMETER_ENTITY_SIZE_LIMIT}.</p>
*
* <p>JDK 8 through 21 used a less restrictive value of {@code 1000000}.</p>
*
* <p>Applied to: stock JDK only. External Xerces' {@code SecurityManager} and Woodstox have no equivalent.</p>
*/
private static final int DEFAULT_PARAMETER_ENTITY_SIZE_LIMIT = 15000;
/**
* Cumulative size, in characters, of all entity replacements in a single parse; JDK 25 secure value.
*
* <p>Override with system property {@value #SP_TOTAL_ENTITY_SIZE_LIMIT}.</p>
*
* <p>JDK 8 through 21 used a less restrictive value of {@code 50000000}.</p>
*
* <p>Applied to: stock JDK only. External Xerces' {@code SecurityManager} and Woodstox have no equivalent.</p>
*/
private static final int DEFAULT_TOTAL_ENTITY_SIZE_LIMIT = 100000;
/**
* URL-form property name to current value, in a stable order, for every limit that the stock JDK's parsers accept. Iterated by every {@code applyToJdk*}
* method so each JDK factory or parser instance ends up with the same set of limits applied.
*/
private static final Map<String, IntSupplier> JDK_LIMITS;
/**
* URL form of the JDK's per-element attribute limit; recognised across JDK 8 through 25.
*/
private static final String JDK_URL_ELEMENT_ATTRIBUTE_LIMIT = "http://www.oracle.com/xml/jaxp/properties/elementAttributeLimit";
/**
* URL form of the JDK's entity-expansion limit; recognised across JDK 8 through 25. The short form {@code jdk.xml.entityExpansionLimit} is JDK 11+ only.
*/
private static final String JDK_URL_ENTITY_EXPANSION_LIMIT = "http://www.oracle.com/xml/jaxp/properties/entityExpansionLimit";
/**
* URL form of the JDK's cumulative entity-replacement limit; recognised across JDK 8 through 25.
*/
private static final String JDK_URL_ENTITY_REPLACEMENT_LIMIT = "http://www.oracle.com/xml/jaxp/properties/entityReplacementLimit";
/**
* URL form of the JDK's per-general-entity size limit; recognised across JDK 8 through 25.
*/
private static final String JDK_URL_GENERAL_ENTITY_SIZE_LIMIT = "http://www.oracle.com/xml/jaxp/properties/maxGeneralEntitySizeLimit";
/**
* URL form of the JDK's maximum element depth; recognised across JDK 8 through 25.
*/
private static final String JDK_URL_MAX_ELEMENT_DEPTH = "http://www.oracle.com/xml/jaxp/properties/maxElementDepth";
/**
* URL form of the JDK's maximum XML name length; recognised across JDK 8 through 25.
*/
private static final String JDK_URL_MAX_NAME_LIMIT = "http://www.oracle.com/xml/jaxp/properties/maxXMLNameLimit";
/**
* URL form of the JDK's per-parameter-entity size limit; recognised across JDK 8 through 25.
*/
private static final String JDK_URL_PARAMETER_ENTITY_SIZE_LIMIT = "http://www.oracle.com/xml/jaxp/properties/maxParameterEntitySizeLimit";
/**
* URL form of the JDK's total entity-size limit; recognised across JDK 8 through 25.
*/
private static final String JDK_URL_TOTAL_ENTITY_SIZE_LIMIT = "http://www.oracle.com/xml/jaxp/properties/totalEntitySizeLimit";
/**
* JDK system property name for the per-element attribute limit.
*/
private static final String SP_ELEMENT_ATTRIBUTE_LIMIT = "jdk.xml.elementAttributeLimit";
/**
* JDK system property name for the entity-expansion limit; same name the JDK Zephyr/Xerces stack honours.
*/
private static final String SP_ENTITY_EXPANSION_LIMIT = "jdk.xml.entityExpansionLimit";
/**
* JDK system property name for the cumulative entity-replacement limit.
*/
private static final String SP_ENTITY_REPLACEMENT_LIMIT = "jdk.xml.entityReplacementLimit";
/**
* JDK system property name for the per-general-entity size limit.
*/
private static final String SP_GENERAL_ENTITY_SIZE_LIMIT = "jdk.xml.maxGeneralEntitySizeLimit";
/**
* JDK system property name for the maximum element depth.
*/
private static final String SP_MAX_ELEMENT_DEPTH = "jdk.xml.maxElementDepth";
/**
* JDK system property name for the maximum XML name length.
*/
private static final String SP_MAX_NAME_LIMIT = "jdk.xml.maxXMLNameLimit";
/**
* JDK system property name for the {@code maxOccurs} expansion limit.
*/
private static final String SP_MAX_OCCUR_LIMIT = "jdk.xml.maxOccurLimit";
/**
* JDK system property name for the per-parameter-entity size limit.
*/
private static final String SP_PARAMETER_ENTITY_SIZE_LIMIT = "jdk.xml.maxParameterEntitySizeLimit";
/**
* JDK system property name for the cumulative entity size limit.
*/
private static final String SP_TOTAL_ENTITY_SIZE_LIMIT = "jdk.xml.totalEntitySizeLimit";
/**
* Woodstox property: maximum number of attributes on a single XML element.
*/
private static final String WSTX_MAX_ATTRIBUTES_PER_ELEMENT = "com.ctc.wstx.maxAttributesPerElement";
/**
* Woodstox property: maximum nesting depth of XML elements.
*/
private static final String WSTX_MAX_ELEMENT_DEPTH = "com.ctc.wstx.maxElementDepth";
/**
* Woodstox property: maximum number of entity expansions in a single parse.
*/
private static final String WSTX_MAX_ENTITY_COUNT = "com.ctc.wstx.maxEntityCount";
static {
final Map<String, IntSupplier> map = new LinkedHashMap<>();
map.put(JDK_URL_ENTITY_EXPANSION_LIMIT, Limits::getEntityExpansionLimit);
map.put(JDK_URL_ELEMENT_ATTRIBUTE_LIMIT, Limits::getElementAttributeLimit);
map.put(JDK_URL_MAX_ELEMENT_DEPTH, Limits::getMaxElementDepth);
map.put(JDK_URL_TOTAL_ENTITY_SIZE_LIMIT, Limits::getTotalEntitySizeLimit);
map.put(JDK_URL_GENERAL_ENTITY_SIZE_LIMIT, Limits::getGeneralEntitySizeLimit);
map.put(JDK_URL_PARAMETER_ENTITY_SIZE_LIMIT, Limits::getParameterEntitySizeLimit);
map.put(JDK_URL_ENTITY_REPLACEMENT_LIMIT, Limits::getEntityReplacementLimit);
map.put(JDK_URL_MAX_NAME_LIMIT, Limits::getMaxNameLimit);
JDK_LIMITS = Collections.unmodifiableMap(map);
}
/**
* Sets every JDK-supported limit on a stock JDK {@link DocumentBuilderFactory}.
*
* @param factory The target factory to modify.
*/
static void applyToJdkDom(final DocumentBuilderFactory factory) {
JDK_LIMITS.forEach((name, supplier) -> setAttribute(factory, name, Integer.toString(supplier.getAsInt())));
}
/**
* Sets every JDK-supported limit on a stock JDK {@link SchemaFactory}.
*
* @param factory The target factory to modify.
*/
static void applyToJdkSchema(final SchemaFactory factory) {
JDK_LIMITS.forEach((name, supplier) -> setProperty(factory, name, Integer.toString(supplier.getAsInt())));
}
/**
* Sets every JDK-supported limit on the stock JDK's {@link XMLInputFactory}.
*
* @param factory The target factory to modify.
*/
static void applyToJdkStax(final XMLInputFactory factory) {
JDK_LIMITS.forEach((name, supplier) -> setProperty(factory, name, Integer.toString(supplier.getAsInt())));
}
/**
* Sets every JDK-supported limit on a stock JDK {@link TransformerFactory}.
*
* @param factory The target factory to modify.
*/
static void applyToJdkTransformer(final TransformerFactory factory) {
JDK_LIMITS.forEach((name, supplier) -> setAttribute(factory, name, Integer.toString(supplier.getAsInt())));
}
/**
* Sets every JDK-supported limit on a stock JDK {@link XMLReader}.
*
* @param reader The target reader to modify.
*/
static void applyToJdkXmlReader(final XMLReader reader) {
JDK_LIMITS.forEach((name, supplier) -> setProperty(reader, name, Integer.toString(supplier.getAsInt())));
}
/**
* Sets every JDK-supported limit on a Woodstox {@link XMLInputFactory}.
*
* @param factory The target factory to modify.
*/
static void applyToWoodstox(final XMLInputFactory factory) {
setProperty(factory, WSTX_MAX_ENTITY_COUNT, getEntityExpansionLimit());
setProperty(factory, WSTX_MAX_ATTRIBUTES_PER_ELEMENT, getElementAttributeLimit());
setProperty(factory, WSTX_MAX_ELEMENT_DEPTH, getMaxElementDepth());
}
/**
* Sets every JDK-supported limit on a Xerces {@code org.apache.xerces.util.SecurityManager}.
*
* @param securityManager an instance of {@code org.apache.xerces.util.SecurityManager}; if {@code null} the call is a no-op.
*/
static void applyToXerces(final Object securityManager) {
if (securityManager == null) {
return;
}
try {
final Class<?> clazz = securityManager.getClass();
clazz.getMethod("setEntityExpansionLimit", int.class).invoke(securityManager, getEntityExpansionLimit());
clazz.getMethod("setMaxOccurNodeLimit", int.class).invoke(securityManager, getMaxOccurLimit());
} catch (final ReflectiveOperationException ignore) {
// Class on the classpath is not the expected Xerces SecurityManager; leave the limits at whatever defaults it carries.
}
}
private static int getElementAttributeLimit() {
return read(SP_ELEMENT_ATTRIBUTE_LIMIT, DEFAULT_ELEMENT_ATTRIBUTE_LIMIT);
}
private static int getEntityExpansionLimit() {
return read(SP_ENTITY_EXPANSION_LIMIT, DEFAULT_ENTITY_EXPANSION_LIMIT);
}
private static int getEntityReplacementLimit() {
return read(SP_ENTITY_REPLACEMENT_LIMIT, DEFAULT_ENTITY_REPLACEMENT_LIMIT);
}
private static int getGeneralEntitySizeLimit() {
return read(SP_GENERAL_ENTITY_SIZE_LIMIT, DEFAULT_GENERAL_ENTITY_SIZE_LIMIT);
}
private static int getMaxElementDepth() {
return read(SP_MAX_ELEMENT_DEPTH, DEFAULT_MAX_ELEMENT_DEPTH);
}
private static int getMaxNameLimit() {
return read(SP_MAX_NAME_LIMIT, DEFAULT_MAX_NAME_LIMIT);
}
private static int getMaxOccurLimit() {
return read(SP_MAX_OCCUR_LIMIT, DEFAULT_MAX_OCCUR_LIMIT);
}
private static int getParameterEntitySizeLimit() {
return read(SP_PARAMETER_ENTITY_SIZE_LIMIT, DEFAULT_PARAMETER_ENTITY_SIZE_LIMIT);
}
private static int getTotalEntitySizeLimit() {
return read(SP_TOTAL_ENTITY_SIZE_LIMIT, DEFAULT_TOTAL_ENTITY_SIZE_LIMIT);
}
private static int read(final String systemPropertyName, final int defaultValue) {
final String raw = System.getProperty(systemPropertyName);
if (raw == null || raw.isEmpty()) {
return defaultValue;
}
try {
return Integer.parseInt(raw.trim());
} catch (final NumberFormatException e) {
return defaultValue;
}
}
private Limits() {
}
}