| /* |
| * 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() { |
| } |
| } |