Create a new XML parser configuration with enhancements for the JAXP secure processing feature.

This new configuration supports the following properties that were defined for the JDK:

jdk.xml.entityExpansionLimit
jdk.xml.maxOccur
jdk.xml.totalEntitySizeLimit
jdk.xml.maxGeneralEntitySizeLimit
jdk.xml.maxParameterEntitySizeLimit

For now support will be limited to system properties and jaxp.properties.

git-svn-id: https://svn.apache.org/repos/asf/xerces/java/trunk@1556006 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/org/apache/xerces/impl/msg/XMLMessages.properties b/src/org/apache/xerces/impl/msg/XMLMessages.properties
index 02b7fb3..c0f1afb 100644
--- a/src/org/apache/xerces/impl/msg/XMLMessages.properties
+++ b/src/org/apache/xerces/impl/msg/XMLMessages.properties
@@ -307,3 +307,7 @@
 #Application can set the limit of number of entities that should be expanded by the parser.
 EntityExpansionLimitExceeded=The parser has encountered more than \"{0}\" entity expansions in this document; this is the limit imposed by the application.
 
+#Application can set limits on the size of entities that should be processed by the parser.
+TotalEntitySizeLimitExceeded=The parser has encountered more than \"{0}\" bytes or characters within entities declared and referenced by this document; this is the limit imposed by the application.
+MaxGeneralEntitySizeLimitExceeded=The parser has encountered more than \"{0}\" bytes or characters within a general entity; this is the limit imposed by the application.
+MaxParameterEntitySizeLimitExceeded=The parser has encountered more than \"{0}\" bytes or characters within a parameter entity; this is the limit imposed by the application.
diff --git a/src/org/apache/xerces/parsers/SecureProcessingConfiguration.java b/src/org/apache/xerces/parsers/SecureProcessingConfiguration.java
new file mode 100644
index 0000000..0ac4537
--- /dev/null
+++ b/src/org/apache/xerces/parsers/SecureProcessingConfiguration.java
@@ -0,0 +1,748 @@
+/*
+ * 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
+ * 
+ *      http://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.xerces.parsers;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FilterInputStream;
+import java.io.FilterReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.URL;
+import java.util.Properties;
+
+import org.apache.xerces.impl.Constants;
+import org.apache.xerces.impl.XMLEntityDescription;
+import org.apache.xerces.impl.XMLErrorReporter;
+import org.apache.xerces.impl.msg.XMLMessageFormatter;
+import org.apache.xerces.util.SecurityManager;
+import org.apache.xerces.util.SymbolTable;
+import org.apache.xerces.xni.Augmentations;
+import org.apache.xerces.xni.XMLDTDHandler;
+import org.apache.xerces.xni.XMLLocator;
+import org.apache.xerces.xni.XMLResourceIdentifier;
+import org.apache.xerces.xni.XMLString;
+import org.apache.xerces.xni.XNIException;
+import org.apache.xerces.xni.grammars.XMLGrammarPool;
+import org.apache.xerces.xni.parser.XMLComponentManager;
+import org.apache.xerces.xni.parser.XMLConfigurationException;
+import org.apache.xerces.xni.parser.XMLDTDFilter;
+import org.apache.xerces.xni.parser.XMLDTDSource;
+import org.apache.xerces.xni.parser.XMLEntityResolver;
+import org.apache.xerces.xni.parser.XMLInputSource;
+
+/**
+ * This configuration enhances Xerces support for the JAXP secure processing feature.
+ * 
+ * @author Michael Glavassevich, IBM
+ */
+public final class SecureProcessingConfiguration extends
+        XIncludeAwareParserConfiguration {
+    
+    //
+    // Constants
+    //
+    
+    /** Property identifier: security manager. */
+    private static final String SECURITY_MANAGER_PROPERTY =
+            Constants.XERCES_PROPERTY_PREFIX + Constants.SECURITY_MANAGER_PROPERTY;
+    
+    /** Property identifier: entity resolver. */
+    private static final String ENTITY_RESOLVER_PROPERTY = 
+        Constants.XERCES_PROPERTY_PREFIX + Constants.ENTITY_RESOLVER_PROPERTY;
+    
+    /** Set to true for debugging */
+    private static final boolean DEBUG = isDebugEnabled();
+    
+    /** Cache the contents of the jaxp.properties file, if used. */
+    private static Properties jaxpProperties = null;
+
+    /** Cache the timestamp of the jaxp.properties file, if used. */
+    private static long lastModified = -1;
+    
+    /** Xerces SecurityManager default value for entity expansion limit. **/
+    private static final int SECURITY_MANAGER_DEFAULT_ENTITY_EXPANSION_LIMIT = 100000;
+    
+    /** Xerces SecurityManager default value of number of nodes created. **/
+    private static final int SECURITY_MANAGER_DEFAULT_MAX_OCCUR_NODE_LIMIT = 3000;
+    
+    private static final String ENTITY_EXPANSION_LIMIT_PROPERTY_NAME = "jdk.xml.entityExpansionLimit";
+    private static final String MAX_OCCUR_LIMIT_PROPERTY_NAME = "jdk.xml.maxOccur";
+    private static final String TOTAL_ENTITY_SIZE_LIMIT_PROPERTY_NAME = "jdk.xml.totalEntitySizeLimit";
+    private static final String MAX_GENERAL_ENTITY_SIZE_LIMIT_PROPERTY_NAME = "jdk.xml.maxGeneralEntitySizeLimit";
+    private static final String MAX_PARAMETER_ENTITY_SIZE_LIMIT_PROPERTY_NAME = "jdk.xml.maxParameterEntitySizeLimit";
+    
+    private static final int ENTITY_EXPANSION_LIMIT_DEFAULT_VALUE = 64000;
+    private static final int MAX_OCCUR_LIMIT_DEFAULT_VALUE = 5000;
+    private static final int TOTAL_ENTITY_SIZE_LIMIT_DEFAULT_VALUE = 50000000;
+    private static final int MAX_GENERAL_ENTITY_SIZE_LIMIT_DEFAULT_VALUE = Integer.MAX_VALUE;
+    private static final int MAX_PARAMETER_ENTITY_SIZE_LIMIT_DEFAULT_VALUE = Integer.MAX_VALUE;
+    
+    protected final int ENTITY_EXPANSION_LIMIT_SYSTEM_VALUE;
+    protected final int MAX_OCCUR_LIMIT_SYSTEM_VALUE;
+    protected final int TOTAL_ENTITY_SIZE_LIMIT_SYSTEM_VALUE;
+    protected final int MAX_GENERAL_ENTITY_SIZE_LIMIT_SYSTEM_VALUE;
+    protected final int MAX_PARAMETER_ENTITY_SIZE_LIMIT_SYSTEM_VALUE;
+    
+    //
+    // Fields
+    //
+    
+    private final boolean fJavaSecurityManagerEnabled;
+    private boolean fLimitSpecified;
+    private SecurityManager fSecurityManager;
+    private InternalEntityMonitor fInternalEntityMonitor;
+    private final ExternalEntityMonitor fExternalEntityMonitor;
+    private int fTotalEntitySize = 0;
+    
+    /** Default constructor. */
+    public SecureProcessingConfiguration() {
+        this(null, null, null);
+    } // <init>()
+    
+    /** 
+     * Constructs a parser configuration using the specified symbol table. 
+     *
+     * @param symbolTable The symbol table to use.
+     */
+    public SecureProcessingConfiguration(SymbolTable symbolTable) {
+        this(symbolTable, null, null);
+    } // <init>(SymbolTable)
+    
+    /**
+     * Constructs a parser configuration using the specified symbol table and
+     * grammar pool.
+     * <p>
+     *
+     * @param symbolTable The symbol table to use.
+     * @param grammarPool The grammar pool to use.
+     */
+    public SecureProcessingConfiguration(
+            SymbolTable symbolTable,
+            XMLGrammarPool grammarPool) {
+        this(symbolTable, grammarPool, null); 
+    } // <init>(SymbolTable,XMLGrammarPool)
+    
+    /**
+     * Constructs a parser configuration using the specified symbol table,
+     * grammar pool, and parent settings.
+     * <p>
+     *
+     * @param symbolTable    The symbol table to use.
+     * @param grammarPool    The grammar pool to use.
+     * @param parentSettings The parent settings.
+     */
+    public SecureProcessingConfiguration(
+            SymbolTable symbolTable,
+            XMLGrammarPool grammarPool,
+            XMLComponentManager parentSettings) {
+        super(symbolTable, grammarPool, parentSettings);
+        fJavaSecurityManagerEnabled = (System.getSecurityManager() != null);
+        ENTITY_EXPANSION_LIMIT_SYSTEM_VALUE = getPropertyValue(ENTITY_EXPANSION_LIMIT_PROPERTY_NAME, ENTITY_EXPANSION_LIMIT_DEFAULT_VALUE);
+        MAX_OCCUR_LIMIT_SYSTEM_VALUE = getPropertyValue(MAX_OCCUR_LIMIT_PROPERTY_NAME, MAX_OCCUR_LIMIT_DEFAULT_VALUE);
+        TOTAL_ENTITY_SIZE_LIMIT_SYSTEM_VALUE = getPropertyValue(TOTAL_ENTITY_SIZE_LIMIT_PROPERTY_NAME, TOTAL_ENTITY_SIZE_LIMIT_DEFAULT_VALUE);
+        MAX_GENERAL_ENTITY_SIZE_LIMIT_SYSTEM_VALUE = getPropertyValue(MAX_GENERAL_ENTITY_SIZE_LIMIT_PROPERTY_NAME, MAX_GENERAL_ENTITY_SIZE_LIMIT_DEFAULT_VALUE);
+        MAX_PARAMETER_ENTITY_SIZE_LIMIT_SYSTEM_VALUE = getPropertyValue(MAX_PARAMETER_ENTITY_SIZE_LIMIT_PROPERTY_NAME, MAX_PARAMETER_ENTITY_SIZE_LIMIT_DEFAULT_VALUE);
+        if (fJavaSecurityManagerEnabled || fLimitSpecified) {
+            fSecurityManager = new org.apache.xerces.util.SecurityManager();
+            fSecurityManager.setEntityExpansionLimit(ENTITY_EXPANSION_LIMIT_SYSTEM_VALUE);
+            fSecurityManager.setMaxOccurNodeLimit(MAX_OCCUR_LIMIT_SYSTEM_VALUE);
+            super.setProperty(SECURITY_MANAGER_PROPERTY, fSecurityManager);
+        }
+        fExternalEntityMonitor = new ExternalEntityMonitor();
+        super.setProperty(ENTITY_RESOLVER_PROPERTY, fExternalEntityMonitor);
+    }
+    
+    protected void checkEntitySizeLimits(int sizeOfEntity, int delta, boolean isPE) {
+        fTotalEntitySize += delta;
+        if (fTotalEntitySize > TOTAL_ENTITY_SIZE_LIMIT_SYSTEM_VALUE) {
+            fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
+                    "TotalEntitySizeLimitExceeded",
+                    new Object[] {new Integer(TOTAL_ENTITY_SIZE_LIMIT_SYSTEM_VALUE)},
+                    XMLErrorReporter.SEVERITY_FATAL_ERROR);
+        }
+        if (isPE) {
+            if (sizeOfEntity > MAX_PARAMETER_ENTITY_SIZE_LIMIT_SYSTEM_VALUE) {
+                fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
+                        "MaxParameterEntitySizeLimitExceeded",
+                        new Object[] {new Integer(MAX_PARAMETER_ENTITY_SIZE_LIMIT_SYSTEM_VALUE)},
+                        XMLErrorReporter.SEVERITY_FATAL_ERROR);
+            }
+        }
+        else if (sizeOfEntity > MAX_GENERAL_ENTITY_SIZE_LIMIT_SYSTEM_VALUE) {
+            fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
+                    "MaxGeneralEntitySizeLimitExceeded",
+                    new Object[] {new Integer(MAX_GENERAL_ENTITY_SIZE_LIMIT_SYSTEM_VALUE)},
+                    XMLErrorReporter.SEVERITY_FATAL_ERROR);
+        }
+    }
+    
+    /**
+     * Returns the value of a property.
+     * 
+     * @param propertyId The property identifier.
+     * @return the value of the property
+     * 
+     * @throws XMLConfigurationException Thrown for configuration error.
+     *                                   In general, components should
+     *                                   only throw this exception if
+     *                                   it is <strong>really</strong>
+     *                                   a critical error.
+     */
+    public Object getProperty(String propertyId)
+        throws XMLConfigurationException {
+        if (SECURITY_MANAGER_PROPERTY.equals(propertyId)) {
+            return fSecurityManager;
+        }
+        else if (ENTITY_RESOLVER_PROPERTY.equals(propertyId)) {
+            return fExternalEntityMonitor;
+        }
+        return super.getProperty(propertyId);
+    }
+    
+    /**
+     * setProperty
+     * 
+     * @param propertyId 
+     * @param value 
+     */
+    public void setProperty(String propertyId, Object value)
+        throws XMLConfigurationException {
+        if (SECURITY_MANAGER_PROPERTY.equals(propertyId)) {
+            // Do not allow the Xerces SecurityManager to be 
+            // removed if the Java SecurityManager has been installed.
+            if (value == null && fJavaSecurityManagerEnabled) {
+                return;
+            }
+            fSecurityManager = (SecurityManager) value;
+            if (fSecurityManager != null) {
+                // Override SecurityManager default values with the system property / jaxp.properties / config default determined values.
+                if (fSecurityManager.getEntityExpansionLimit() == SECURITY_MANAGER_DEFAULT_ENTITY_EXPANSION_LIMIT) {
+                    fSecurityManager.setEntityExpansionLimit(ENTITY_EXPANSION_LIMIT_SYSTEM_VALUE);
+                }
+                if (fSecurityManager.getMaxOccurNodeLimit() == SECURITY_MANAGER_DEFAULT_MAX_OCCUR_NODE_LIMIT) {
+                    fSecurityManager.setMaxOccurNodeLimit(MAX_OCCUR_LIMIT_SYSTEM_VALUE);
+                }
+            }  
+        }
+        else if (ENTITY_RESOLVER_PROPERTY.equals(propertyId)) {
+            fExternalEntityMonitor.setEntityResolver((XMLEntityResolver) value);
+            return;
+        }
+        super.setProperty(propertyId, value);
+    }
+    
+    /** Configures the XML 1.0 pipeline. */
+    protected void configurePipeline() {
+        super.configurePipeline();
+        configurePipelineCommon();
+    }
+    
+    /** Configures the XML 1.1 pipeline. */
+    protected void configureXML11Pipeline() {
+        super.configureXML11Pipeline();
+        configurePipelineCommon();
+    }
+    
+    private void configurePipelineCommon() {
+        if (fSecurityManager != null) {
+            fTotalEntitySize = 0;
+            if (fInternalEntityMonitor == null) {
+                fInternalEntityMonitor = new InternalEntityMonitor();
+            }
+            // Reconfigure DTD pipeline. Insert internal entity decl monitor.
+            fDTDScanner.setDTDHandler(fInternalEntityMonitor);
+            fInternalEntityMonitor.setDTDSource(fDTDScanner);
+            fInternalEntityMonitor.setDTDHandler(fDTDProcessor);
+            fDTDProcessor.setDTDSource(fInternalEntityMonitor);
+        }
+    }
+    
+    private int getPropertyValue(String propertyName, int defaultValue) {
+        
+        // Step #1: Use the system property first
+        try {
+            String propertyValue = SecuritySupport.getSystemProperty(propertyName);
+            if (propertyValue != null && propertyValue.length() > 0) {
+                if (DEBUG) {
+                    debugPrintln("found system property \"" + propertyName + "\", value=" + propertyValue);
+                }
+                final int intValue = Integer.parseInt(propertyValue);
+                fLimitSpecified = true;
+                if (intValue > 0) {
+                    return intValue;
+                }
+                // Treat 0 and negative numbers as no limit (i.e. max integer).
+                return Integer.MAX_VALUE;
+            }
+        }
+        // The VM ran out of memory or there was some other serious problem. Re-throw.
+        catch (VirtualMachineError vme) {
+            throw vme;
+        }
+        // ThreadDeath should always be re-thrown
+        catch (ThreadDeath td) {
+            throw td;
+        }
+        catch (Throwable e) {
+            // Ignore all other exceptions/errors and continue w/ next location
+            if (DEBUG) {
+                debugPrintln(e.getClass().getName() + ": " + e.getMessage());
+                e.printStackTrace();
+            }
+        }
+        
+        // Step #2: Use $java.home/lib/jaxp.properties
+        try {
+            boolean fExists = false;
+            File f = null;
+            try {               
+                String javah = SecuritySupport.getSystemProperty("java.home");
+                String configFile = javah + File.separator +
+                        "lib" + File.separator + "jaxp.properties";
+
+                f = new File(configFile);
+                fExists = SecuritySupport.getFileExists(f);
+
+            }
+            catch (SecurityException se) {
+                // If there is a security exception, move on to next location.
+                lastModified = -1;
+                jaxpProperties = null;            
+            }
+
+            synchronized (SecureProcessingConfiguration.class) {    
+
+                boolean runBlock = false;
+                FileInputStream fis = null;
+
+                try {
+                    if (lastModified >= 0) {
+                        // File has been modified, or didn't previously exist. 
+                        // Need to reload properties    
+                        if ((fExists) &&
+                            (lastModified < (lastModified = SecuritySupport.getLastModified(f)))) {  
+                            runBlock = true;
+                        } 
+                        else {
+                            if (!fExists) {
+                                // file existed, but it's been deleted.
+                                lastModified = -1;
+                                jaxpProperties = null;
+                            }
+                        }        
+                    } 
+                    else {
+                        if (fExists) { 
+                            // File didn't exist, but it does now.
+                            runBlock = true;
+                            lastModified = SecuritySupport.getLastModified(f);
+                        }    
+                    }
+
+                    if (runBlock == true) {
+                        // Try to read from $java.home/lib/jaxp.properties
+                        jaxpProperties = new Properties();
+
+                        fis = SecuritySupport.getFileInputStream(f);
+                        jaxpProperties.load(fis);
+                    }       
+
+                }
+                catch (Exception x) {
+                    lastModified = -1;
+                    jaxpProperties = null;
+                    // assert(x instanceof FileNotFoundException
+                    //        || x instanceof SecurityException)
+                    // In both cases, ignore and return the default value
+                }
+                finally {
+                    // try to close the input stream if one was opened.
+                    if (fis != null) {
+                        try {
+                            fis.close();
+                        }
+                        // Ignore the exception.
+                        catch (IOException exc) {}
+                    }
+                }
+            }
+
+            if (jaxpProperties != null) {            
+                String propertyValue = jaxpProperties.getProperty(propertyName);
+                if (propertyValue != null && propertyValue.length() > 0) {
+                    if (DEBUG) {
+                        debugPrintln("found \"" + propertyName + "\" in jaxp.properties, value=" + propertyValue);
+                    }
+                    final int intValue = Integer.parseInt(propertyValue);
+                    fLimitSpecified = true;
+                    if (intValue > 0) {
+                        return intValue;
+                    }
+                    // Treat 0 and negative numbers as no limit (i.e. max integer).
+                    return Integer.MAX_VALUE;
+                }
+            }
+        }
+        // The VM ran out of memory or there was some other serious problem. Re-throw.
+        catch (VirtualMachineError vme) {
+            throw vme;
+        }
+        // ThreadDeath should always be re-thrown
+        catch (ThreadDeath td) {
+            throw td;
+        }
+        catch (Throwable e) {
+            // Ignore all other exceptions/errors and return the default value.
+            if (DEBUG) {
+                debugPrintln(e.getClass().getName() + ": " + e.getMessage());
+                e.printStackTrace();
+            }
+        }
+        
+        // Step #3: Return the default value.
+        return defaultValue;
+    }
+    
+    //
+    // Private static methods
+    //
+    
+    /** Returns true if debug has been enabled. */
+    private static boolean isDebugEnabled() {
+        try {
+            String val = SecuritySupport.getSystemProperty("xerces.debug");
+            // Allow simply setting the prop to turn on debug
+            return (val != null && (!"false".equals(val)));
+        } 
+        catch (SecurityException se) {}
+        return false;
+    } // isDebugEnabled()
+
+    /** Prints a message to standard error if debugging is enabled. */
+    private static void debugPrintln(String msg) {
+        if (DEBUG) {
+            System.err.println("XERCES: " + msg);
+        }
+    } // debugPrintln(String)
+    
+    /**
+     * XMLDTDFilter which checks limits imposed by the application 
+     * on the sizes of general and parameter entities.
+     */
+    final class InternalEntityMonitor implements XMLDTDFilter {
+        
+        /** DTD source and handler. **/
+        private XMLDTDSource fDTDSource;
+        private XMLDTDHandler fDTDHandler;
+        
+        public InternalEntityMonitor() {}
+
+        /*
+         * XMLDTDHandler methods
+         */
+
+        public void startDTD(XMLLocator locator, Augmentations augmentations)
+                throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.startDTD(locator, augmentations);
+            }
+        }
+
+        public void startParameterEntity(String name,
+                XMLResourceIdentifier identifier, String encoding,
+                Augmentations augmentations) throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.startParameterEntity(name, identifier, encoding, augmentations);
+            }
+        }
+
+        public void textDecl(String version, String encoding,
+                Augmentations augmentations) throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.textDecl(version, encoding, augmentations);
+            }
+        }
+
+        public void endParameterEntity(String name, Augmentations augmentations)
+                throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.endParameterEntity(name, augmentations);
+            }
+        }
+
+        public void startExternalSubset(XMLResourceIdentifier identifier,
+                Augmentations augmentations) throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.startExternalSubset(identifier, augmentations);
+            }
+        }
+
+        public void endExternalSubset(Augmentations augmentations)
+                throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.endExternalSubset(augmentations);
+            }
+        }
+
+        public void comment(XMLString text, Augmentations augmentations)
+                throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.comment(text, augmentations);
+            }
+        }
+
+        public void processingInstruction(String target, XMLString data,
+                Augmentations augmentations) throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.processingInstruction(target, data, augmentations);
+            }
+        }
+
+        public void elementDecl(String name, String contentModel,
+                Augmentations augmentations) throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.elementDecl(name, contentModel, augmentations);
+            }
+        }
+
+        public void startAttlist(String elementName, Augmentations augmentations)
+                throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.startAttlist(elementName, augmentations);
+            }
+        }
+
+        public void attributeDecl(String elementName, String attributeName,
+                String type, String[] enumeration, String defaultType,
+                XMLString defaultValue, XMLString nonNormalizedDefaultValue,
+                Augmentations augmentations) throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.attributeDecl(elementName, attributeName,
+                        type, enumeration, defaultType,
+                        defaultValue, nonNormalizedDefaultValue,
+                        augmentations);
+            }
+        }
+
+        public void endAttlist(Augmentations augmentations) throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.endAttlist(augmentations);
+            }
+        }
+
+        public void internalEntityDecl(String name, XMLString text,
+                XMLString nonNormalizedText, Augmentations augmentations)
+                throws XNIException {
+            checkEntitySizeLimits(text.length, text.length, name != null && name.startsWith("%"));
+            if (fDTDHandler != null) {
+                fDTDHandler.internalEntityDecl(name, text,
+                        nonNormalizedText, augmentations);
+            }
+        }
+
+        public void externalEntityDecl(String name,
+                XMLResourceIdentifier identifier, Augmentations augmentations)
+                throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.externalEntityDecl(name, identifier, augmentations);
+            }
+        }
+
+        public void unparsedEntityDecl(String name,
+                XMLResourceIdentifier identifier, String notation,
+                Augmentations augmentations) throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.unparsedEntityDecl(name, identifier, notation, augmentations);
+            }
+        }
+
+        public void notationDecl(String name, XMLResourceIdentifier identifier,
+                Augmentations augmentations) throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.notationDecl(name, identifier, augmentations);
+            }
+        }
+
+        public void startConditional(short type, Augmentations augmentations)
+                throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.startConditional(type, augmentations);
+            }
+        }
+
+        public void ignoredCharacters(XMLString text, Augmentations augmentations)
+                throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.ignoredCharacters(text, augmentations);
+            }
+
+        }
+
+        public void endConditional(Augmentations augmentations) throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.endConditional(augmentations);
+            }
+        }
+
+        public void endDTD(Augmentations augmentations) throws XNIException {
+            if (fDTDHandler != null) {
+                fDTDHandler.endDTD(augmentations);
+            }
+        }
+
+        public void setDTDSource(XMLDTDSource source) {
+            fDTDSource = source;
+        }
+
+        public XMLDTDSource getDTDSource() {
+            return fDTDSource;
+        }
+        
+        /*
+         * XMLDTDSource methods
+         */
+
+        public void setDTDHandler(XMLDTDHandler handler) {
+            fDTDHandler = handler;
+        }
+
+        public XMLDTDHandler getDTDHandler() {
+            return fDTDHandler;
+        }
+    }
+    
+    /**
+     * XMLEntityResolver which checks limits imposed by the application 
+     * on the sizes of general and parameter entities.
+     */
+    final class ExternalEntityMonitor implements XMLEntityResolver {
+        
+        /**
+         * java.io.InputStream wrapper which check entity size limits.
+         */
+        final class InputStreamMonitor extends FilterInputStream {
+            
+            private final boolean isPE;
+            private int size = 0;
+
+            protected InputStreamMonitor(InputStream in, boolean isPE) {
+                super(in);
+                this.isPE = isPE;
+            }
+            
+            public int read() throws IOException {
+                int i = super.read();
+                if (i != -1) {
+                    ++size;
+                    checkEntitySizeLimits(size, 1, isPE);
+                }
+                return i;
+            }
+            
+            public int read(byte[] b, int off, int len) throws IOException {
+                int i = super.read(b, off, len);
+                if (i > 0) {
+                    size += i;
+                    checkEntitySizeLimits(size, i, isPE);
+                }
+                return i;
+            }
+        }
+        
+        /**
+         * java.io.Reader wrapper which check entity size limits.
+         */
+        final class ReaderMonitor extends FilterReader {
+            
+            private final boolean isPE;
+            private int size = 0;
+
+            protected ReaderMonitor(Reader in, boolean isPE) {
+                super(in);
+                this.isPE = isPE;
+            }
+            
+            public int read() throws IOException {
+                int i = super.read();
+                if (i != -1) {
+                    ++size;
+                    checkEntitySizeLimits(size, 1, isPE);
+                }
+                return i;
+            }
+            
+            public int read(char[] cbuf, int off, int len) throws IOException {
+                int i = super.read(cbuf, off, len);
+                if (i > 0) {
+                    size += i;
+                    checkEntitySizeLimits(size, i, isPE);
+                }
+                return i;
+            }
+        }
+        
+        private XMLEntityResolver fEntityResolver;
+
+        public XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier) throws XNIException,
+                IOException {
+            XMLInputSource source = null;
+            if (fEntityResolver != null) {
+                source = fEntityResolver.resolveEntity(resourceIdentifier);
+            }
+            if (fSecurityManager != null && resourceIdentifier instanceof XMLEntityDescription) {
+                String name = ((XMLEntityDescription) resourceIdentifier).getEntityName();
+                boolean isPE = name != null && name.startsWith("%");
+                if (source == null) {
+                    String publicId = resourceIdentifier.getPublicId();
+                    String systemId = resourceIdentifier.getExpandedSystemId();
+                    String baseSystemId = resourceIdentifier.getBaseSystemId();
+                    source = new XMLInputSource(publicId, systemId, baseSystemId);
+                }
+                Reader reader = source.getCharacterStream();
+                if (reader != null) {
+                    source.setCharacterStream(new ReaderMonitor(reader, isPE));
+                }
+                else {
+                    InputStream stream = source.getByteStream();
+                    if (stream != null) {
+                        source.setByteStream(new InputStreamMonitor(stream, isPE));
+                    }
+                    else {
+                        String systemId = resourceIdentifier.getExpandedSystemId();
+                        URL url = new URL(systemId);
+                        stream = url.openStream();
+                        source.setByteStream(new InputStreamMonitor(stream, isPE));
+                    }
+                }
+            }
+            return source;
+        }
+        
+        /** Sets the XNI entity resolver. */
+        public void setEntityResolver(XMLEntityResolver entityResolver) {
+            fEntityResolver = entityResolver;
+        } // setEntityResolver(XMLEntityResolver)
+
+        /** Returns the XNI entity resolver. */
+        public XMLEntityResolver getEntityResolver() {
+            return fEntityResolver;
+        } // getEntityResolver():XMLEntityResolver
+    }  
+}