| /* | |
| * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package jdk.xml.internal; | |
| import com.sun.org.apache.xerces.internal.util.SecurityManager; | |
| import java.util.Collections; | |
| import java.util.HashMap; | |
| import java.util.Map; | |
| import java.util.Objects; | |
| import java.util.concurrent.CopyOnWriteArrayList; | |
| import java.util.stream.Collectors; | |
| import javax.xml.catalog.CatalogManager; | |
| import javax.xml.catalog.CatalogResolver; | |
| import javax.xml.catalog.CatalogResolver.NotFoundAction; | |
| import javax.xml.stream.XMLInputFactory; | |
| import jdk.xml.internal.JdkProperty.State; | |
| import jdk.xml.internal.JdkProperty.ImplPropMap; | |
| import org.xml.sax.SAXException; | |
| /** | |
| * This class manages standard and implementation-specific limitations. | |
| * | |
| */ | |
| public final class XMLSecurityManager implements Cloneable { | |
| public static final String DTD_KEY = JdkConstants.DTD_PROPNAME; | |
| // Xerces Feature | |
| public static final String DISALLOW_DTD = "http://apache.org/xml/features/disallow-doctype-decl"; | |
| public static final String LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; | |
| // StAX feature | |
| public static final String ZEPHYR_PROPERTY_PREFIX = "http://java.sun.com/xml/stream/properties/" ; | |
| public static final String IGNORE_EXTERNAL_DTD = ZEPHYR_PROPERTY_PREFIX + "ignore-external-dtd"; | |
| // Valid values for the DTD property | |
| public static final String DTD_ALLOW = "allow"; | |
| public static final String DTD_IGNORE = "ignore"; | |
| public static final String DTD_DENY = "deny"; | |
| static final Map<String, Integer> DTD_MAP; | |
| // Source Level JDK 8 | |
| static { | |
| Map<String, Integer> map = new HashMap<>(); | |
| map.put(DTD_ALLOW, 0); | |
| map.put(DTD_IGNORE, 1); | |
| map.put(DTD_DENY, 2); | |
| DTD_MAP = Collections.unmodifiableMap(map); | |
| } | |
| // Valid values for Catalog Resolve, and mappings between the string and | |
| // interger values | |
| static final Map<String, Integer> CR_MAP; | |
| // Source Level JDK 8 | |
| static { | |
| Map<String, Integer> map = new HashMap<>(); | |
| map.put("continue", 0); | |
| map.put("ignore", 1); | |
| map.put("strict", 2); | |
| CR_MAP = Collections.unmodifiableMap(map); | |
| } | |
| // Value converter for properties of type Boolean | |
| private static final BooleanMapper BOOLMAPPER = new BooleanMapper(); | |
| // Value converter for properties of type Integer | |
| private static final IntegerMapper INTMAPPER = new IntegerMapper(); | |
| // DTD value mapper | |
| private static final StringMapper DTDMAPPER = new StringMapper(DTD_MAP); | |
| // Catalog Resolve value mapper | |
| private static final StringMapper CRMAPPER = new StringMapper(CR_MAP); | |
| /** | |
| * Limits managed by the security manager | |
| */ | |
| @SuppressWarnings("deprecation") | |
| public static enum Limit { | |
| ENTITY_EXPANSION_LIMIT("EntityExpansionLimit", JdkConstants.JDK_ENTITY_EXPANSION_LIMIT, | |
| JdkConstants.SP_ENTITY_EXPANSION_LIMIT, JdkConstants.ENTITY_EXPANSION_LIMIT, 2500, 2500, Processor.PARSER, INTMAPPER), | |
| MAX_OCCUR_NODE_LIMIT("MaxOccurLimit", JdkConstants.JDK_MAX_OCCUR_LIMIT, | |
| JdkConstants.SP_MAX_OCCUR_LIMIT, JdkConstants.MAX_OCCUR_LIMIT, 5000, 5000, Processor.PARSER, INTMAPPER), | |
| ELEMENT_ATTRIBUTE_LIMIT("ElementAttributeLimit", JdkConstants.JDK_ELEMENT_ATTRIBUTE_LIMIT, | |
| JdkConstants.SP_ELEMENT_ATTRIBUTE_LIMIT, JdkConstants.ELEMENT_ATTRIBUTE_LIMIT, 200, 200, Processor.PARSER, INTMAPPER), | |
| TOTAL_ENTITY_SIZE_LIMIT("TotalEntitySizeLimit", JdkConstants.JDK_TOTAL_ENTITY_SIZE_LIMIT, | |
| JdkConstants.SP_TOTAL_ENTITY_SIZE_LIMIT, null, 100000, 100000, Processor.PARSER, INTMAPPER), | |
| GENERAL_ENTITY_SIZE_LIMIT("MaxEntitySizeLimit", JdkConstants.JDK_GENERAL_ENTITY_SIZE_LIMIT, | |
| JdkConstants.SP_GENERAL_ENTITY_SIZE_LIMIT, null, 100000, 100000, Processor.PARSER, INTMAPPER), | |
| PARAMETER_ENTITY_SIZE_LIMIT("MaxParameterEntitySizeLimit", JdkConstants.JDK_PARAMETER_ENTITY_SIZE_LIMIT, | |
| JdkConstants.SP_PARAMETER_ENTITY_SIZE_LIMIT, null, 15000, 15000, Processor.PARSER, INTMAPPER), | |
| MAX_ELEMENT_DEPTH_LIMIT("MaxElementDepthLimit", JdkConstants.JDK_MAX_ELEMENT_DEPTH, | |
| JdkConstants.SP_MAX_ELEMENT_DEPTH, null, 100, 100, Processor.PARSER, INTMAPPER), | |
| MAX_NAME_LIMIT("MaxXMLNameLimit", JdkConstants.JDK_XML_NAME_LIMIT, | |
| JdkConstants.SP_XML_NAME_LIMIT, null, 1000, 1000, Processor.PARSER, INTMAPPER), | |
| ENTITY_REPLACEMENT_LIMIT("EntityReplacementLimit", JdkConstants.JDK_ENTITY_REPLACEMENT_LIMIT, | |
| JdkConstants.SP_ENTITY_REPLACEMENT_LIMIT, null, 100000, 100000, Processor.PARSER, INTMAPPER), | |
| XPATH_GROUP_LIMIT("XPathGroupLimit", JdkConstants.XPATH_GROUP_LIMIT, | |
| JdkConstants.XPATH_GROUP_LIMIT, null, 10, 10, Processor.XPATH, INTMAPPER), | |
| XPATH_OP_LIMIT("XPathExprOpLimit", JdkConstants.XPATH_OP_LIMIT, | |
| JdkConstants.XPATH_OP_LIMIT, null, 100, 100, Processor.XPATH, INTMAPPER), | |
| XPATH_TOTALOP_LIMIT("XPathTotalOpLimit", JdkConstants.XPATH_TOTALOP_LIMIT, | |
| JdkConstants.XPATH_TOTALOP_LIMIT, null, 10000, 10000, Processor.XPATH, INTMAPPER), | |
| DTD("DTDProperty", JdkConstants.DTD_PROPNAME, JdkConstants.DTD_PROPNAME, null, | |
| JdkConstants.ALLOW, JdkConstants.ALLOW, Processor.PARSER, DTDMAPPER), | |
| XERCES_DISALLOW_DTD("disallowDTD", DISALLOW_DTD, null, null, 0, 0, Processor.PARSER, BOOLMAPPER), | |
| STAX_SUPPORT_DTD("supportDTD", XMLInputFactory.SUPPORT_DTD, null, null, 1, 1, Processor.PARSER, BOOLMAPPER), | |
| JDKCATALOG_RESOLVE("JDKCatalogResolve", JdkConstants.JDKCATALOG_RESOLVE, JdkConstants.JDKCATALOG_RESOLVE, null, | |
| JdkConstants.CONTINUE, JdkConstants.CONTINUE, Processor.PARSER, CRMAPPER), | |
| ; | |
| final String key; | |
| final String apiProperty; | |
| final String systemProperty; | |
| final String spOld; | |
| final int defaultValue; | |
| final int secureValue; | |
| final Processor processor; | |
| final ValueMapper mapper; | |
| Limit(String key, String apiProperty, String systemProperty, String spOld, int value, | |
| int secureValue, Processor processor, ValueMapper mapper) { | |
| this.key = key; | |
| this.apiProperty = apiProperty; | |
| this.systemProperty = systemProperty; | |
| this.spOld = spOld; | |
| this.defaultValue = value; | |
| this.secureValue = secureValue; | |
| this.processor = processor; | |
| this.mapper = mapper; | |
| } | |
| /** | |
| * Checks whether the specified name is a limit. Checks both the | |
| * property and System Property which is now the new property name. | |
| * | |
| * @param name the specified name | |
| * @return true if there is a match, false otherwise | |
| */ | |
| public boolean is(String name) { | |
| // current spec: new property name == systemProperty | |
| return (systemProperty != null && systemProperty.equals(name)) || | |
| // current spec: apiProperty is legacy | |
| (apiProperty.equals(name)); | |
| } | |
| /** | |
| * Returns the state of a property name. By the specification as of JDK 17, | |
| * the "jdk.xml." prefixed System property name is also the current API | |
| * name. The URI-based qName is legacy. | |
| * | |
| * @param name the property name | |
| * @return the state of the property name, null if no match | |
| */ | |
| public State getState(String name) { | |
| if (systemProperty != null && systemProperty.equals(name)) { | |
| return State.APIPROPERTY; | |
| } else if (apiProperty.equals(name)) { | |
| //the URI-style qName is legacy | |
| return State.LEGACY_APIPROPERTY; | |
| } | |
| return null; | |
| } | |
| public String key() { | |
| return key; | |
| } | |
| public String apiProperty() { | |
| return apiProperty; | |
| } | |
| public String systemProperty() { | |
| return systemProperty; | |
| } | |
| // returns legacy System Property | |
| public String spOld() { | |
| return spOld; | |
| } | |
| public int defaultValue() { | |
| return defaultValue; | |
| } | |
| public boolean isSupported(Processor p) { | |
| return processor == p; | |
| } | |
| int secureValue() { | |
| return secureValue; | |
| } | |
| public ValueMapper mapper() { | |
| return mapper; | |
| } | |
| } | |
| /** | |
| * Supported processors | |
| */ | |
| public static enum Processor { | |
| ANY, | |
| PARSER, | |
| XPATH, | |
| } | |
| private static final int NO_LIMIT = 0; | |
| /** | |
| * Values of the properties | |
| */ | |
| private int[] values; | |
| /** | |
| * States of the settings for each property | |
| */ | |
| private State[] states; | |
| /** | |
| * Flag indicating if secure processing is set | |
| */ | |
| boolean secureProcessing; | |
| /** | |
| * Flag indicating the secure processing is set explicitly through factories' | |
| * setFeature method and then the setSecureProcessing method | |
| */ | |
| boolean secureProcessingSet; | |
| /** | |
| * States that determine if properties are set explicitly | |
| */ | |
| private boolean[] isSet; | |
| /** | |
| * Index of the special entityCountInfo property | |
| */ | |
| private final int indexEntityCountInfo = 10000; | |
| private String printEntityCountInfo = ""; | |
| /** | |
| * Default constructor. Establishes default values for known security | |
| * vulnerabilities. | |
| */ | |
| public XMLSecurityManager() { | |
| this(false); | |
| } | |
| /** | |
| * Instantiate Security Manager in accordance with the status of | |
| * secure processing | |
| * @param secureProcessing | |
| */ | |
| public XMLSecurityManager(boolean secureProcessing) { | |
| values = new int[Limit.values().length]; | |
| states = new State[Limit.values().length]; | |
| isSet = new boolean[Limit.values().length]; | |
| this.secureProcessing = secureProcessing; | |
| for (Limit limit : Limit.values()) { | |
| if (secureProcessing) { | |
| values[limit.ordinal()] = limit.secureValue; | |
| states[limit.ordinal()] = State.FSP; | |
| } else { | |
| values[limit.ordinal()] = limit.defaultValue(); | |
| states[limit.ordinal()] = State.DEFAULT; | |
| } | |
| } | |
| } | |
| /** | |
| * Returns a copy of the XMLSecurityManager. | |
| * @return a copy of the XMLSecurityManager | |
| */ | |
| public XMLSecurityManager clone() { | |
| try { | |
| XMLSecurityManager copy = (XMLSecurityManager) super.clone(); | |
| copy.values = this.values.clone(); | |
| copy.states = this.states.clone(); | |
| copy.isSet = this.isSet.clone(); | |
| return copy; | |
| } catch (CloneNotSupportedException e) { | |
| // shouldn't happen as this class is Cloneable | |
| throw new InternalError(e); | |
| } | |
| } | |
| /** | |
| * Returns a copy of the XMLSecurityManager that is updated with the | |
| * current System Properties. | |
| * @return a copy of the XMLSecurityManager | |
| */ | |
| public XMLSecurityManager cloneAndUpdate() { | |
| XMLSecurityManager copy = clone(); | |
| copy.readSystemProperties(); | |
| return copy; | |
| } | |
| /** | |
| * Returns the JDKCatalogResolver with the current setting of the RESOLVE | |
| * property. | |
| * | |
| * @return the JDKCatalogResolver | |
| */ | |
| public CatalogResolver getJDKCatalogResolver() { | |
| String resolve = getLimitValueAsString(Limit.JDKCATALOG_RESOLVE); | |
| return CatalogManager.catalogResolver( | |
| JdkXmlConfig.getInstance(false).getJdkCatalog(), toActionType(resolve)); | |
| } | |
| // convert the string value of the RESOLVE property to the corresponding | |
| // action type | |
| private NotFoundAction toActionType(String resolve) { | |
| for (NotFoundAction type : NotFoundAction.values()) { | |
| if (type.toString().equals(resolve)) { | |
| return type; | |
| } | |
| } | |
| return null; | |
| } | |
| /** | |
| * Setting FEATURE_SECURE_PROCESSING explicitly | |
| */ | |
| public void setSecureProcessing(boolean secure) { | |
| secureProcessingSet = true; | |
| secureProcessing = secure; | |
| for (Limit limit : Limit.values()) { | |
| if (secure) { | |
| setLimit(limit.ordinal(), State.FSP, limit.secureValue()); | |
| } else { | |
| setLimit(limit.ordinal(), State.FSP, limit.defaultValue()); | |
| } | |
| } | |
| } | |
| /** | |
| * Return the state of secure processing | |
| * @return the state of secure processing | |
| */ | |
| public boolean isSecureProcessing() { | |
| return secureProcessing; | |
| } | |
| /** | |
| * Returns the state indicating whether the Secure Processing is set explicitly, | |
| * via factories' setFeature and then this class' setSecureProcessing method. | |
| * @return the state indicating whether the Secure Processing is set explicitly | |
| */ | |
| public boolean isSecureProcessingSet() { | |
| return secureProcessingSet; | |
| } | |
| /** | |
| * Finds a limit's new name with the given property name. | |
| * @param propertyName the property name specified | |
| * @return the limit's new name if found, null otherwise | |
| */ | |
| public String find(String propertyName) { | |
| for (Limit limit : Limit.values()) { | |
| if (limit.is(propertyName)) { | |
| // current spec: new property name == systemProperty | |
| return (limit.systemProperty != null) ? limit.systemProperty : limit.apiProperty; | |
| } | |
| } | |
| //ENTITYCOUNT's new name is qName | |
| if (ImplPropMap.ENTITYCOUNT.is(propertyName)) { | |
| return ImplPropMap.ENTITYCOUNT.qName(); | |
| } | |
| return null; | |
| } | |
| /** | |
| * Set limit by property name and state | |
| * @param propertyName property name | |
| * @param state the state of the property | |
| * @param value the value of the property | |
| * @return true if the property is managed by the security manager; false | |
| * if otherwise. | |
| */ | |
| public boolean setLimit(String propertyName, State state, Object value) { | |
| // special property to return entity count info | |
| if (ImplPropMap.ENTITYCOUNT.is(propertyName)) { | |
| printEntityCountInfo = (String)value; | |
| return true; | |
| } | |
| Limit limit = getEnumValue(propertyName); | |
| if (limit != null) { | |
| State pState = state; | |
| if (state == State.APIPROPERTY) { | |
| // ordinal is the index of the value array | |
| pState = (Limit.values()[limit.ordinal()]).getState(propertyName); | |
| } | |
| setLimit(limit, pState, value); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Set the value for a specific limit. | |
| * | |
| * @param limit the limit | |
| * @param state the state of the property | |
| * @param value the value of the property | |
| */ | |
| public void setLimit(Limit limit, State state, int value) { | |
| setLimit(limit.ordinal(), state, value); | |
| } | |
| /** | |
| * Sets the value of a property by its enum name | |
| * | |
| * @param limit the limit | |
| * @param state the state of the property | |
| * @param value the value of the property | |
| */ | |
| public void setLimit(Limit limit, State state, Object value) { | |
| int intValue = limit.mapper().toInt(value); | |
| if (intValue < 0) { | |
| intValue = 0; | |
| } | |
| setLimit(limit.ordinal(), state, intValue); | |
| } | |
| /** | |
| * Set the value of a property by its index | |
| * | |
| * @param index the index of the property | |
| * @param state the state of the property | |
| * @param value the value of the property | |
| */ | |
| public void setLimit(int index, State state, int value) { | |
| if (index == indexEntityCountInfo) { | |
| //if it's explicitly set, it's treated as yes no matter the value | |
| printEntityCountInfo = JdkConstants.JDK_YES; | |
| } else { | |
| //only update if it shall override | |
| if (state.compareTo(states[index]) >= 0) { | |
| values[index] = value; | |
| states[index] = state; | |
| isSet[index] = true; | |
| } | |
| } | |
| } | |
| /** | |
| * Return the value of the specified property | |
| * | |
| * @param propertyName the property name | |
| * @return the value of the property as a string. If a property is managed | |
| * by this manager, its value shall not be null. | |
| */ | |
| public String getLimitAsString(String propertyName) { | |
| int index = getIndex(propertyName); | |
| if (index > -1) { | |
| return getLimitValueByIndex(index); | |
| } | |
| return null; | |
| } | |
| /** | |
| * Return the value of the specified property | |
| * | |
| * @param limit the property | |
| * @return the value of the property | |
| */ | |
| public int getLimit(Limit limit) { | |
| return values[limit.ordinal()]; | |
| } | |
| /** | |
| * Return the value of a property by its ordinal | |
| * | |
| * @param limit the property | |
| * @return value of a property | |
| */ | |
| public String getLimitValueAsString(Limit limit) { | |
| return limit.mapper().toString(values[limit.ordinal()]); | |
| } | |
| /** | |
| * Return the value of a property by its ordinal | |
| * | |
| * @param index the index of a property | |
| * @return limit of a property as a string | |
| */ | |
| public String getLimitValueByIndex(int index) { | |
| if (index == indexEntityCountInfo) { | |
| return printEntityCountInfo; | |
| } | |
| Limit limit = Limit.values()[index]; | |
| return limit.mapper().toString(values[index]); | |
| } | |
| /** | |
| * Return the state of the limit property | |
| * | |
| * @param limit the limit | |
| * @return the state of the limit property | |
| */ | |
| public State getState(Limit limit) { | |
| return states[limit.ordinal()]; | |
| } | |
| /** | |
| * Return the state of the limit property | |
| * | |
| * @param limit the limit | |
| * @return the state of the limit property | |
| */ | |
| public String getStateLiteral(Limit limit) { | |
| return states[limit.ordinal()].literal(); | |
| } | |
| /** | |
| * Returns the enum value by its property name. | |
| * | |
| * @param propertyName property name | |
| * @return the enum value if found; null otherwise | |
| */ | |
| public Limit getEnumValue(String propertyName) { | |
| for (Limit limit : Limit.values()) { | |
| if (limit.is(propertyName)) { | |
| return limit; | |
| } | |
| } | |
| return null; | |
| } | |
| /** | |
| * Get the index by property name | |
| * | |
| * @param propertyName property name | |
| * @return the index of the property if found; return -1 if not | |
| */ | |
| public int getIndex(String propertyName) { | |
| for (Limit limit : Limit.values()) { | |
| // see JDK-8265248, accept both the URL and jdk.xml as prefix | |
| if (limit.is(propertyName)) { | |
| //internally, ordinal is used as index | |
| return limit.ordinal(); | |
| } | |
| } | |
| //special property to return entity count info | |
| if (ImplPropMap.ENTITYCOUNT.is(propertyName)) { | |
| return indexEntityCountInfo; | |
| } | |
| return -1; | |
| } | |
| /** | |
| * Check if there's no limit defined by the Security Manager | |
| * @param limit | |
| * @return | |
| */ | |
| public boolean isNoLimit(int limit) { | |
| return limit == NO_LIMIT; | |
| } | |
| /** | |
| * Check if the size (length or count) of the specified limit property is | |
| * over the limit | |
| * | |
| * @param limit the type of the limit property | |
| * @param entityName the name of the entity | |
| * @param size the size (count or length) of the entity | |
| * @return true if the size is over the limit, false otherwise | |
| */ | |
| public boolean isOverLimit(Limit limit, String entityName, int size, | |
| XMLLimitAnalyzer limitAnalyzer) { | |
| return isOverLimit(limit.ordinal(), entityName, size, limitAnalyzer); | |
| } | |
| /** | |
| * Check if the value (length or count) of the specified limit property is | |
| * over the limit | |
| * | |
| * @param index the index of the limit property | |
| * @param entityName the name of the entity | |
| * @param size the size (count or length) of the entity | |
| * @return true if the size is over the limit, false otherwise | |
| */ | |
| public boolean isOverLimit(int index, String entityName, int size, | |
| XMLLimitAnalyzer limitAnalyzer) { | |
| if (values[index] == NO_LIMIT) { | |
| return false; | |
| } | |
| if (size > values[index]) { | |
| limitAnalyzer.addValue(index, entityName, size); | |
| return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Check against cumulated value | |
| * | |
| * @param limit the type of the limit property | |
| * @param size the size (count or length) of the entity | |
| * @return true if the size is over the limit, false otherwise | |
| */ | |
| public boolean isOverLimit(Limit limit, XMLLimitAnalyzer limitAnalyzer) { | |
| return isOverLimit(limit.ordinal(), limitAnalyzer); | |
| } | |
| public boolean isOverLimit(int index, XMLLimitAnalyzer limitAnalyzer) { | |
| if (values[index] == NO_LIMIT) { | |
| return false; | |
| } | |
| if (index == Limit.ELEMENT_ATTRIBUTE_LIMIT.ordinal() || | |
| index == Limit.ENTITY_EXPANSION_LIMIT.ordinal() || | |
| index == Limit.TOTAL_ENTITY_SIZE_LIMIT.ordinal() || | |
| index == Limit.ENTITY_REPLACEMENT_LIMIT.ordinal() || | |
| index == Limit.MAX_ELEMENT_DEPTH_LIMIT.ordinal() || | |
| index == Limit.MAX_NAME_LIMIT.ordinal() | |
| ) { | |
| return (limitAnalyzer.getTotalValue(index) > values[index]); | |
| } else { | |
| return (limitAnalyzer.getValue(index) > values[index]); | |
| } | |
| } | |
| public void debugPrint(XMLLimitAnalyzer limitAnalyzer) { | |
| if (printEntityCountInfo.equals(JdkConstants.JDK_YES)) { | |
| limitAnalyzer.debugPrint(this); | |
| } | |
| } | |
| /** | |
| * Indicate if a property is set explicitly | |
| * @param limit the limit | |
| * @return true if the limit is set, false otherwise | |
| */ | |
| public boolean isSet(Limit limit) { | |
| return isSet[limit.ordinal()]; | |
| } | |
| /** | |
| * Checks whether the specified {@link Limit} is set and the value is | |
| * as specified. | |
| * | |
| * @param limit the {@link Limit} | |
| * @param value the value | |
| * @return true if the {@code Limit} is set and the values match | |
| */ | |
| public boolean is(Limit limit, int value) { | |
| return getLimit(limit) == value; | |
| } | |
| /** | |
| * Checks whether the specified {@link Limit} is set and the value is | |
| * 1 (true for a property of boolean type). | |
| * | |
| * @param limit the {@link Limit} | |
| * | |
| * @return true if the {@code Limit} is set and the value is 1 | |
| */ | |
| public boolean is(Limit limit) { | |
| return getLimit(limit) == 1; | |
| } | |
| public boolean printEntityCountInfo() { | |
| return printEntityCountInfo.equals(JdkConstants.JDK_YES); | |
| } | |
| /** | |
| * Read system properties, or the configuration file | |
| */ | |
| public void readSystemProperties() { | |
| for (Limit limit : Limit.values()) { | |
| if (State.SYSTEMPROPERTY.compareTo(states[limit.ordinal()]) >= 0 && | |
| limit.systemProperty() != null) { | |
| // attempts to read both the current and old system propery | |
| if (!getSystemProperty(limit, limit.systemProperty()) | |
| && (!getSystemProperty(limit, limit.spOld()))) { | |
| //if system property is not found, try the config file | |
| if (State.JAXPDOTPROPERTIES.compareTo(states[limit.ordinal()]) >= 0) { | |
| getPropertyConfig(limit, limit.systemProperty()); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Array list to store printed warnings for each SAX parser used | |
| private static final CopyOnWriteArrayList<String> printedWarnings = new CopyOnWriteArrayList<>(); | |
| /** | |
| * Prints out warnings if a parser does not support the specified feature/property. | |
| * | |
| * @param parserClassName the name of the parser class | |
| * @param propertyName the property name | |
| * @param exception the exception thrown by the parser | |
| */ | |
| public static void printWarning(String parserClassName, String propertyName, SAXException exception) { | |
| String key = parserClassName+":"+propertyName; | |
| if (printedWarnings.addIfAbsent(key)) { | |
| System.err.println( "Warning: "+parserClassName+": "+exception.getMessage()); | |
| } | |
| } | |
| /** | |
| * Reads a system property, sets value and state if found. | |
| * | |
| * @param limit the limit property | |
| * @param sysPropertyName the name of system property | |
| */ | |
| private boolean getSystemProperty(Limit limit, String sysPropertyName) { | |
| if (sysPropertyName == null) return false; | |
| try { | |
| String value = System.getProperty(sysPropertyName); | |
| if (value != null && !value.equals("")) { | |
| setLimit(limit, State.SYSTEMPROPERTY, value); | |
| return true; | |
| } | |
| } catch (NumberFormatException e) { | |
| //invalid setting | |
| throw new NumberFormatException("Invalid setting for system property: " + limit.systemProperty()); | |
| } | |
| return false; | |
| } | |
| /** | |
| * Reads a property from a configuration file, if any. | |
| * | |
| * @param limit the limit property | |
| * @param sysPropertyName the name of system property | |
| * @return | |
| */ | |
| private boolean getPropertyConfig(Limit limit, String sysPropertyName) { | |
| try { | |
| String value = SecuritySupport.readConfig(sysPropertyName); | |
| if (value != null && !value.equals("")) { | |
| setLimit(limit, State.JAXPDOTPROPERTIES, value); | |
| return true; | |
| } | |
| } catch (NumberFormatException e) { | |
| //invalid setting | |
| throw new NumberFormatException("Invalid setting for system property: " + limit.systemProperty()); | |
| } | |
| return false; | |
| } | |
| /** | |
| * Convert a value set through setProperty to XMLSecurityManager. | |
| * If the value is an instance of XMLSecurityManager, use it to override the default; | |
| * If the value is an old SecurityManager, convert to the new XMLSecurityManager. | |
| * | |
| * @param value user specified security manager | |
| * @param securityManager an instance of XMLSecurityManager | |
| * @return an instance of the new security manager XMLSecurityManager | |
| */ | |
| public static XMLSecurityManager convert(Object value, XMLSecurityManager securityManager) { | |
| if (value == null) { | |
| if (securityManager == null) { | |
| securityManager = new XMLSecurityManager(true); | |
| } | |
| return securityManager; | |
| } | |
| if (value instanceof XMLSecurityManager) { | |
| return (XMLSecurityManager)value; | |
| } else { | |
| if (securityManager == null) { | |
| securityManager = new XMLSecurityManager(true); | |
| } | |
| if (value instanceof SecurityManager) { | |
| SecurityManager origSM = (SecurityManager)value; | |
| securityManager.setLimit(Limit.MAX_OCCUR_NODE_LIMIT, State.APIPROPERTY, origSM.getMaxOccurNodeLimit()); | |
| securityManager.setLimit(Limit.ENTITY_EXPANSION_LIMIT, State.APIPROPERTY, origSM.getEntityExpansionLimit()); | |
| securityManager.setLimit(Limit.ELEMENT_ATTRIBUTE_LIMIT, State.APIPROPERTY, origSM.getElementAttrLimit()); | |
| } | |
| return securityManager; | |
| } | |
| } | |
| /** | |
| * Represents a mapper for properties of type String. The input is expected | |
| * to be a String or Object. If there is a map, the mappings are between the | |
| * keys and values within the map. | |
| */ | |
| public static class StringMapper extends ValueMapper { | |
| private final Map<String, Integer> map; | |
| private final Map<Integer, String> reverseMap; | |
| public StringMapper(Map<String, Integer> map) { | |
| this.map = map; | |
| if (map != null) { | |
| reverseMap = map.entrySet().stream() | |
| .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); | |
| } else { | |
| reverseMap = null; | |
| } | |
| } | |
| /** | |
| * Finds the mapping int value with the specified property value. This | |
| * method will try to convert the provided value to an integer if no | |
| * mapping is found. | |
| * @param value the property value | |
| * @return the mapping int value if found, null otherwise | |
| */ | |
| @Override | |
| public int toInt(Object value) { | |
| Objects.requireNonNull(value); | |
| Integer iVal; | |
| if (map != null) { | |
| iVal = map.get(((String)value).toLowerCase()); | |
| iVal = (iVal == null) ? 0 : iVal; | |
| } else { | |
| try { | |
| iVal = (int)Double.parseDouble((String)value); | |
| } catch (NumberFormatException e) { | |
| // Note: this is the currently expected behavior. It may be | |
| // appropriate for the setter to catch it. | |
| throw new NumberFormatException("Invalid setting " + value | |
| + " for a property of Integer type."); | |
| } | |
| } | |
| return iVal; | |
| } | |
| @Override | |
| public String toObject(int value) { | |
| if (reverseMap != null) { | |
| return reverseMap.get(value); | |
| } | |
| return Integer.toString(value); | |
| } | |
| @Override | |
| public String toString(int value) { | |
| return toObject(value); | |
| } | |
| } | |
| /** | |
| * Represents a mapper for properties of type Integer. The input is expected | |
| * to be either an Integer or String. | |
| */ | |
| public static class IntegerMapper extends ValueMapper { | |
| @Override | |
| public int toInt(Object value) { | |
| Objects.requireNonNull(value); | |
| Integer iVal; | |
| if (value instanceof Integer) { | |
| iVal = (Integer)value; | |
| } else { | |
| try { | |
| iVal = Integer.parseInt((String)value); | |
| } catch (NumberFormatException e) { | |
| // Note: this is the currently expected behavior. It may be | |
| // appropriate for the setter to catch it. | |
| throw new NumberFormatException("Invalid setting " + value | |
| + " for a property of Integer type."); | |
| } | |
| } | |
| return iVal; | |
| } | |
| @Override | |
| public Integer toObject(int value) { | |
| return value; | |
| } | |
| @Override | |
| public String toString(int value) { | |
| return Integer.toString(value); | |
| } | |
| } | |
| /** | |
| * Represents a mapper for properties of type Boolean. The input is expected | |
| * to be either a Boolean or String. | |
| */ | |
| public static class BooleanMapper extends ValueMapper { | |
| @Override | |
| public int toInt(Object value) { | |
| Objects.requireNonNull(value); | |
| Boolean bVal; | |
| if (value instanceof Boolean) { | |
| bVal = (Boolean)value; | |
| } else { | |
| bVal = ((String)value).equalsIgnoreCase("true"); | |
| } | |
| return bVal ? 1 : 0; | |
| } | |
| @Override | |
| public Boolean toObject(int value) { | |
| return value != 0; | |
| } | |
| @Override | |
| public String toString(int value) { | |
| return Boolean.toString(value != 0); | |
| } | |
| } | |
| /** | |
| * Represents a mapper of property values between int and other types, such as | |
| * Boolean, String, and Object. | |
| */ | |
| public static abstract class ValueMapper { | |
| // converts to an int value from that of the specified type | |
| public abstract int toInt(Object value); | |
| // converts the int value back to the original type | |
| public abstract Object toObject(int value); | |
| // converts the int value of a property to a String representation | |
| public abstract String toString(int value); | |
| } | |
| /** | |
| * Represents a mapper of property values between int and other types, such as | |
| * Boolean, String, and Object. | |
| * | |
| * @param <T> the value type to be mapped with an int value | |
| */ | |
| public abstract class ValueMapper1<T> { | |
| // converts to an int value from that of the specified type | |
| public abstract int toInt(T value); | |
| // converts the int value back to the original type | |
| public abstract T toObject(int value); | |
| // converts the int value of a property to a String representation | |
| public abstract String toString(int value); | |
| } | |
| } |