1 /*
   2  * Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved.
   3  */
   4 /*
   5  * Licensed to the Apache Software Foundation (ASF) under one or more
   6  * contributor license agreements.  See the NOTICE file distributed with
   7  * this work for additional information regarding copyright ownership.
   8  * The ASF licenses this file to You under the Apache License, Version 2.0
   9  * (the "License"); you may not use this file except in compliance with
  10  * the License.  You may obtain a copy of the License at
  11  *
  12  *      http://www.apache.org/licenses/LICENSE-2.0
  13  *
  14  * Unless required by applicable law or agreed to in writing, software
  15  * distributed under the License is distributed on an "AS IS" BASIS,
  16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17  * See the License for the specific language governing permissions and
  18  * limitations under the License.
  19  */
  20 
  21 package com.sun.org.apache.xerces.internal.impl ;
  22 
  23 import com.sun.org.apache.xerces.internal.impl.io.ASCIIReader;
  24 import com.sun.org.apache.xerces.internal.impl.io.UCSReader;
  25 import com.sun.org.apache.xerces.internal.impl.io.UTF16Reader;
  26 import com.sun.org.apache.xerces.internal.impl.io.UTF8Reader;
  27 import com.sun.org.apache.xerces.internal.impl.msg.XMLMessageFormatter;
  28 import com.sun.org.apache.xerces.internal.impl.validation.ValidationManager;
  29 import com.sun.org.apache.xerces.internal.util.*;
  30 import com.sun.org.apache.xerces.internal.util.URI;
  31 import com.sun.org.apache.xerces.internal.utils.XMLLimitAnalyzer;
  32 import com.sun.org.apache.xerces.internal.utils.XMLSecurityManager;
  33 import com.sun.org.apache.xerces.internal.utils.XMLSecurityPropertyManager;
  34 import com.sun.org.apache.xerces.internal.xni.Augmentations;
  35 import com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier;
  36 import com.sun.org.apache.xerces.internal.xni.XNIException;
  37 import com.sun.org.apache.xerces.internal.xni.parser.*;
  38 import com.sun.xml.internal.stream.Entity;
  39 import com.sun.xml.internal.stream.StaxEntityResolverWrapper;
  40 import com.sun.xml.internal.stream.StaxXMLInputSource;
  41 import com.sun.xml.internal.stream.XMLEntityStorage;
  42 import java.io.*;
  43 import java.net.HttpURLConnection;
  44 import java.net.URISyntaxException;
  45 import java.net.URL;
  46 import java.net.URLConnection;
  47 import java.util.HashMap;
  48 import java.util.Iterator;
  49 import java.util.Locale;
  50 import java.util.Map;
  51 import java.util.Stack;
  52 import java.util.StringTokenizer;
  53 import javax.xml.XMLConstants;
  54 import javax.xml.catalog.CatalogException;
  55 import javax.xml.catalog.CatalogFeatures.Feature;
  56 import javax.xml.catalog.CatalogFeatures;
  57 import javax.xml.catalog.CatalogManager;
  58 import javax.xml.catalog.CatalogResolver;
  59 import javax.xml.stream.XMLInputFactory;
  60 import javax.xml.transform.Source;
  61 import jdk.xml.internal.JdkXmlUtils;
  62 import jdk.xml.internal.SecuritySupport;
  63 import org.xml.sax.InputSource;
  64 
  65 
  66 /**
  67  * Will keep track of current entity.
  68  *
  69  * The entity manager handles the registration of general and parameter
  70  * entities; resolves entities; and starts entities. The entity manager
  71  * is a central component in a standard parser configuration and this
  72  * class works directly with the entity scanner to manage the underlying
  73  * xni.
  74  * <p>
  75  * This component requires the following features and properties from the
  76  * component manager that uses it:
  77  * <ul>
  78  *  <li>http://xml.org/sax/features/validation</li>
  79  *  <li>http://xml.org/sax/features/external-general-entities</li>
  80  *  <li>http://xml.org/sax/features/external-parameter-entities</li>
  81  *  <li>http://apache.org/xml/features/allow-java-encodings</li>
  82  *  <li>http://apache.org/xml/properties/internal/symbol-table</li>
  83  *  <li>http://apache.org/xml/properties/internal/error-reporter</li>
  84  *  <li>http://apache.org/xml/properties/internal/entity-resolver</li>
  85  * </ul>
  86  *
  87  *
  88  * @author Andy Clark, IBM
  89  * @author Arnaud  Le Hors, IBM
  90  * @author K.Venugopal SUN Microsystems
  91  * @author Neeraj Bajaj SUN Microsystems
  92  * @author Sunitha Reddy SUN Microsystems
  93  * @LastModified: Apr 2019
  94  */
  95 public class XMLEntityManager implements XMLComponent, XMLEntityResolver {
  96 
  97     //
  98     // Constants
  99     //
 100 
 101     /** Default buffer size (2048). */
 102     public static final int DEFAULT_BUFFER_SIZE = 8192;
 103 
 104     /** Default buffer size before we've finished with the XMLDecl:  */
 105     public static final int DEFAULT_XMLDECL_BUFFER_SIZE = 64;
 106 
 107     /** Default internal entity buffer size (1024). */
 108     public static final int DEFAULT_INTERNAL_BUFFER_SIZE = 1024;
 109 
 110     // feature identifiers
 111 
 112     /** Feature identifier: validation. */
 113     protected static final String VALIDATION =
 114             Constants.SAX_FEATURE_PREFIX + Constants.VALIDATION_FEATURE;
 115 
 116     /**
 117      * standard uri conformant (strict uri).
 118      * http://apache.org/xml/features/standard-uri-conformant
 119      */
 120     protected boolean fStrictURI;
 121 
 122 
 123     /** Feature identifier: external general entities. */
 124     protected static final String EXTERNAL_GENERAL_ENTITIES =
 125             Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_GENERAL_ENTITIES_FEATURE;
 126 
 127     /** Feature identifier: external parameter entities. */
 128     protected static final String EXTERNAL_PARAMETER_ENTITIES =
 129             Constants.SAX_FEATURE_PREFIX + Constants.EXTERNAL_PARAMETER_ENTITIES_FEATURE;
 130 
 131     /** Feature identifier: allow Java encodings. */
 132     protected static final String ALLOW_JAVA_ENCODINGS =
 133             Constants.XERCES_FEATURE_PREFIX + Constants.ALLOW_JAVA_ENCODINGS_FEATURE;
 134 
 135     /** Feature identifier: warn on duplicate EntityDef */
 136     protected static final String WARN_ON_DUPLICATE_ENTITYDEF =
 137             Constants.XERCES_FEATURE_PREFIX +Constants.WARN_ON_DUPLICATE_ENTITYDEF_FEATURE;
 138 
 139     /** Feature identifier: load external DTD. */
 140     protected static final String LOAD_EXTERNAL_DTD =
 141             Constants.XERCES_FEATURE_PREFIX + Constants.LOAD_EXTERNAL_DTD_FEATURE;
 142 
 143     // property identifiers
 144 
 145     /** Property identifier: symbol table. */
 146     protected static final String SYMBOL_TABLE =
 147             Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY;
 148 
 149     /** Property identifier: error reporter. */
 150     protected static final String ERROR_REPORTER =
 151             Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY;
 152 
 153     /** Feature identifier: standard uri conformant */
 154     protected static final String STANDARD_URI_CONFORMANT =
 155             Constants.XERCES_FEATURE_PREFIX +Constants.STANDARD_URI_CONFORMANT_FEATURE;
 156 
 157     /** Property identifier: entity resolver. */
 158     protected static final String ENTITY_RESOLVER =
 159             Constants.XERCES_PROPERTY_PREFIX + Constants.ENTITY_RESOLVER_PROPERTY;
 160 
 161     protected static final String STAX_ENTITY_RESOLVER =
 162             Constants.XERCES_PROPERTY_PREFIX + Constants.STAX_ENTITY_RESOLVER_PROPERTY;
 163 
 164     // property identifier:  ValidationManager
 165     protected static final String VALIDATION_MANAGER =
 166             Constants.XERCES_PROPERTY_PREFIX + Constants.VALIDATION_MANAGER_PROPERTY;
 167 
 168     /** property identifier: buffer size. */
 169     protected static final String BUFFER_SIZE =
 170             Constants.XERCES_PROPERTY_PREFIX + Constants.BUFFER_SIZE_PROPERTY;
 171 
 172     /** property identifier: security manager. */
 173     protected static final String SECURITY_MANAGER =
 174         Constants.XERCES_PROPERTY_PREFIX + Constants.SECURITY_MANAGER_PROPERTY;
 175 
 176     protected static final String PARSER_SETTINGS =
 177         Constants.XERCES_FEATURE_PREFIX + Constants.PARSER_SETTINGS;
 178 
 179     /** Property identifier: Security property manager. */
 180     private static final String XML_SECURITY_PROPERTY_MANAGER =
 181             Constants.XML_SECURITY_PROPERTY_MANAGER;
 182 
 183     /** access external dtd: file protocol */
 184     static final String EXTERNAL_ACCESS_DEFAULT = Constants.EXTERNAL_ACCESS_DEFAULT;
 185 
 186     // recognized features and properties
 187 
 188     /** Recognized features. */
 189     private static final String[] RECOGNIZED_FEATURES = {
 190                 VALIDATION,
 191                 EXTERNAL_GENERAL_ENTITIES,
 192                 EXTERNAL_PARAMETER_ENTITIES,
 193                 ALLOW_JAVA_ENCODINGS,
 194                 WARN_ON_DUPLICATE_ENTITYDEF,
 195                 STANDARD_URI_CONFORMANT,
 196                 XMLConstants.USE_CATALOG
 197     };
 198 
 199     /** Feature defaults. */
 200     private static final Boolean[] FEATURE_DEFAULTS = {
 201                 null,
 202                 Boolean.TRUE,
 203                 Boolean.TRUE,
 204                 Boolean.TRUE,
 205                 Boolean.FALSE,
 206                 Boolean.FALSE,
 207                 JdkXmlUtils.USE_CATALOG_DEFAULT
 208     };
 209 
 210     /** Recognized properties. */
 211     private static final String[] RECOGNIZED_PROPERTIES = {
 212                 SYMBOL_TABLE,
 213                 ERROR_REPORTER,
 214                 ENTITY_RESOLVER,
 215                 VALIDATION_MANAGER,
 216                 BUFFER_SIZE,
 217                 SECURITY_MANAGER,
 218                 XML_SECURITY_PROPERTY_MANAGER,
 219                 JdkXmlUtils.CATALOG_DEFER,
 220                 JdkXmlUtils.CATALOG_FILES,
 221                 JdkXmlUtils.CATALOG_PREFER,
 222                 JdkXmlUtils.CATALOG_RESOLVE,
 223                 JdkXmlUtils.CDATA_CHUNK_SIZE
 224     };
 225 
 226     /** Property defaults. */
 227     private static final Object[] PROPERTY_DEFAULTS = {
 228                 null,
 229                 null,
 230                 null,
 231                 null,
 232                 DEFAULT_BUFFER_SIZE,
 233                 null,
 234                 null,
 235                 null,
 236                 null,
 237                 null,
 238                 null,
 239                 JdkXmlUtils.CDATA_CHUNK_SIZE_DEFAULT
 240     };
 241 
 242     private static final String XMLEntity = "[xml]".intern();
 243     private static final String DTDEntity = "[dtd]".intern();
 244 
 245     // debugging
 246 
 247     /**
 248      * Debug printing of buffer. This debugging flag works best when you
 249      * resize the DEFAULT_BUFFER_SIZE down to something reasonable like
 250      * 64 characters.
 251      */
 252     private static final boolean DEBUG_BUFFER = false;
 253 
 254     /** warn on duplicate Entity declaration.
 255      *  http://apache.org/xml/features/warn-on-duplicate-entitydef
 256      */
 257     protected boolean fWarnDuplicateEntityDef;
 258 
 259     /** Debug some basic entities. */
 260     private static final boolean DEBUG_ENTITIES = false;
 261 
 262     /** Debug switching readers for encodings. */
 263     private static final boolean DEBUG_ENCODINGS = false;
 264 
 265     // should be diplayed trace resolving messages
 266     private static final boolean DEBUG_RESOLVER = false ;
 267 
 268     //
 269     // Data
 270     //
 271 
 272     // features
 273 
 274     /**
 275      * Validation. This feature identifier is:
 276      * http://xml.org/sax/features/validation
 277      */
 278     protected boolean fValidation;
 279 
 280     /**
 281      * External general entities. This feature identifier is:
 282      * http://xml.org/sax/features/external-general-entities
 283      */
 284     protected boolean fExternalGeneralEntities;
 285 
 286     /**
 287      * External parameter entities. This feature identifier is:
 288      * http://xml.org/sax/features/external-parameter-entities
 289      */
 290     protected boolean fExternalParameterEntities;
 291 
 292     /**
 293      * Allow Java encoding names. This feature identifier is:
 294      * http://apache.org/xml/features/allow-java-encodings
 295      */
 296     protected boolean fAllowJavaEncodings = true ;
 297 
 298     /** Load external DTD. */
 299     protected boolean fLoadExternalDTD = true;
 300 
 301     // properties
 302 
 303     /**
 304      * Symbol table. This property identifier is:
 305      * http://apache.org/xml/properties/internal/symbol-table
 306      */
 307     protected SymbolTable fSymbolTable;
 308 
 309     /**
 310      * Error reporter. This property identifier is:
 311      * http://apache.org/xml/properties/internal/error-reporter
 312      */
 313     protected XMLErrorReporter fErrorReporter;
 314 
 315     /**
 316      * Entity resolver. This property identifier is:
 317      * http://apache.org/xml/properties/internal/entity-resolver
 318      */
 319     protected XMLEntityResolver fEntityResolver;
 320 
 321     /** Stax Entity Resolver. This property identifier is XMLInputFactory.ENTITY_RESOLVER */
 322 
 323     protected StaxEntityResolverWrapper fStaxEntityResolver;
 324 
 325     /** Property Manager. This is used from Stax */
 326     protected PropertyManager fPropertyManager ;
 327 
 328     /** StAX properties */
 329     boolean fSupportDTD = true;
 330     boolean fReplaceEntityReferences = true;
 331     boolean fSupportExternalEntities = true;
 332 
 333     /** used to restrict external access */
 334     protected String fAccessExternalDTD = EXTERNAL_ACCESS_DEFAULT;
 335 
 336     // settings
 337 
 338     /**
 339      * Validation manager. This property identifier is:
 340      * http://apache.org/xml/properties/internal/validation-manager
 341      */
 342     protected ValidationManager fValidationManager;
 343 
 344     // settings
 345 
 346     /**
 347      * Buffer size. We get this value from a property. The default size
 348      * is used if the input buffer size property is not specified.
 349      * REVISIT: do we need a property for internal entity buffer size?
 350      */
 351     protected int fBufferSize = DEFAULT_BUFFER_SIZE;
 352 
 353     /** Security Manager */
 354     protected XMLSecurityManager fSecurityManager = null;
 355 
 356     protected XMLLimitAnalyzer fLimitAnalyzer = null;
 357 
 358     protected int entityExpansionIndex;
 359 
 360     /**
 361      * True if the document entity is standalone. This should really
 362      * only be set by the document source (e.g. XMLDocumentScanner).
 363      */
 364     protected boolean fStandalone;
 365 
 366     // are the entities being parsed in the external subset?
 367     // NOTE:  this *is not* the same as whether they're external entities!
 368     protected boolean fInExternalSubset = false;
 369 
 370 
 371     // handlers
 372     /** Entity handler. */
 373     protected XMLEntityHandler fEntityHandler;
 374 
 375     /** Current entity scanner */
 376     protected XMLEntityScanner fEntityScanner ;
 377 
 378     /** XML 1.0 entity scanner. */
 379     protected XMLEntityScanner fXML10EntityScanner;
 380 
 381     /** XML 1.1 entity scanner. */
 382     protected XMLEntityScanner fXML11EntityScanner;
 383 
 384     /** count of entities expanded: */
 385     protected int fEntityExpansionCount = 0;
 386 
 387     // entities
 388 
 389     /** Entities. */
 390     protected Map<String, Entity> fEntities = new HashMap<>();
 391 
 392     /** Entity stack. */
 393     protected Stack<Entity> fEntityStack = new Stack<>();
 394 
 395     /** Current entity. */
 396     protected Entity.ScannedEntity fCurrentEntity = null;
 397 
 398     /** identify if the InputSource is created by a resolver */
 399     boolean fISCreatedByResolver = false;
 400 
 401     // shared context
 402 
 403     protected XMLEntityStorage fEntityStorage ;
 404 
 405     protected final Object [] defaultEncoding = new Object[]{"UTF-8", null};
 406 
 407 
 408     // temp vars
 409 
 410     /** Resource identifer. */
 411     private final XMLResourceIdentifierImpl fResourceIdentifier = new XMLResourceIdentifierImpl();
 412 
 413     /** Augmentations for entities. */
 414     private final Augmentations fEntityAugs = new AugmentationsImpl();
 415 
 416     /** indicate whether Catalog should be used for resolving external resources */
 417     private boolean fUseCatalog = true;
 418     CatalogFeatures fCatalogFeatures;
 419     CatalogResolver fCatalogResolver;
 420 
 421     private String fCatalogFile;
 422     private String fDefer;
 423     private String fPrefer;
 424     private String fResolve;
 425 
 426     //
 427     // Constructors
 428     //
 429 
 430     /**
 431      * If this constructor is used to create the object, reset() should be invoked on this object
 432      */
 433     public XMLEntityManager() {
 434         //for entity managers not created by parsers
 435         fSecurityManager = new XMLSecurityManager(true);
 436         fEntityStorage = new XMLEntityStorage(this) ;
 437         setScannerVersion(Constants.XML_VERSION_1_0);
 438     } // <init>()
 439 
 440     /** Default constructor. */
 441     public XMLEntityManager(PropertyManager propertyManager) {
 442         fPropertyManager = propertyManager ;
 443         //pass a reference to current entity being scanned
 444         //fEntityStorage = new XMLEntityStorage(fCurrentEntity) ;
 445         fEntityStorage = new XMLEntityStorage(this) ;
 446         fEntityScanner = new XMLEntityScanner(propertyManager, this) ;
 447         reset(propertyManager);
 448     } // <init>()
 449 
 450     /**
 451      * Adds an internal entity declaration.
 452      * <p>
 453      * <strong>Note:</strong> This method ignores subsequent entity
 454      * declarations.
 455      * <p>
 456      * <strong>Note:</strong> The name should be a unique symbol. The
 457      * SymbolTable can be used for this purpose.
 458      *
 459      * @param name The name of the entity.
 460      * @param text The text of the entity.
 461      *
 462      * @see SymbolTable
 463      */
 464     public void addInternalEntity(String name, String text) {
 465         if (!fEntities.containsKey(name)) {
 466             Entity entity = new Entity.InternalEntity(name, text, fInExternalSubset);
 467             fEntities.put(name, entity);
 468         } else{
 469             if(fWarnDuplicateEntityDef){
 470                 fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
 471                         "MSG_DUPLICATE_ENTITY_DEFINITION",
 472                         new Object[]{ name },
 473                         XMLErrorReporter.SEVERITY_WARNING );
 474             }
 475         }
 476 
 477     } // addInternalEntity(String,String)
 478 
 479     /**
 480      * Adds an external entity declaration.
 481      * <p>
 482      * <strong>Note:</strong> This method ignores subsequent entity
 483      * declarations.
 484      * <p>
 485      * <strong>Note:</strong> The name should be a unique symbol. The
 486      * SymbolTable can be used for this purpose.
 487      *
 488      * @param name         The name of the entity.
 489      * @param publicId     The public identifier of the entity.
 490      * @param literalSystemId     The system identifier of the entity.
 491      * @param baseSystemId The base system identifier of the entity.
 492      *                     This is the system identifier of the entity
 493      *                     where <em>the entity being added</em> and
 494      *                     is used to expand the system identifier when
 495      *                     the system identifier is a relative URI.
 496      *                     When null the system identifier of the first
 497      *                     external entity on the stack is used instead.
 498      *
 499      * @see SymbolTable
 500      */
 501     public void addExternalEntity(String name,
 502             String publicId, String literalSystemId,
 503             String baseSystemId) throws IOException {
 504         if (!fEntities.containsKey(name)) {
 505             if (baseSystemId == null) {
 506                 // search for the first external entity on the stack
 507                 int size = fEntityStack.size();
 508                 if (size == 0 && fCurrentEntity != null && fCurrentEntity.entityLocation != null) {
 509                     baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId();
 510                 }
 511                 for (int i = size - 1; i >= 0 ; i--) {
 512                     Entity.ScannedEntity externalEntity =
 513                             (Entity.ScannedEntity)fEntityStack.get(i);
 514                     if (externalEntity.entityLocation != null && externalEntity.entityLocation.getExpandedSystemId() != null) {
 515                         baseSystemId = externalEntity.entityLocation.getExpandedSystemId();
 516                         break;
 517                     }
 518                 }
 519             }
 520             Entity entity = new Entity.ExternalEntity(name,
 521                     new XMLEntityDescriptionImpl(name, publicId, literalSystemId, baseSystemId,
 522                     expandSystemId(literalSystemId, baseSystemId, false)), null, fInExternalSubset);
 523             fEntities.put(name, entity);
 524         } else{
 525             if(fWarnDuplicateEntityDef){
 526                 fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
 527                         "MSG_DUPLICATE_ENTITY_DEFINITION",
 528                         new Object[]{ name },
 529                         XMLErrorReporter.SEVERITY_WARNING );
 530             }
 531         }
 532 
 533     } // addExternalEntity(String,String,String,String)
 534 
 535 
 536     /**
 537      * Adds an unparsed entity declaration.
 538      * <p>
 539      * <strong>Note:</strong> This method ignores subsequent entity
 540      * declarations.
 541      * <p>
 542      * <strong>Note:</strong> The name should be a unique symbol. The
 543      * SymbolTable can be used for this purpose.
 544      *
 545      * @param name     The name of the entity.
 546      * @param publicId The public identifier of the entity.
 547      * @param systemId The system identifier of the entity.
 548      * @param notation The name of the notation.
 549      *
 550      * @see SymbolTable
 551      */
 552     public void addUnparsedEntity(String name,
 553             String publicId, String systemId,
 554             String baseSystemId, String notation) {
 555         if (!fEntities.containsKey(name)) {
 556             Entity.ExternalEntity entity = new Entity.ExternalEntity(name,
 557                     new XMLEntityDescriptionImpl(name, publicId, systemId, baseSystemId, null),
 558                     notation, fInExternalSubset);
 559             fEntities.put(name, entity);
 560         } else{
 561             if(fWarnDuplicateEntityDef){
 562                 fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
 563                         "MSG_DUPLICATE_ENTITY_DEFINITION",
 564                         new Object[]{ name },
 565                         XMLErrorReporter.SEVERITY_WARNING );
 566             }
 567         }
 568     } // addUnparsedEntity(String,String,String,String)
 569 
 570 
 571     /** get the entity storage object from entity manager */
 572     public XMLEntityStorage getEntityStore(){
 573         return fEntityStorage ;
 574     }
 575 
 576     /** return the entity responsible for reading the entity */
 577     public XMLEntityScanner getEntityScanner(){
 578         if(fEntityScanner == null) {
 579             // default to 1.0
 580             if(fXML10EntityScanner == null) {
 581                 fXML10EntityScanner = new XMLEntityScanner();
 582             }
 583             fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter);
 584             fEntityScanner = fXML10EntityScanner;
 585         }
 586         return fEntityScanner;
 587 
 588     }
 589 
 590     public void setScannerVersion(short version) {
 591 
 592         if(version == Constants.XML_VERSION_1_0) {
 593             if(fXML10EntityScanner == null) {
 594                 fXML10EntityScanner = new XMLEntityScanner();
 595             }
 596             fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter);
 597             fEntityScanner = fXML10EntityScanner;
 598             fEntityScanner.setCurrentEntity(fCurrentEntity);
 599         } else {
 600             if(fXML11EntityScanner == null) {
 601                 fXML11EntityScanner = new XML11EntityScanner();
 602             }
 603             fXML11EntityScanner.reset(fSymbolTable, this, fErrorReporter);
 604             fEntityScanner = fXML11EntityScanner;
 605             fEntityScanner.setCurrentEntity(fCurrentEntity);
 606         }
 607 
 608     }
 609 
 610     /**
 611      * This method uses the passed-in XMLInputSource to make
 612      * fCurrentEntity usable for reading.
 613      *
 614      * @param reference flag to indicate whether the entity is an Entity Reference.
 615      * @param name  name of the entity (XML is it's the document entity)
 616      * @param xmlInputSource    the input source, with sufficient information
 617      *      to begin scanning characters.
 618      * @param literal        True if this entity is started within a
 619      *                       literal value.
 620      * @param isExternal    whether this entity should be treated as an internal or external entity.
 621      * @throws IOException  if anything can't be read
 622      *  XNIException    If any parser-specific goes wrong.
 623      * @return the encoding of the new entity or null if a character stream was employed
 624      */
 625     public String setupCurrentEntity(boolean reference, String name, XMLInputSource xmlInputSource,
 626             boolean literal, boolean isExternal)
 627             throws IOException, XNIException {
 628         // get information
 629 
 630         final String publicId = xmlInputSource.getPublicId();
 631         String literalSystemId = xmlInputSource.getSystemId();
 632         String baseSystemId = xmlInputSource.getBaseSystemId();
 633         String encoding = xmlInputSource.getEncoding();
 634         final boolean encodingExternallySpecified = (encoding != null);
 635         Boolean isBigEndian = null;
 636 
 637         // create reader
 638         InputStream stream = null;
 639         Reader reader = xmlInputSource.getCharacterStream();
 640 
 641         // First chance checking strict URI
 642         String expandedSystemId = expandSystemId(literalSystemId, baseSystemId, fStrictURI);
 643         if (baseSystemId == null) {
 644             baseSystemId = expandedSystemId;
 645         }
 646         if (reader == null) {
 647             stream = xmlInputSource.getByteStream();
 648             if (stream == null) {
 649                 URL location = new URL(expandedSystemId);
 650                 URLConnection connect = location.openConnection();
 651                 if (!(connect instanceof HttpURLConnection)) {
 652                     stream = connect.getInputStream();
 653                 }
 654                 else {
 655                     boolean followRedirects = true;
 656 
 657                     // setup URLConnection if we have an HTTPInputSource
 658                     if (xmlInputSource instanceof HTTPInputSource) {
 659                         final HttpURLConnection urlConnection = (HttpURLConnection) connect;
 660                         final HTTPInputSource httpInputSource = (HTTPInputSource) xmlInputSource;
 661 
 662                         // set request properties
 663                         Iterator<Map.Entry<String, String>> propIter = httpInputSource.getHTTPRequestProperties();
 664                         while (propIter.hasNext()) {
 665                             Map.Entry<String, String> entry = propIter.next();
 666                             urlConnection.setRequestProperty(entry.getKey(), entry.getValue());
 667                         }
 668 
 669                         // set preference for redirection
 670                         followRedirects = httpInputSource.getFollowHTTPRedirects();
 671                         if (!followRedirects) {
 672                             urlConnection.setInstanceFollowRedirects(followRedirects);
 673                         }
 674                     }
 675 
 676                     stream = connect.getInputStream();
 677 
 678                     // REVISIT: If the URLConnection has external encoding
 679                     // information, we should be reading it here. It's located
 680                     // in the charset parameter of Content-Type. -- mrglavas
 681 
 682                     if (followRedirects) {
 683                         String redirect = connect.getURL().toString();
 684                         // E43: Check if the URL was redirected, and then
 685                         // update literal and expanded system IDs if needed.
 686                         if (!redirect.equals(expandedSystemId)) {
 687                             literalSystemId = redirect;
 688                             expandedSystemId = redirect;
 689                         }
 690                     }
 691                 }
 692             }
 693 
 694             // wrap this stream in RewindableInputStream
 695             RewindableInputStream rewindableStream = new RewindableInputStream(stream);
 696             stream = rewindableStream;
 697 
 698             // perform auto-detect of encoding if necessary
 699             if (encoding == null) {
 700                 // read first four bytes and determine encoding
 701                 final byte[] b4 = new byte[4];
 702                 int count = 0;
 703                 for (; count<4; count++ ) {
 704                     b4[count] = (byte)rewindableStream.readAndBuffer();
 705                 }
 706                 if (count == 4) {
 707                     final EncodingInfo info = getEncodingInfo(b4, count);
 708                     encoding = info.autoDetectedEncoding;
 709                     final String readerEncoding = info.readerEncoding;
 710                     isBigEndian = info.isBigEndian;
 711                     stream.reset();
 712                     if (info.hasBOM) {
 713                         // Special case UTF-8 files with BOM created by Microsoft
 714                         // tools. It's more efficient to consume the BOM than make
 715                         // the reader perform extra checks. -Ac
 716                         if (EncodingInfo.STR_UTF8.equals(readerEncoding)) {
 717                             // UTF-8 BOM: 0xEF 0xBB 0xBF
 718                             stream.skip(3);
 719                         }
 720                         // It's also more efficient to consume the UTF-16 BOM.
 721                         else if (EncodingInfo.STR_UTF16.equals(readerEncoding)) {
 722                             // UTF-16 BE BOM: 0xFE 0xFF
 723                             // UTF-16 LE BOM: 0xFF 0xFE
 724                             stream.skip(2);
 725                         }
 726                     }
 727                     reader = createReader(stream, readerEncoding, isBigEndian);
 728                 } else {
 729                     reader = createReader(stream, encoding, isBigEndian);
 730                 }
 731             }
 732 
 733             // use specified encoding
 734             else {
 735                 encoding = encoding.toUpperCase(Locale.ENGLISH);
 736 
 737                 // If encoding is UTF-8, consume BOM if one is present.
 738                 if (EncodingInfo.STR_UTF8.equals(encoding)) {
 739                     final int[] b3 = new int[3];
 740                     int count = 0;
 741                     for (; count < 3; ++count) {
 742                         b3[count] = rewindableStream.readAndBuffer();
 743                         if (b3[count] == -1)
 744                             break;
 745                     }
 746                     if (count == 3) {
 747                         if (b3[0] != 0xEF || b3[1] != 0xBB || b3[2] != 0xBF) {
 748                             // First three bytes are not BOM, so reset.
 749                             stream.reset();
 750                         }
 751                     } else {
 752                         stream.reset();
 753                     }
 754                 }
 755                 // If encoding is UTF-16, we still need to read the first
 756                 // four bytes, in order to discover the byte order.
 757                 else if (EncodingInfo.STR_UTF16.equals(encoding)) {
 758                     final int[] b4 = new int[4];
 759                     int count = 0;
 760                     for (; count < 4; ++count) {
 761                         b4[count] = rewindableStream.readAndBuffer();
 762                         if (b4[count] == -1)
 763                             break;
 764                     }
 765                     stream.reset();
 766                     if (count >= 2) {
 767                         final int b0 = b4[0];
 768                         final int b1 = b4[1];
 769                         if (b0 == 0xFE && b1 == 0xFF) {
 770                             // UTF-16, big-endian
 771                             isBigEndian = Boolean.TRUE;
 772                             stream.skip(2);
 773                         }
 774                         else if (b0 == 0xFF && b1 == 0xFE) {
 775                             // UTF-16, little-endian
 776                             isBigEndian = Boolean.FALSE;
 777                             stream.skip(2);
 778                         }
 779                         else if (count == 4) {
 780                             final int b2 = b4[2];
 781                             final int b3 = b4[3];
 782                             if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) {
 783                                 // UTF-16, big-endian, no BOM
 784                                 isBigEndian = Boolean.TRUE;
 785                             }
 786                             if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) {
 787                                 // UTF-16, little-endian, no BOM
 788                                 isBigEndian = Boolean.FALSE;
 789                             }
 790                         }
 791                     }
 792                 }
 793                 // If encoding is UCS-4, we still need to read the first four bytes
 794                 // in order to discover the byte order.
 795                 else if (EncodingInfo.STR_UCS4.equals(encoding)) {
 796                     final int[] b4 = new int[4];
 797                     int count = 0;
 798                     for (; count < 4; ++count) {
 799                         b4[count] = rewindableStream.readAndBuffer();
 800                         if (b4[count] == -1)
 801                             break;
 802                     }
 803                     stream.reset();
 804 
 805                     // Ignore unusual octet order for now.
 806                     if (count == 4) {
 807                         // UCS-4, big endian (1234)
 808                         if (b4[0] == 0x00 && b4[1] == 0x00 && b4[2] == 0x00 && b4[3] == 0x3C) {
 809                             isBigEndian = Boolean.TRUE;
 810                         }
 811                         // UCS-4, little endian (1234)
 812                         else if (b4[0] == 0x3C && b4[1] == 0x00 && b4[2] == 0x00 && b4[3] == 0x00) {
 813                             isBigEndian = Boolean.FALSE;
 814                         }
 815                     }
 816                 }
 817                 // If encoding is UCS-2, we still need to read the first four bytes
 818                 // in order to discover the byte order.
 819                 else if (EncodingInfo.STR_UCS2.equals(encoding)) {
 820                     final int[] b4 = new int[4];
 821                     int count = 0;
 822                     for (; count < 4; ++count) {
 823                         b4[count] = rewindableStream.readAndBuffer();
 824                         if (b4[count] == -1)
 825                             break;
 826                     }
 827                     stream.reset();
 828 
 829                     if (count == 4) {
 830                         // UCS-2, big endian
 831                         if (b4[0] == 0x00 && b4[1] == 0x3C && b4[2] == 0x00 && b4[3] == 0x3F) {
 832                             isBigEndian = Boolean.TRUE;
 833                         }
 834                         // UCS-2, little endian
 835                         else if (b4[0] == 0x3C && b4[1] == 0x00 && b4[2] == 0x3F && b4[3] == 0x00) {
 836                             isBigEndian = Boolean.FALSE;
 837                         }
 838                     }
 839                 }
 840 
 841                 reader = createReader(stream, encoding, isBigEndian);
 842             }
 843 
 844             // read one character at a time so we don't jump too far
 845             // ahead, converting characters from the byte stream in
 846             // the wrong encoding
 847             if (DEBUG_ENCODINGS) {
 848                 System.out.println("$$$ no longer wrapping reader in OneCharReader");
 849             }
 850             //reader = new OneCharReader(reader);
 851         }
 852 
 853         // We've seen a new Reader.
 854         // Push it on the stack so we can close it later.
 855         fReaderStack.push(reader);
 856 
 857         // push entity on stack
 858         if (fCurrentEntity != null) {
 859             fEntityStack.push(fCurrentEntity);
 860         }
 861 
 862         // create entity
 863         /* if encoding is specified externally, 'encoding' information present
 864          * in the prolog of the XML document is not considered. Hence, prolog can
 865          * be read in Chunks of data instead of byte by byte.
 866          */
 867         fCurrentEntity = new Entity.ScannedEntity(reference, name,
 868                 new XMLResourceIdentifierImpl(publicId, literalSystemId, baseSystemId, expandedSystemId),
 869                 stream, reader, encoding, literal, encodingExternallySpecified, isExternal);
 870         fCurrentEntity.setEncodingExternallySpecified(encodingExternallySpecified);
 871         fEntityScanner.setCurrentEntity(fCurrentEntity);
 872         fResourceIdentifier.setValues(publicId, literalSystemId, baseSystemId, expandedSystemId);
 873         if (fLimitAnalyzer != null) {
 874             fLimitAnalyzer.startEntity(name);
 875         }
 876         return encoding;
 877     } //setupCurrentEntity(String, XMLInputSource, boolean, boolean):  String
 878 
 879 
 880     /**
 881      * Checks whether an entity given by name is external.
 882      *
 883      * @param entityName The name of the entity to check.
 884      * @return True if the entity is external, false otherwise
 885      * (including when the entity is not declared).
 886      */
 887     public boolean isExternalEntity(String entityName) {
 888 
 889         Entity entity = fEntities.get(entityName);
 890         if (entity == null) {
 891             return false;
 892         }
 893         return entity.isExternal();
 894     }
 895 
 896     /**
 897      * Checks whether the declaration of an entity given by name is
 898      * // in the external subset.
 899      *
 900      * @param entityName The name of the entity to check.
 901      * @return True if the entity was declared in the external subset, false otherwise
 902      *           (including when the entity is not declared).
 903      */
 904     public boolean isEntityDeclInExternalSubset(String entityName) {
 905 
 906         Entity entity = fEntities.get(entityName);
 907         if (entity == null) {
 908             return false;
 909         }
 910         return entity.isEntityDeclInExternalSubset();
 911     }
 912 
 913 
 914 
 915     //
 916     // Public methods
 917     //
 918 
 919     /**
 920      * Sets whether the document entity is standalone.
 921      *
 922      * @param standalone True if document entity is standalone.
 923      */
 924     public void setStandalone(boolean standalone) {
 925         fStandalone = standalone;
 926     }
 927     // setStandalone(boolean)
 928 
 929     /** Returns true if the document entity is standalone. */
 930     public boolean isStandalone() {
 931         return fStandalone;
 932     }  //isStandalone():boolean
 933 
 934     public boolean isDeclaredEntity(String entityName) {
 935 
 936         Entity entity = fEntities.get(entityName);
 937         return entity != null;
 938     }
 939 
 940     public boolean isUnparsedEntity(String entityName) {
 941 
 942         Entity entity = fEntities.get(entityName);
 943         if (entity == null) {
 944             return false;
 945         }
 946         return entity.isUnparsed();
 947     }
 948 
 949 
 950 
 951     // this simply returns the fResourceIdentifier object;
 952     // this should only be used with caution by callers that
 953     // carefully manage the entity manager's behaviour, so that
 954     // this doesn't returning meaningless or misleading data.
 955     // @return  a reference to the current fResourceIdentifier object
 956     public XMLResourceIdentifier getCurrentResourceIdentifier() {
 957         return fResourceIdentifier;
 958     }
 959 
 960     /**
 961      * Sets the entity handler. When an entity starts and ends, the
 962      * entity handler is notified of the change.
 963      *
 964      * @param entityHandler The new entity handler.
 965      */
 966 
 967     public void setEntityHandler(com.sun.org.apache.xerces.internal.impl.XMLEntityHandler entityHandler) {
 968         fEntityHandler = entityHandler;
 969     } // setEntityHandler(XMLEntityHandler)
 970 
 971     //this function returns StaxXMLInputSource
 972     public StaxXMLInputSource resolveEntityAsPerStax(XMLResourceIdentifier resourceIdentifier) throws java.io.IOException{
 973 
 974         if(resourceIdentifier == null ) return null;
 975 
 976         String publicId = resourceIdentifier.getPublicId();
 977         String literalSystemId = resourceIdentifier.getLiteralSystemId();
 978         String baseSystemId = resourceIdentifier.getBaseSystemId();
 979         String expandedSystemId = resourceIdentifier.getExpandedSystemId();
 980         // if no base systemId given, assume that it's relative
 981         // to the systemId of the current scanned entity
 982         // Sometimes the system id is not (properly) expanded.
 983         // We need to expand the system id if:
 984         // a. the expanded one was null; or
 985         // b. the base system id was null, but becomes non-null from the current entity.
 986         boolean needExpand = (expandedSystemId == null);
 987         // REVISIT:  why would the baseSystemId ever be null?  if we
 988         // didn't have to make this check we wouldn't have to reuse the
 989         // fXMLResourceIdentifier object...
 990         if (baseSystemId == null && fCurrentEntity != null && fCurrentEntity.entityLocation != null) {
 991             baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId();
 992             if (baseSystemId != null)
 993                 needExpand = true;
 994         }
 995         if (needExpand)
 996             expandedSystemId = expandSystemId(literalSystemId, baseSystemId,false);
 997 
 998         // give the entity resolver a chance
 999         StaxXMLInputSource staxInputSource = null;
1000         XMLInputSource xmlInputSource = null;
1001 
1002         XMLResourceIdentifierImpl ri = null;
1003 
1004         if (resourceIdentifier instanceof XMLResourceIdentifierImpl) {
1005             ri = (XMLResourceIdentifierImpl)resourceIdentifier;
1006         } else {
1007             fResourceIdentifier.clear();
1008             ri = fResourceIdentifier;
1009         }
1010         ri.setValues(publicId, literalSystemId, baseSystemId, expandedSystemId);
1011         if(DEBUG_RESOLVER){
1012             System.out.println("BEFORE Calling resolveEntity") ;
1013         }
1014 
1015         fISCreatedByResolver = false;
1016         //either of Stax or Xerces would be null
1017         if(fStaxEntityResolver != null){
1018             staxInputSource = fStaxEntityResolver.resolveEntity(ri);
1019             if(staxInputSource != null) {
1020                 fISCreatedByResolver = true;
1021             }
1022         }
1023 
1024         if(fEntityResolver != null){
1025             xmlInputSource = fEntityResolver.resolveEntity(ri);
1026             if(xmlInputSource != null) {
1027                 fISCreatedByResolver = true;
1028             }
1029         }
1030 
1031         if(xmlInputSource != null){
1032             //wrap this XMLInputSource to StaxInputSource
1033             staxInputSource = new StaxXMLInputSource(xmlInputSource, fISCreatedByResolver);
1034         }
1035 
1036         if (staxInputSource == null && fUseCatalog) {
1037             if (fCatalogFeatures == null) {
1038                 fCatalogFeatures = JdkXmlUtils.getCatalogFeatures(fDefer, fCatalogFile, fPrefer, fResolve);
1039             }
1040             fCatalogFile = fCatalogFeatures.get(Feature.FILES);
1041             if (fCatalogFile != null) {
1042                 try {
1043                     if (fCatalogResolver == null) {
1044                         fCatalogResolver = CatalogManager.catalogResolver(fCatalogFeatures);
1045                     }
1046                     InputSource is = fCatalogResolver.resolveEntity(publicId, literalSystemId);
1047                     if (is != null && !is.isEmpty()) {
1048                         staxInputSource = new StaxXMLInputSource(new XMLInputSource(is, true), true);
1049                     }
1050                 } catch (CatalogException e) {
1051                     fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,"CatalogException",
1052                     new Object[]{SecuritySupport.sanitizePath(fCatalogFile)},
1053                     XMLErrorReporter.SEVERITY_FATAL_ERROR, e );
1054                 }
1055             }
1056         }
1057 
1058         // do default resolution
1059         //this works for both stax & Xerces, if staxInputSource is null,
1060         //it means parser need to revert to default resolution
1061         if (staxInputSource == null) {
1062             // REVISIT: when systemId is null, I think we should return null.
1063             //          is this the right solution? -SG
1064             //if (systemId != null)
1065             staxInputSource = new StaxXMLInputSource(
1066                     new XMLInputSource(publicId, literalSystemId, baseSystemId, true), false);
1067         }else if(staxInputSource.hasXMLStreamOrXMLEventReader()){
1068             //Waiting for the clarification from EG. - nb
1069         }
1070 
1071         if (DEBUG_RESOLVER) {
1072             System.err.println("XMLEntityManager.resolveEntity(" + publicId + ")");
1073             System.err.println(" = " + xmlInputSource);
1074         }
1075 
1076         return staxInputSource;
1077 
1078     }
1079 
1080     /**
1081      * Resolves the specified public and system identifiers. This
1082      * method first attempts to resolve the entity based on the
1083      * EntityResolver registered by the application. If no entity
1084      * resolver is registered or if the registered entity handler
1085      * is unable to resolve the entity, then default entity
1086      * resolution will occur.
1087      *
1088      * @param publicId     The public identifier of the entity.
1089      * @param systemId     The system identifier of the entity.
1090      * @param baseSystemId The base system identifier of the entity.
1091      *                     This is the system identifier of the current
1092      *                     entity and is used to expand the system
1093      *                     identifier when the system identifier is a
1094      *                     relative URI.
1095      *
1096      * @return Returns an input source that wraps the resolved entity.
1097      *         This method will never return null.
1098      *
1099      * @throws IOException  Thrown on i/o error.
1100      * @throws XNIException Thrown by entity resolver to signal an error.
1101      */
1102     public XMLInputSource resolveEntity(XMLResourceIdentifier resourceIdentifier) throws IOException, XNIException {
1103         if(resourceIdentifier == null ) return null;
1104         String publicId = resourceIdentifier.getPublicId();
1105         String literalSystemId = resourceIdentifier.getLiteralSystemId();
1106         String baseSystemId = resourceIdentifier.getBaseSystemId();
1107         String expandedSystemId = resourceIdentifier.getExpandedSystemId();
1108 
1109         // if no base systemId given, assume that it's relative
1110         // to the systemId of the current scanned entity
1111         // Sometimes the system id is not (properly) expanded.
1112         // We need to expand the system id if:
1113         // a. the expanded one was null; or
1114         // b. the base system id was null, but becomes non-null from the current entity.
1115         boolean needExpand = (expandedSystemId == null);
1116         // REVISIT:  why would the baseSystemId ever be null?  if we
1117         // didn't have to make this check we wouldn't have to reuse the
1118         // fXMLResourceIdentifier object...
1119         if (baseSystemId == null && fCurrentEntity != null && fCurrentEntity.entityLocation != null) {
1120             baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId();
1121             if (baseSystemId != null)
1122                 needExpand = true;
1123         }
1124         if (needExpand)
1125             expandedSystemId = expandSystemId(literalSystemId, baseSystemId,false);
1126 
1127         // give the entity resolver a chance
1128         XMLInputSource xmlInputSource = null;
1129 
1130         if (fEntityResolver != null) {
1131             resourceIdentifier.setBaseSystemId(baseSystemId);
1132             resourceIdentifier.setExpandedSystemId(expandedSystemId);
1133             xmlInputSource = fEntityResolver.resolveEntity(resourceIdentifier);
1134         }
1135 
1136         if (xmlInputSource == null && fUseCatalog) {
1137             if (fCatalogFeatures == null) {
1138                 fCatalogFeatures = JdkXmlUtils.getCatalogFeatures(fDefer, fCatalogFile, fPrefer, fResolve);
1139             }
1140             fCatalogFile = fCatalogFeatures.get(Feature.FILES);
1141             if (fCatalogFile != null) {
1142                 /*
1143                  since the method can be called from various processors, both
1144                  EntityResolver and URIResolver are used to attempt to find
1145                  a match
1146                 */
1147                 InputSource is = null;
1148                 try {
1149                     if (fCatalogResolver == null) {
1150                         fCatalogResolver = CatalogManager.catalogResolver(fCatalogFeatures);
1151                     }
1152                     String pid = (publicId != null? publicId : resourceIdentifier.getNamespace());
1153                     if (pid != null || literalSystemId != null) {
1154                         is = fCatalogResolver.resolveEntity(pid, literalSystemId);
1155                     }
1156                 } catch (CatalogException e) {}
1157 
1158                 if (is != null && !is.isEmpty()) {
1159                     xmlInputSource = new XMLInputSource(is, true);
1160                 } else if (literalSystemId != null) {
1161                     if (fCatalogResolver == null) {
1162                         fCatalogResolver = CatalogManager.catalogResolver(fCatalogFeatures);
1163                     }
1164 
1165                     Source source = null;
1166                     try {
1167                         source = fCatalogResolver.resolve(literalSystemId, baseSystemId);
1168                     } catch (CatalogException e) {
1169                         throw new XNIException(e);
1170                     }
1171                     if (source != null && !source.isEmpty()) {
1172                         xmlInputSource = new XMLInputSource(publicId, source.getSystemId(), baseSystemId, true);
1173                     }
1174                 }
1175             }
1176         }
1177 
1178         // do default resolution
1179         // REVISIT: what's the correct behavior if the user provided an entity
1180         // resolver (fEntityResolver != null), but resolveEntity doesn't return
1181         // an input source (xmlInputSource == null)?
1182         // do we do default resolution, or do we just return null? -SG
1183         if (xmlInputSource == null) {
1184             // REVISIT: when systemId is null, I think we should return null.
1185             //          is this the right solution? -SG
1186             //if (systemId != null)
1187             xmlInputSource = new XMLInputSource(publicId, literalSystemId, baseSystemId, false);
1188         }
1189 
1190         if (DEBUG_RESOLVER) {
1191             System.err.println("XMLEntityManager.resolveEntity(" + publicId + ")");
1192             System.err.println(" = " + xmlInputSource);
1193         }
1194 
1195         return xmlInputSource;
1196 
1197     } // resolveEntity(XMLResourceIdentifier):XMLInputSource
1198 
1199     /**
1200      * Starts a named entity.
1201      *
1202      * @param isGE flag to indicate whether the entity is a General Entity
1203      * @param entityName The name of the entity to start.
1204      * @param literal    True if this entity is started within a literal
1205      *                   value.
1206      *
1207      * @throws IOException  Thrown on i/o error.
1208      * @throws XNIException Thrown by entity handler to signal an error.
1209      */
1210     public void startEntity(boolean isGE, String entityName, boolean literal)
1211     throws IOException, XNIException {
1212 
1213         // was entity declared?
1214         Entity entity = fEntityStorage.getEntity(entityName);
1215         if (entity == null) {
1216             if (fEntityHandler != null) {
1217                 String encoding = null;
1218                 fResourceIdentifier.clear();
1219                 fEntityAugs.removeAllItems();
1220                 fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1221                 fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs);
1222                 fEntityAugs.removeAllItems();
1223                 fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1224                 fEntityHandler.endEntity(entityName, fEntityAugs);
1225             }
1226             return;
1227         }
1228 
1229         // should we skip external entities?
1230         boolean external = entity.isExternal();
1231         Entity.ExternalEntity externalEntity = null;
1232         String extLitSysId = null, extBaseSysId = null, expandedSystemId = null;
1233         if (external) {
1234             externalEntity = (Entity.ExternalEntity)entity;
1235             extLitSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getLiteralSystemId() : null);
1236             extBaseSysId = (externalEntity.entityLocation != null ? externalEntity.entityLocation.getBaseSystemId() : null);
1237             expandedSystemId = expandSystemId(extLitSysId, extBaseSysId);
1238             boolean unparsed = entity.isUnparsed();
1239             boolean parameter = entityName.startsWith("%");
1240             boolean general = !parameter;
1241             if (unparsed || (general && !fExternalGeneralEntities) ||
1242                     (parameter && !fExternalParameterEntities) ||
1243                     !fSupportDTD || !fSupportExternalEntities) {
1244 
1245                 if (fEntityHandler != null) {
1246                     fResourceIdentifier.clear();
1247                     final String encoding = null;
1248                     fResourceIdentifier.setValues(
1249                             (externalEntity.entityLocation != null ? externalEntity.entityLocation.getPublicId() : null),
1250                             extLitSysId, extBaseSysId, expandedSystemId);
1251                     fEntityAugs.removeAllItems();
1252                     fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1253                     fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs);
1254                     fEntityAugs.removeAllItems();
1255                     fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1256                     fEntityHandler.endEntity(entityName, fEntityAugs);
1257                 }
1258                 return;
1259             }
1260         }
1261 
1262         // is entity recursive?
1263         int size = fEntityStack.size();
1264         for (int i = size; i >= 0; i--) {
1265             Entity activeEntity = i == size
1266                     ? fCurrentEntity
1267                     : fEntityStack.get(i);
1268             if (activeEntity.name == entityName) {
1269                 String path = entityName;
1270                 for (int j = i + 1; j < size; j++) {
1271                     activeEntity = fEntityStack.get(j);
1272                     path = path + " -> " + activeEntity.name;
1273                 }
1274                 path = path + " -> " + fCurrentEntity.name;
1275                 path = path + " -> " + entityName;
1276                 fErrorReporter.reportError(this.getEntityScanner(),XMLMessageFormatter.XML_DOMAIN,
1277                         "RecursiveReference",
1278                         new Object[] { entityName, path },
1279                         XMLErrorReporter.SEVERITY_FATAL_ERROR);
1280 
1281                         if (fEntityHandler != null) {
1282                             fResourceIdentifier.clear();
1283                             final String encoding = null;
1284                             if (external) {
1285                                 fResourceIdentifier.setValues(
1286                                         (externalEntity.entityLocation != null ? externalEntity.entityLocation.getPublicId() : null),
1287                                         extLitSysId, extBaseSysId, expandedSystemId);
1288                             }
1289                             fEntityAugs.removeAllItems();
1290                             fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1291                             fEntityHandler.startEntity(entityName, fResourceIdentifier, encoding, fEntityAugs);
1292                             fEntityAugs.removeAllItems();
1293                             fEntityAugs.putItem(Constants.ENTITY_SKIPPED, Boolean.TRUE);
1294                             fEntityHandler.endEntity(entityName, fEntityAugs);
1295                         }
1296 
1297                         return;
1298             }
1299         }
1300 
1301         // resolve external entity
1302         StaxXMLInputSource staxInputSource = null;
1303         XMLInputSource xmlInputSource = null ;
1304 
1305         if (external) {
1306             staxInputSource = resolveEntityAsPerStax(externalEntity.entityLocation);
1307             /** xxx:  Waiting from the EG
1308              * //simply return if there was entity resolver registered and application
1309              * //returns either XMLStreamReader or XMLEventReader.
1310              * if(staxInputSource.hasXMLStreamOrXMLEventReader()) return ;
1311              */
1312             xmlInputSource = staxInputSource.getXMLInputSource() ;
1313             if (!fISCreatedByResolver) {
1314                 //let the not-LoadExternalDTD or not-SupportDTD process to handle the situation
1315                 if (fLoadExternalDTD) {
1316                     String accessError = SecuritySupport.checkAccess(expandedSystemId, fAccessExternalDTD, Constants.ACCESS_EXTERNAL_ALL);
1317                     if (accessError != null) {
1318                         fErrorReporter.reportError(this.getEntityScanner(),XMLMessageFormatter.XML_DOMAIN,
1319                                 "AccessExternalEntity",
1320                                 new Object[] { SecuritySupport.sanitizePath(expandedSystemId), accessError },
1321                                 XMLErrorReporter.SEVERITY_FATAL_ERROR);
1322                     }
1323                 }
1324             }
1325         }
1326         // wrap internal entity
1327         else {
1328             Entity.InternalEntity internalEntity = (Entity.InternalEntity)entity;
1329             Reader reader = new StringReader(internalEntity.text);
1330             xmlInputSource = new XMLInputSource(null, null, null, reader, null);
1331         }
1332 
1333         // start the entity
1334         startEntity(isGE, entityName, xmlInputSource, literal, external);
1335 
1336     } // startEntity(String,boolean)
1337 
1338     /**
1339      * Starts the document entity. The document entity has the "[xml]"
1340      * pseudo-name.
1341      *
1342      * @param xmlInputSource The input source of the document entity.
1343      *
1344      * @throws IOException  Thrown on i/o error.
1345      * @throws XNIException Thrown by entity handler to signal an error.
1346      */
1347     public void startDocumentEntity(XMLInputSource xmlInputSource)
1348     throws IOException, XNIException {
1349         startEntity(false, XMLEntity, xmlInputSource, false, true);
1350     } // startDocumentEntity(XMLInputSource)
1351 
1352     //xxx these methods are not required.
1353     /**
1354      * Starts the DTD entity. The DTD entity has the "[dtd]"
1355      * pseudo-name.
1356      *
1357      * @param xmlInputSource The input source of the DTD entity.
1358      *
1359      * @throws IOException  Thrown on i/o error.
1360      * @throws XNIException Thrown by entity handler to signal an error.
1361      */
1362     public void startDTDEntity(XMLInputSource xmlInputSource)
1363     throws IOException, XNIException {
1364         startEntity(false, DTDEntity, xmlInputSource, false, true);
1365     } // startDTDEntity(XMLInputSource)
1366 
1367     // indicate start of external subset so that
1368     // location of entity decls can be tracked
1369     public void startExternalSubset() {
1370         fInExternalSubset = true;
1371     }
1372 
1373     public void endExternalSubset() {
1374         fInExternalSubset = false;
1375     }
1376 
1377     /**
1378      * Starts an entity.
1379      * <p>
1380      * This method can be used to insert an application defined XML
1381      * entity stream into the parsing stream.
1382      *
1383      * @param isGE flag to indicate whether the entity is a General Entity
1384      * @param name           The name of the entity.
1385      * @param xmlInputSource The input source of the entity.
1386      * @param literal        True if this entity is started within a
1387      *                       literal value.
1388      * @param isExternal    whether this entity should be treated as an internal or external entity.
1389      *
1390      * @throws IOException  Thrown on i/o error.
1391      * @throws XNIException Thrown by entity handler to signal an error.
1392      */
1393     public void startEntity(boolean isGE, String name,
1394             XMLInputSource xmlInputSource,
1395             boolean literal, boolean isExternal)
1396             throws IOException, XNIException {
1397 
1398         String encoding = setupCurrentEntity(isGE, name, xmlInputSource, literal, isExternal);
1399 
1400         //when entity expansion limit is set by the Application, we need to
1401         //check for the entity expansion limit set by the parser, if number of entity
1402         //expansions exceeds the entity expansion limit, parser will throw fatal error.
1403         // Note that this represents the nesting level of open entities.
1404         fEntityExpansionCount++;
1405         if(fLimitAnalyzer != null) {
1406            fLimitAnalyzer.addValue(entityExpansionIndex, name, 1);
1407         }
1408         if( fSecurityManager != null && fSecurityManager.isOverLimit(entityExpansionIndex, fLimitAnalyzer)){
1409             fSecurityManager.debugPrint(fLimitAnalyzer);
1410             fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,"EntityExpansionLimit",
1411                     new Object[]{fSecurityManager.getLimitValueByIndex(entityExpansionIndex)},
1412                                              XMLErrorReporter.SEVERITY_FATAL_ERROR );
1413             // is there anything better to do than reset the counter?
1414             // at least one can envision debugging applications where this might
1415             // be useful...
1416             fEntityExpansionCount = 0;
1417         }
1418 
1419         // call handler
1420         if (fEntityHandler != null) {
1421             fEntityHandler.startEntity(name, fResourceIdentifier, encoding, null);
1422         }
1423 
1424     } // startEntity(String,XMLInputSource)
1425 
1426     /**
1427      * Return the current entity being scanned. Current entity is SET using startEntity function.
1428      * @return Entity.ScannedEntity
1429      */
1430 
1431     public Entity.ScannedEntity getCurrentEntity(){
1432         return fCurrentEntity ;
1433     }
1434 
1435     /**
1436      * Return the top level entity handled by this manager, or null
1437      * if no entity was added.
1438      */
1439     public Entity.ScannedEntity getTopLevelEntity() {
1440         return (Entity.ScannedEntity)
1441             (fEntityStack.empty() ? null : fEntityStack.get(0));
1442     }
1443 
1444     // A stack containing all the open readers
1445     protected Stack<Reader> fReaderStack = new Stack<>();
1446 
1447     /**
1448      * Close all opened InputStreams and Readers opened by this parser.
1449      */
1450     public void closeReaders() {
1451         // close all readers
1452         while (!fReaderStack.isEmpty()) {
1453             try {
1454                 (fReaderStack.pop()).close();
1455             } catch (IOException e) {
1456                 // ignore
1457             }
1458         }
1459     }
1460 
1461     public void endEntity() throws IOException, XNIException {
1462 
1463         // call handler
1464         if (DEBUG_BUFFER) {
1465             System.out.print("(endEntity: ");
1466             print();
1467             System.out.println();
1468         }
1469         //pop the entity from the stack
1470         Entity.ScannedEntity entity = fEntityStack.size() > 0 ? (Entity.ScannedEntity)fEntityStack.pop() : null ;
1471 
1472         /** need to close the reader first since the program can end
1473          *  prematurely (e.g. fEntityHandler.endEntity may throw exception)
1474          *  leaving the reader open
1475          */
1476         //close the reader
1477         if(fCurrentEntity != null){
1478             //close the reader
1479             try{
1480                 if (fLimitAnalyzer != null) {
1481                     fLimitAnalyzer.endEntity(XMLSecurityManager.Limit.GENERAL_ENTITY_SIZE_LIMIT, fCurrentEntity.name);
1482                     if (fCurrentEntity.name.equals("[xml]")) {
1483                         fSecurityManager.debugPrint(fLimitAnalyzer);
1484                     }
1485                 }
1486                 fCurrentEntity.close();
1487             }catch(IOException ex){
1488                 throw new XNIException(ex);
1489             }
1490         }
1491 
1492         // REVISIT: We should never encounter underflow if the calls
1493         // to startEntity and endEntity are balanced, but guard
1494         // against the EmptyStackException for now. -- mrglavas
1495         if (!fReaderStack.isEmpty()) {
1496             fReaderStack.pop();
1497         }
1498 
1499         if (fEntityHandler != null) {
1500             //so this is the last opened entity, signal it to current fEntityHandler using Augmentation
1501             if(entity == null){
1502                 fEntityAugs.removeAllItems();
1503                 fEntityAugs.putItem(Constants.LAST_ENTITY, Boolean.TRUE);
1504                 fEntityHandler.endEntity(fCurrentEntity.name, fEntityAugs);
1505                 fEntityAugs.removeAllItems();
1506             }else{
1507                 fEntityHandler.endEntity(fCurrentEntity.name, null);
1508             }
1509         }
1510         //check if it is a document entity
1511         boolean documentEntity = fCurrentEntity.name == XMLEntity;
1512 
1513         //set popped entity as current entity
1514         fCurrentEntity = entity;
1515         fEntityScanner.setCurrentEntity(fCurrentEntity);
1516 
1517         //check if there are any entity left in the stack -- if there are
1518         //no entries EOF has been reached.
1519         // throw exception when it is the last entity but it is not a document entity
1520 
1521         if(fCurrentEntity == null & !documentEntity){
1522             throw new EOFException() ;
1523         }
1524 
1525         if (DEBUG_BUFFER) {
1526             System.out.print(")endEntity: ");
1527             print();
1528             System.out.println();
1529         }
1530 
1531     } // endEntity()
1532 
1533 
1534     //
1535     // XMLComponent methods
1536     //
1537     public void reset(PropertyManager propertyManager){
1538         // xerces properties
1539         fSymbolTable = (SymbolTable)propertyManager.getProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.SYMBOL_TABLE_PROPERTY);
1540         fErrorReporter = (XMLErrorReporter)propertyManager.getProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY);
1541         try {
1542             fStaxEntityResolver = (StaxEntityResolverWrapper)propertyManager.getProperty(STAX_ENTITY_RESOLVER);
1543         } catch (XMLConfigurationException e) {
1544             fStaxEntityResolver = null;
1545         }
1546 
1547         fSupportDTD = ((Boolean)propertyManager.getProperty(XMLInputFactory.SUPPORT_DTD));
1548         fReplaceEntityReferences = ((Boolean)propertyManager.getProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES));
1549         fSupportExternalEntities = ((Boolean)propertyManager.getProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES));
1550 
1551         // Zephyr feature ignore-external-dtd is the opposite of Xerces' load-external-dtd
1552         fLoadExternalDTD = !((Boolean)propertyManager.getProperty(Constants.ZEPHYR_PROPERTY_PREFIX + Constants.IGNORE_EXTERNAL_DTD));
1553 
1554         //Use Catalog
1555         fUseCatalog = (Boolean)propertyManager.getProperty(XMLConstants.USE_CATALOG);
1556         fCatalogFile = (String)propertyManager.getProperty(JdkXmlUtils.CATALOG_FILES);
1557         fDefer = (String)propertyManager.getProperty(JdkXmlUtils.CATALOG_DEFER);
1558         fPrefer = (String)propertyManager.getProperty(JdkXmlUtils.CATALOG_PREFER);
1559         fResolve = (String)propertyManager.getProperty(JdkXmlUtils.CATALOG_RESOLVE);
1560 
1561         // JAXP 1.5 feature
1562         XMLSecurityPropertyManager spm = (XMLSecurityPropertyManager) propertyManager.getProperty(XML_SECURITY_PROPERTY_MANAGER);
1563         fAccessExternalDTD = spm.getValue(XMLSecurityPropertyManager.Property.ACCESS_EXTERNAL_DTD);
1564 
1565         fSecurityManager = (XMLSecurityManager)propertyManager.getProperty(SECURITY_MANAGER);
1566 
1567         fLimitAnalyzer = new XMLLimitAnalyzer();
1568         //reset fEntityStorage
1569         fEntityStorage.reset(propertyManager);
1570         //reset XMLEntityReaderImpl
1571         fEntityScanner.reset(propertyManager);
1572 
1573         // initialize state
1574         //fStandalone = false;
1575         fEntities.clear();
1576         fEntityStack.removeAllElements();
1577         fCurrentEntity = null;
1578         fValidation = false;
1579         fExternalGeneralEntities = true;
1580         fExternalParameterEntities = true;
1581         fAllowJavaEncodings = true ;
1582     }
1583 
1584     /**
1585      * Resets the component. The component can query the component manager
1586      * about any features and properties that affect the operation of the
1587      * component.
1588      *
1589      * @param componentManager The component manager.
1590      *
1591      * @throws SAXException Thrown by component on initialization error.
1592      *                      For example, if a feature or property is
1593      *                      required for the operation of the component, the
1594      *                      component manager may throw a
1595      *                      SAXNotRecognizedException or a
1596      *                      SAXNotSupportedException.
1597      */
1598     public void reset(XMLComponentManager componentManager)
1599     throws XMLConfigurationException {
1600 
1601         boolean parser_settings = componentManager.getFeature(PARSER_SETTINGS, true);
1602 
1603         if (!parser_settings) {
1604             // parser settings have not been changed
1605             reset();
1606             if(fEntityScanner != null){
1607                 fEntityScanner.reset(componentManager);
1608             }
1609             if(fEntityStorage != null){
1610                 fEntityStorage.reset(componentManager);
1611             }
1612             return;
1613         }
1614 
1615         // sax features
1616         fValidation = componentManager.getFeature(VALIDATION, false);
1617         fExternalGeneralEntities = componentManager.getFeature(EXTERNAL_GENERAL_ENTITIES, true);
1618         fExternalParameterEntities = componentManager.getFeature(EXTERNAL_PARAMETER_ENTITIES, true);
1619 
1620         // xerces features
1621         fAllowJavaEncodings = componentManager.getFeature(ALLOW_JAVA_ENCODINGS, false);
1622         fWarnDuplicateEntityDef = componentManager.getFeature(WARN_ON_DUPLICATE_ENTITYDEF, false);
1623         fStrictURI = componentManager.getFeature(STANDARD_URI_CONFORMANT, false);
1624         fLoadExternalDTD = componentManager.getFeature(LOAD_EXTERNAL_DTD, true);
1625 
1626         // xerces properties
1627         fSymbolTable = (SymbolTable)componentManager.getProperty(SYMBOL_TABLE);
1628         fErrorReporter = (XMLErrorReporter)componentManager.getProperty(ERROR_REPORTER);
1629         fEntityResolver = (XMLEntityResolver)componentManager.getProperty(ENTITY_RESOLVER, null);
1630         fStaxEntityResolver = (StaxEntityResolverWrapper)componentManager.getProperty(STAX_ENTITY_RESOLVER, null);
1631         fValidationManager = (ValidationManager)componentManager.getProperty(VALIDATION_MANAGER, null);
1632         fSecurityManager = (XMLSecurityManager)componentManager.getProperty(SECURITY_MANAGER, null);
1633         entityExpansionIndex = fSecurityManager.getIndex(Constants.JDK_ENTITY_EXPANSION_LIMIT);
1634 
1635         //StAX Property
1636         fSupportDTD = true;
1637         fReplaceEntityReferences = true;
1638         fSupportExternalEntities = true;
1639 
1640         // JAXP 1.5 feature
1641         XMLSecurityPropertyManager spm = (XMLSecurityPropertyManager) componentManager.getProperty(XML_SECURITY_PROPERTY_MANAGER, null);
1642         if (spm == null) {
1643             spm = new XMLSecurityPropertyManager();
1644         }
1645         fAccessExternalDTD = spm.getValue(XMLSecurityPropertyManager.Property.ACCESS_EXTERNAL_DTD);
1646 
1647         //Use Catalog
1648         fUseCatalog = componentManager.getFeature(XMLConstants.USE_CATALOG, true);
1649         fCatalogFile = (String)componentManager.getProperty(JdkXmlUtils.CATALOG_FILES);
1650         fDefer = (String)componentManager.getProperty(JdkXmlUtils.CATALOG_DEFER);
1651         fPrefer = (String)componentManager.getProperty(JdkXmlUtils.CATALOG_PREFER);
1652         fResolve = (String)componentManager.getProperty(JdkXmlUtils.CATALOG_RESOLVE);
1653 
1654         //reset general state
1655         reset();
1656 
1657         fEntityScanner.reset(componentManager);
1658         fEntityStorage.reset(componentManager);
1659 
1660     } // reset(XMLComponentManager)
1661 
1662     // reset general state.  Should not be called other than by
1663     // a class acting as a component manager but not
1664     // implementing that interface for whatever reason.
1665     public void reset() {
1666         fLimitAnalyzer = new XMLLimitAnalyzer();
1667         // initialize state
1668         fStandalone = false;
1669         fEntities.clear();
1670         fEntityStack.removeAllElements();
1671         fEntityExpansionCount = 0;
1672 
1673         fCurrentEntity = null;
1674         // reset scanner
1675         if(fXML10EntityScanner != null){
1676             fXML10EntityScanner.reset(fSymbolTable, this, fErrorReporter);
1677         }
1678         if(fXML11EntityScanner != null) {
1679             fXML11EntityScanner.reset(fSymbolTable, this, fErrorReporter);
1680         }
1681 
1682         // DEBUG
1683         if (DEBUG_ENTITIES) {
1684             addInternalEntity("text", "Hello, World.");
1685             addInternalEntity("empty-element", "<foo/>");
1686             addInternalEntity("balanced-element", "<foo></foo>");
1687             addInternalEntity("balanced-element-with-text", "<foo>Hello, World</foo>");
1688             addInternalEntity("balanced-element-with-entity", "<foo>&text;</foo>");
1689             addInternalEntity("unbalanced-entity", "<foo>");
1690             addInternalEntity("recursive-entity", "<foo>&recursive-entity2;</foo>");
1691             addInternalEntity("recursive-entity2", "<bar>&recursive-entity3;</bar>");
1692             addInternalEntity("recursive-entity3", "<baz>&recursive-entity;</baz>");
1693             try {
1694                 addExternalEntity("external-text", null, "external-text.ent", "test/external-text.xml");
1695                 addExternalEntity("external-balanced-element", null, "external-balanced-element.ent", "test/external-balanced-element.xml");
1696                 addExternalEntity("one", null, "ent/one.ent", "test/external-entity.xml");
1697                 addExternalEntity("two", null, "ent/two.ent", "test/ent/one.xml");
1698             }
1699             catch (IOException ex) {
1700                 // should never happen
1701             }
1702         }
1703 
1704         fEntityHandler = null;
1705 
1706         // reset scanner
1707         //if(fEntityScanner!=null)
1708           //  fEntityScanner.reset(fSymbolTable, this,fErrorReporter);
1709 
1710     }
1711     /**
1712      * Returns a list of feature identifiers that are recognized by
1713      * this component. This method may return null if no features
1714      * are recognized by this component.
1715      */
1716     public String[] getRecognizedFeatures() {
1717         return RECOGNIZED_FEATURES.clone();
1718     } // getRecognizedFeatures():String[]
1719 
1720     /**
1721      * Sets the state of a feature. This method is called by the component
1722      * manager any time after reset when a feature changes state.
1723      * <p>
1724      * <strong>Note:</strong> Components should silently ignore features
1725      * that do not affect the operation of the component.
1726      *
1727      * @param featureId The feature identifier.
1728      * @param state     The state of the feature.
1729      *
1730      * @throws SAXNotRecognizedException The component should not throw
1731      *                                   this exception.
1732      * @throws SAXNotSupportedException The component should not throw
1733      *                                  this exception.
1734      */
1735     public void setFeature(String featureId, boolean state)
1736     throws XMLConfigurationException {
1737 
1738         // xerces features
1739         if (featureId.startsWith(Constants.XERCES_FEATURE_PREFIX)) {
1740             final int suffixLength = featureId.length() - Constants.XERCES_FEATURE_PREFIX.length();
1741             if (suffixLength == Constants.ALLOW_JAVA_ENCODINGS_FEATURE.length() &&
1742                 featureId.endsWith(Constants.ALLOW_JAVA_ENCODINGS_FEATURE)) {
1743                 fAllowJavaEncodings = state;
1744             }
1745             if (suffixLength == Constants.LOAD_EXTERNAL_DTD_FEATURE.length() &&
1746                 featureId.endsWith(Constants.LOAD_EXTERNAL_DTD_FEATURE)) {
1747                 fLoadExternalDTD = state;
1748                 return;
1749             }
1750         } else if (featureId.equals(XMLConstants.USE_CATALOG)) {
1751             fUseCatalog = state;
1752         }
1753 
1754     } // setFeature(String,boolean)
1755 
1756     /**
1757      * Sets the value of a property. This method is called by the component
1758      * manager any time after reset when a property changes value.
1759      * <p>
1760      * <strong>Note:</strong> Components should silently ignore properties
1761      * that do not affect the operation of the component.
1762      *
1763      * @param propertyId The property identifier.
1764      * @param value      The value of the property.
1765      *
1766      * @throws SAXNotRecognizedException The component should not throw
1767      *                                   this exception.
1768      * @throws SAXNotSupportedException The component should not throw
1769      *                                  this exception.
1770      */
1771     public void setProperty(String propertyId, Object value){
1772         // Xerces properties
1773         if (propertyId.startsWith(Constants.XERCES_PROPERTY_PREFIX)) {
1774             final int suffixLength = propertyId.length() - Constants.XERCES_PROPERTY_PREFIX.length();
1775 
1776             if (suffixLength == Constants.SYMBOL_TABLE_PROPERTY.length() &&
1777                 propertyId.endsWith(Constants.SYMBOL_TABLE_PROPERTY)) {
1778                 fSymbolTable = (SymbolTable)value;
1779                 return;
1780             }
1781             if (suffixLength == Constants.ERROR_REPORTER_PROPERTY.length() &&
1782                 propertyId.endsWith(Constants.ERROR_REPORTER_PROPERTY)) {
1783                 fErrorReporter = (XMLErrorReporter)value;
1784                 return;
1785             }
1786             if (suffixLength == Constants.ENTITY_RESOLVER_PROPERTY.length() &&
1787                 propertyId.endsWith(Constants.ENTITY_RESOLVER_PROPERTY)) {
1788                 fEntityResolver = (XMLEntityResolver)value;
1789                 return;
1790             }
1791             if (suffixLength == Constants.BUFFER_SIZE_PROPERTY.length() &&
1792                 propertyId.endsWith(Constants.BUFFER_SIZE_PROPERTY)) {
1793                 Integer bufferSize = (Integer)value;
1794                 if (bufferSize != null &&
1795                     bufferSize.intValue() > DEFAULT_XMLDECL_BUFFER_SIZE) {
1796                     fBufferSize = bufferSize.intValue();
1797                     fEntityScanner.setBufferSize(fBufferSize);
1798                 }
1799             }
1800             if (suffixLength == Constants.SECURITY_MANAGER_PROPERTY.length() &&
1801                 propertyId.endsWith(Constants.SECURITY_MANAGER_PROPERTY)) {
1802                 fSecurityManager = (XMLSecurityManager)value;
1803             }
1804         }
1805 
1806         //JAXP 1.5 properties
1807         if (propertyId.equals(XML_SECURITY_PROPERTY_MANAGER))
1808         {
1809             XMLSecurityPropertyManager spm = (XMLSecurityPropertyManager)value;
1810             fAccessExternalDTD = spm.getValue(XMLSecurityPropertyManager.Property.ACCESS_EXTERNAL_DTD);
1811             return;
1812         }
1813 
1814         //Catalog properties
1815         if (propertyId.equals(JdkXmlUtils.CATALOG_FILES)) {
1816             fCatalogFile = (String)value;
1817         } else if (propertyId.equals(JdkXmlUtils.CATALOG_DEFER)) {
1818             fDefer = (String)value;
1819         } else if (propertyId.equals(JdkXmlUtils.CATALOG_PREFER)) {
1820             fPrefer = (String)value;
1821         } else if (propertyId.equals(JdkXmlUtils.CATALOG_RESOLVE)) {
1822             fResolve = (String)value;
1823         }
1824     }
1825 
1826     public void setLimitAnalyzer(XMLLimitAnalyzer fLimitAnalyzer) {
1827         this.fLimitAnalyzer = fLimitAnalyzer;
1828     }
1829 
1830     /**
1831      * Returns a list of property identifiers that are recognized by
1832      * this component. This method may return null if no properties
1833      * are recognized by this component.
1834      */
1835     public String[] getRecognizedProperties() {
1836         return RECOGNIZED_PROPERTIES.clone();
1837     } // getRecognizedProperties():String[]
1838     /**
1839      * Returns the default state for a feature, or null if this
1840      * component does not want to report a default value for this
1841      * feature.
1842      *
1843      * @param featureId The feature identifier.
1844      *
1845      * @since Xerces 2.2.0
1846      */
1847     public Boolean getFeatureDefault(String featureId) {
1848         for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
1849             if (RECOGNIZED_FEATURES[i].equals(featureId)) {
1850                 return FEATURE_DEFAULTS[i];
1851             }
1852         }
1853         return null;
1854     } // getFeatureDefault(String):Boolean
1855 
1856     /**
1857      * Returns the default state for a property, or null if this
1858      * component does not want to report a default value for this
1859      * property.
1860      *
1861      * @param propertyId The property identifier.
1862      *
1863      * @since Xerces 2.2.0
1864      */
1865     public Object getPropertyDefault(String propertyId) {
1866         for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
1867             if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
1868                 return PROPERTY_DEFAULTS[i];
1869             }
1870         }
1871         return null;
1872     } // getPropertyDefault(String):Object
1873 
1874     //
1875     // Public static methods
1876     //
1877 
1878     /**
1879      * Expands a system id and returns the system id as a URI, if
1880      * it can be expanded. A return value of null means that the
1881      * identifier is already expanded. An exception thrown
1882      * indicates a failure to expand the id.
1883      *
1884      * @param systemId The systemId to be expanded.
1885      *
1886      * @return Returns the URI string representing the expanded system
1887      *         identifier. A null value indicates that the given
1888      *         system identifier is already expanded.
1889      *
1890      */
1891     public static String expandSystemId(String systemId) {
1892         return expandSystemId(systemId, null);
1893     } // expandSystemId(String):String
1894 
1895     //
1896     // Public static methods
1897     //
1898 
1899     // current value of the "user.dir" property
1900     private static String gUserDir;
1901     // cached URI object for the current value of the escaped "user.dir" property stored as a URI
1902     private static URI gUserDirURI;
1903     // which ASCII characters need to be escaped
1904     private static boolean gNeedEscaping[] = new boolean[128];
1905     // the first hex character if a character needs to be escaped
1906     private static char gAfterEscaping1[] = new char[128];
1907     // the second hex character if a character needs to be escaped
1908     private static char gAfterEscaping2[] = new char[128];
1909     private static char[] gHexChs = {'0', '1', '2', '3', '4', '5', '6', '7',
1910                                      '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
1911     // initialize the above 3 arrays
1912     static {
1913         for (int i = 0; i <= 0x1f; i++) {
1914             gNeedEscaping[i] = true;
1915             gAfterEscaping1[i] = gHexChs[i >> 4];
1916             gAfterEscaping2[i] = gHexChs[i & 0xf];
1917         }
1918         gNeedEscaping[0x7f] = true;
1919         gAfterEscaping1[0x7f] = '7';
1920         gAfterEscaping2[0x7f] = 'F';
1921         char[] escChs = {' ', '<', '>', '#', '%', '"', '{', '}',
1922                          '|', '\\', '^', '~', '[', ']', '`'};
1923         int len = escChs.length;
1924         char ch;
1925         for (int i = 0; i < len; i++) {
1926             ch = escChs[i];
1927             gNeedEscaping[ch] = true;
1928             gAfterEscaping1[ch] = gHexChs[ch >> 4];
1929             gAfterEscaping2[ch] = gHexChs[ch & 0xf];
1930         }
1931     }
1932 
1933     // To escape the "user.dir" system property, by using %HH to represent
1934     // special ASCII characters: 0x00~0x1F, 0x7F, ' ', '<', '>', '#', '%'
1935     // and '"'. It's a static method, so needs to be synchronized.
1936     // this method looks heavy, but since the system property isn't expected
1937     // to change often, so in most cases, we only need to return the URI
1938     // that was escaped before.
1939     // According to the URI spec, non-ASCII characters (whose value >= 128)
1940     // need to be escaped too.
1941     // REVISIT: don't know how to escape non-ASCII characters, especially
1942     // which encoding to use. Leave them for now.
1943     private static synchronized URI getUserDir() throws URI.MalformedURIException {
1944         // get the user.dir property
1945         String userDir = "";
1946         try {
1947             userDir = SecuritySupport.getSystemProperty("user.dir");
1948         }
1949         catch (SecurityException se) {
1950         }
1951 
1952         // return empty string if property value is empty string.
1953         if (userDir.length() == 0)
1954             return new URI("file", "", "", null, null);
1955         // compute the new escaped value if the new property value doesn't
1956         // match the previous one
1957         if (gUserDirURI != null && userDir.equals(gUserDir)) {
1958             return gUserDirURI;
1959         }
1960 
1961         // record the new value as the global property value
1962         gUserDir = userDir;
1963 
1964         char separator = java.io.File.separatorChar;
1965         userDir = userDir.replace(separator, '/');
1966 
1967         int len = userDir.length(), ch;
1968         StringBuilder buffer = new StringBuilder(len*3);
1969         // change C:/blah to /C:/blah
1970         if (len >= 2 && userDir.charAt(1) == ':') {
1971             ch = Character.toUpperCase(userDir.charAt(0));
1972             if (ch >= 'A' && ch <= 'Z') {
1973                 buffer.append('/');
1974             }
1975         }
1976 
1977         // for each character in the path
1978         int i = 0;
1979         for (; i < len; i++) {
1980             ch = userDir.charAt(i);
1981             // if it's not an ASCII character, break here, and use UTF-8 encoding
1982             if (ch >= 128)
1983                 break;
1984             if (gNeedEscaping[ch]) {
1985                 buffer.append('%');
1986                 buffer.append(gAfterEscaping1[ch]);
1987                 buffer.append(gAfterEscaping2[ch]);
1988                 // record the fact that it's escaped
1989             }
1990             else {
1991                 buffer.append((char)ch);
1992             }
1993         }
1994 
1995         // we saw some non-ascii character
1996         if (i < len) {
1997             // get UTF-8 bytes for the remaining sub-string
1998             byte[] bytes = null;
1999             byte b;
2000             try {
2001                 bytes = userDir.substring(i).getBytes("UTF-8");
2002             } catch (java.io.UnsupportedEncodingException e) {
2003                 // should never happen
2004                 return new URI("file", "", userDir, null, null);
2005             }
2006             len = bytes.length;
2007 
2008             // for each byte
2009             for (i = 0; i < len; i++) {
2010                 b = bytes[i];
2011                 // for non-ascii character: make it positive, then escape
2012                 if (b < 0) {
2013                     ch = b + 256;
2014                     buffer.append('%');
2015                     buffer.append(gHexChs[ch >> 4]);
2016                     buffer.append(gHexChs[ch & 0xf]);
2017                 }
2018                 else if (gNeedEscaping[b]) {
2019                     buffer.append('%');
2020                     buffer.append(gAfterEscaping1[b]);
2021                     buffer.append(gAfterEscaping2[b]);
2022                 }
2023                 else {
2024                     buffer.append((char)b);
2025                 }
2026             }
2027         }
2028 
2029         // change blah/blah to blah/blah/
2030         if (!userDir.endsWith("/"))
2031             buffer.append('/');
2032 
2033         gUserDirURI = new URI("file", "", buffer.toString(), null, null);
2034 
2035         return gUserDirURI;
2036     }
2037 
2038     public static OutputStream createOutputStream(String uri) throws IOException {
2039         // URI was specified. Handle relative URIs.
2040         final String expanded = XMLEntityManager.expandSystemId(uri, null, true);
2041         final URL url = new URL(expanded != null ? expanded : uri);
2042         OutputStream out = null;
2043         String protocol = url.getProtocol();
2044         String host = url.getHost();
2045         // Use FileOutputStream if this URI is for a local file.
2046         if (protocol.equals("file")
2047                 && (host == null || host.length() == 0 || host.equals("localhost"))) {
2048             File file = new File(getPathWithoutEscapes(url.getPath()));
2049             if (!file.exists()) {
2050                 File parent = file.getParentFile();
2051                 if (parent != null && !parent.exists()) {
2052                     parent.mkdirs();
2053                 }
2054             }
2055             out = new FileOutputStream(file);
2056         }
2057         // Try to write to some other kind of URI. Some protocols
2058         // won't support this, though HTTP should work.
2059         else {
2060             URLConnection urlCon = url.openConnection();
2061             urlCon.setDoInput(false);
2062             urlCon.setDoOutput(true);
2063             urlCon.setUseCaches(false); // Enable tunneling.
2064             if (urlCon instanceof HttpURLConnection) {
2065                 // The DOM L3 REC says if we are writing to an HTTP URI
2066                 // it is to be done with an HTTP PUT.
2067                 HttpURLConnection httpCon = (HttpURLConnection) urlCon;
2068                 httpCon.setRequestMethod("PUT");
2069             }
2070             out = urlCon.getOutputStream();
2071         }
2072         return out;
2073     }
2074 
2075     private static String getPathWithoutEscapes(String origPath) {
2076         if (origPath != null && origPath.length() != 0 && origPath.indexOf('%') != -1) {
2077             // Locate the escape characters
2078             StringTokenizer tokenizer = new StringTokenizer(origPath, "%");
2079             StringBuilder result = new StringBuilder(origPath.length());
2080             int size = tokenizer.countTokens();
2081             result.append(tokenizer.nextToken());
2082             for(int i = 1; i < size; ++i) {
2083                 String token = tokenizer.nextToken();
2084                 // Decode the 2 digit hexadecimal number following % in '%nn'
2085                 result.append((char)Integer.valueOf(token.substring(0, 2), 16).intValue());
2086                 result.append(token.substring(2));
2087             }
2088             return result.toString();
2089         }
2090         return origPath;
2091     }
2092 
2093     /**
2094      * Absolutizes a URI using the current value
2095      * of the "user.dir" property as the base URI. If
2096      * the URI is already absolute, this is a no-op.
2097      *
2098      * @param uri the URI to absolutize
2099      */
2100     public static void absolutizeAgainstUserDir(URI uri)
2101         throws URI.MalformedURIException {
2102         uri.absolutize(getUserDir());
2103     }
2104 
2105     /**
2106      * Expands a system id and returns the system id as a URI, if
2107      * it can be expanded. A return value of null means that the
2108      * identifier is already expanded. An exception thrown
2109      * indicates a failure to expand the id.
2110      *
2111      * @param systemId The systemId to be expanded.
2112      *
2113      * @return Returns the URI string representing the expanded system
2114      *         identifier. A null value indicates that the given
2115      *         system identifier is already expanded.
2116      *
2117      */
2118     public static String expandSystemId(String systemId, String baseSystemId) {
2119 
2120         // check for bad parameters id
2121         if (systemId == null || systemId.length() == 0) {
2122             return systemId;
2123         }
2124         // if id already expanded, return
2125         try {
2126             URI uri = new URI(systemId);
2127             if (uri != null) {
2128                 return systemId;
2129             }
2130         } catch (URI.MalformedURIException e) {
2131             // continue on...
2132         }
2133         // normalize id
2134         String id = fixURI(systemId);
2135 
2136         // normalize base
2137         URI base = null;
2138         URI uri = null;
2139         try {
2140             if (baseSystemId == null || baseSystemId.length() == 0 ||
2141                     baseSystemId.equals(systemId)) {
2142                 String dir = getUserDir().toString();
2143                 base = new URI("file", "", dir, null, null);
2144             } else {
2145                 try {
2146                     base = new URI(fixURI(baseSystemId));
2147                 } catch (URI.MalformedURIException e) {
2148                     if (baseSystemId.indexOf(':') != -1) {
2149                         // for xml schemas we might have baseURI with
2150                         // a specified drive
2151                         base = new URI("file", "", fixURI(baseSystemId), null, null);
2152                     } else {
2153                         String dir = getUserDir().toString();
2154                         dir = dir + fixURI(baseSystemId);
2155                         base = new URI("file", "", dir, null, null);
2156                     }
2157                 }
2158             }
2159             // expand id
2160             uri = new URI(base, id);
2161         } catch (Exception e) {
2162             // let it go through
2163 
2164         }
2165 
2166         if (uri == null) {
2167             return systemId;
2168         }
2169         return uri.toString();
2170 
2171     } // expandSystemId(String,String):String
2172 
2173     /**
2174      * Expands a system id and returns the system id as a URI, if
2175      * it can be expanded. A return value of null means that the
2176      * identifier is already expanded. An exception thrown
2177      * indicates a failure to expand the id.
2178      *
2179      * @param systemId The systemId to be expanded.
2180      *
2181      * @return Returns the URI string representing the expanded system
2182      *         identifier. A null value indicates that the given
2183      *         system identifier is already expanded.
2184      *
2185      */
2186     public static String expandSystemId(String systemId, String baseSystemId,
2187                                         boolean strict)
2188             throws URI.MalformedURIException {
2189 
2190         // check if there is a system id before
2191         // trying to expand it.
2192         if (systemId == null) {
2193             return null;
2194         }
2195 
2196         // system id has to be a valid URI
2197         if (strict) {
2198             try {
2199                 // if it's already an absolute one, return it
2200                 new URI(systemId);
2201                 return systemId;
2202             }
2203             catch (URI.MalformedURIException ex) {
2204             }
2205             URI base = null;
2206             // if there isn't a base uri, use the working directory
2207             if (baseSystemId == null || baseSystemId.length() == 0) {
2208                 base = new URI("file", "", getUserDir().toString(), null, null);
2209             }
2210             // otherwise, use the base uri
2211             else {
2212                 try {
2213                     base = new URI(baseSystemId);
2214                 }
2215                 catch (URI.MalformedURIException e) {
2216                     // assume "base" is also a relative uri
2217                     String dir = getUserDir().toString();
2218                     dir = dir + baseSystemId;
2219                     base = new URI("file", "", dir, null, null);
2220                 }
2221             }
2222             // absolutize the system id using the base
2223             URI uri = new URI(base, systemId);
2224             // return the string rep of the new uri (an absolute one)
2225             return uri.toString();
2226 
2227             // if any exception is thrown, it'll get thrown to the caller.
2228         }
2229 
2230         // Assume the URIs are well-formed. If it turns out they're not, try fixing them up.
2231         try {
2232              return expandSystemIdStrictOff(systemId, baseSystemId);
2233         }
2234         catch (URI.MalformedURIException e) {
2235             /** Xerces URI rejects unicode, try java.net.URI
2236              * this is not ideal solution, but it covers known cases which either
2237              * Xerces URI or java.net.URI can handle alone
2238              * will file bug against java.net.URI
2239              */
2240             try {
2241                 return expandSystemIdStrictOff1(systemId, baseSystemId);
2242             } catch (URISyntaxException ex) {
2243                 // continue on...
2244             }
2245         }
2246         // check for bad parameters id
2247         if (systemId.length() == 0) {
2248             return systemId;
2249         }
2250 
2251         // normalize id
2252         String id = fixURI(systemId);
2253 
2254         // normalize base
2255         URI base = null;
2256         URI uri = null;
2257         try {
2258             if (baseSystemId == null || baseSystemId.length() == 0 ||
2259                 baseSystemId.equals(systemId)) {
2260                 base = getUserDir();
2261             }
2262             else {
2263                 try {
2264                     base = new URI(fixURI(baseSystemId).trim());
2265                 }
2266                 catch (URI.MalformedURIException e) {
2267                     if (baseSystemId.indexOf(':') != -1) {
2268                         // for xml schemas we might have baseURI with
2269                         // a specified drive
2270                         base = new URI("file", "", fixURI(baseSystemId).trim(), null, null);
2271                     }
2272                     else {
2273                         base = new URI(getUserDir(), fixURI(baseSystemId));
2274                     }
2275                 }
2276              }
2277              // expand id
2278              uri = new URI(base, id.trim());
2279         }
2280         catch (Exception e) {
2281             // let it go through
2282 
2283         }
2284 
2285         if (uri == null) {
2286             return systemId;
2287         }
2288         return uri.toString();
2289 
2290     } // expandSystemId(String,String,boolean):String
2291 
2292     /**
2293      * Helper method for expandSystemId(String,String,boolean):String
2294      */
2295     private static String expandSystemIdStrictOn(String systemId, String baseSystemId)
2296         throws URI.MalformedURIException {
2297 
2298         URI systemURI = new URI(systemId, true);
2299         // If it's already an absolute one, return it
2300         if (systemURI.isAbsoluteURI()) {
2301             return systemId;
2302         }
2303 
2304         // If there isn't a base URI, use the working directory
2305         URI baseURI = null;
2306         if (baseSystemId == null || baseSystemId.length() == 0) {
2307             baseURI = getUserDir();
2308         }
2309         else {
2310             baseURI = new URI(baseSystemId, true);
2311             if (!baseURI.isAbsoluteURI()) {
2312                 // assume "base" is also a relative uri
2313                 baseURI.absolutize(getUserDir());
2314             }
2315         }
2316 
2317         // absolutize the system identifier using the base URI
2318         systemURI.absolutize(baseURI);
2319 
2320         // return the string rep of the new uri (an absolute one)
2321         return systemURI.toString();
2322 
2323         // if any exception is thrown, it'll get thrown to the caller.
2324 
2325     } // expandSystemIdStrictOn(String,String):String
2326 
2327     /**
2328      * Helper method for expandSystemId(String,String,boolean):String
2329      */
2330     private static String expandSystemIdStrictOff(String systemId, String baseSystemId)
2331         throws URI.MalformedURIException {
2332 
2333         URI systemURI = new URI(systemId, true);
2334         // If it's already an absolute one, return it
2335         if (systemURI.isAbsoluteURI()) {
2336             if (systemURI.getScheme().length() > 1) {
2337                 return systemId;
2338             }
2339             /**
2340              * If the scheme's length is only one character,
2341              * it's likely that this was intended as a file
2342              * path. Fixing this up in expandSystemId to
2343              * maintain backwards compatibility.
2344              */
2345             throw new URI.MalformedURIException();
2346         }
2347 
2348         // If there isn't a base URI, use the working directory
2349         URI baseURI = null;
2350         if (baseSystemId == null || baseSystemId.length() == 0) {
2351             baseURI = getUserDir();
2352         }
2353         else {
2354             baseURI = new URI(baseSystemId, true);
2355             if (!baseURI.isAbsoluteURI()) {
2356                 // assume "base" is also a relative uri
2357                 baseURI.absolutize(getUserDir());
2358             }
2359         }
2360 
2361         // absolutize the system identifier using the base URI
2362         systemURI.absolutize(baseURI);
2363 
2364         // return the string rep of the new uri (an absolute one)
2365         return systemURI.toString();
2366 
2367         // if any exception is thrown, it'll get thrown to the caller.
2368 
2369     } // expandSystemIdStrictOff(String,String):String
2370 
2371     private static String expandSystemIdStrictOff1(String systemId, String baseSystemId)
2372         throws URISyntaxException, URI.MalformedURIException {
2373 
2374             java.net.URI systemURI = new java.net.URI(systemId);
2375         // If it's already an absolute one, return it
2376         if (systemURI.isAbsolute()) {
2377             if (systemURI.getScheme().length() > 1) {
2378                 return systemId;
2379             }
2380             /**
2381              * If the scheme's length is only one character,
2382              * it's likely that this was intended as a file
2383              * path. Fixing this up in expandSystemId to
2384              * maintain backwards compatibility.
2385              */
2386             throw new URISyntaxException(systemId, "the scheme's length is only one character");
2387         }
2388 
2389         // If there isn't a base URI, use the working directory
2390         URI baseURI = null;
2391         if (baseSystemId == null || baseSystemId.length() == 0) {
2392             baseURI = getUserDir();
2393         }
2394         else {
2395             baseURI = new URI(baseSystemId, true);
2396             if (!baseURI.isAbsoluteURI()) {
2397                 // assume "base" is also a relative uri
2398                 baseURI.absolutize(getUserDir());
2399             }
2400         }
2401 
2402         // absolutize the system identifier using the base URI
2403 //        systemURI.absolutize(baseURI);
2404         systemURI = (new java.net.URI(baseURI.toString())).resolve(systemURI);
2405 
2406         // return the string rep of the new uri (an absolute one)
2407         return systemURI.toString();
2408 
2409         // if any exception is thrown, it'll get thrown to the caller.
2410 
2411     } // expandSystemIdStrictOff(String,String):String
2412 
2413     //
2414     // Protected methods
2415     //
2416 
2417 
2418     /**
2419      * Returns the IANA encoding name that is auto-detected from
2420      * the bytes specified, with the endian-ness of that encoding where appropriate.
2421      *
2422      * @param b4    The first four bytes of the input.
2423      * @param count The number of bytes actually read.
2424      * @return an instance of EncodingInfo which represents the auto-detected encoding.
2425      */
2426     protected EncodingInfo getEncodingInfo(byte[] b4, int count) {
2427 
2428         if (count < 2) {
2429             return EncodingInfo.UTF_8;
2430         }
2431 
2432         // UTF-16, with BOM
2433         int b0 = b4[0] & 0xFF;
2434         int b1 = b4[1] & 0xFF;
2435         if (b0 == 0xFE && b1 == 0xFF) {
2436             // UTF-16, big-endian
2437             return EncodingInfo.UTF_16_BIG_ENDIAN_WITH_BOM;
2438         }
2439         if (b0 == 0xFF && b1 == 0xFE) {
2440             // UTF-16, little-endian
2441             return EncodingInfo.UTF_16_LITTLE_ENDIAN_WITH_BOM;
2442         }
2443 
2444         // default to UTF-8 if we don't have enough bytes to make a
2445         // good determination of the encoding
2446         if (count < 3) {
2447             return EncodingInfo.UTF_8;
2448         }
2449 
2450         // UTF-8 with a BOM
2451         int b2 = b4[2] & 0xFF;
2452         if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
2453             return EncodingInfo.UTF_8_WITH_BOM;
2454         }
2455 
2456         // default to UTF-8 if we don't have enough bytes to make a
2457         // good determination of the encoding
2458         if (count < 4) {
2459             return EncodingInfo.UTF_8;
2460         }
2461 
2462         // other encodings
2463         int b3 = b4[3] & 0xFF;
2464         if (b0 == 0x00 && b1 == 0x00 && b2 == 0x00 && b3 == 0x3C) {
2465             // UCS-4, big endian (1234)
2466             return EncodingInfo.UCS_4_BIG_ENDIAN;
2467         }
2468         if (b0 == 0x3C && b1 == 0x00 && b2 == 0x00 && b3 == 0x00) {
2469             // UCS-4, little endian (4321)
2470             return EncodingInfo.UCS_4_LITTLE_ENDIAN;
2471         }
2472         if (b0 == 0x00 && b1 == 0x00 && b2 == 0x3C && b3 == 0x00) {
2473             // UCS-4, unusual octet order (2143)
2474             // REVISIT: What should this be?
2475             return EncodingInfo.UCS_4_UNUSUAL_BYTE_ORDER;
2476         }
2477         if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x00) {
2478             // UCS-4, unusual octect order (3412)
2479             // REVISIT: What should this be?
2480             return EncodingInfo.UCS_4_UNUSUAL_BYTE_ORDER;
2481         }
2482         if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) {
2483             // UTF-16, big-endian, no BOM
2484             // (or could turn out to be UCS-2...
2485             // REVISIT: What should this be?
2486             return EncodingInfo.UTF_16_BIG_ENDIAN;
2487         }
2488         if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) {
2489             // UTF-16, little-endian, no BOM
2490             // (or could turn out to be UCS-2...
2491             return EncodingInfo.UTF_16_LITTLE_ENDIAN;
2492         }
2493         if (b0 == 0x4C && b1 == 0x6F && b2 == 0xA7 && b3 == 0x94) {
2494             // EBCDIC
2495             // a la xerces1, return CP037 instead of EBCDIC here
2496             return EncodingInfo.EBCDIC;
2497         }
2498 
2499         // default encoding
2500         return EncodingInfo.UTF_8;
2501 
2502     } // getEncodingName(byte[],int):Object[]
2503 
2504     /**
2505      * Creates a reader capable of reading the given input stream in
2506      * the specified encoding.
2507      *
2508      * @param inputStream  The input stream.
2509      * @param encoding     The encoding name that the input stream is
2510      *                     encoded using. If the user has specified that
2511      *                     Java encoding names are allowed, then the
2512      *                     encoding name may be a Java encoding name;
2513      *                     otherwise, it is an ianaEncoding name.
2514      * @param isBigEndian   For encodings (like uCS-4), whose names cannot
2515      *                      specify a byte order, this tells whether the order
2516      *                      is bigEndian.  null if unknown or irrelevant.
2517      *
2518      * @return Returns a reader.
2519      */
2520     protected Reader createReader(InputStream inputStream, String encoding, Boolean isBigEndian)
2521         throws IOException {
2522 
2523         String enc = (encoding != null) ? encoding : EncodingInfo.STR_UTF8;
2524         enc = enc.toUpperCase(Locale.ENGLISH);
2525         MessageFormatter f = fErrorReporter.getMessageFormatter(XMLMessageFormatter.XML_DOMAIN);
2526         Locale l = fErrorReporter.getLocale();
2527         switch (enc) {
2528             case EncodingInfo.STR_UTF8:
2529                 return new UTF8Reader(inputStream, fBufferSize, f, l);
2530             case EncodingInfo.STR_UTF16:
2531                 if (isBigEndian != null) {
2532                     return new UTF16Reader(inputStream, fBufferSize, isBigEndian, f, l);
2533                 }
2534                 break;
2535             case EncodingInfo.STR_UTF16BE:
2536                 return new UTF16Reader(inputStream, fBufferSize, true, f, l);
2537             case EncodingInfo.STR_UTF16LE:
2538                 return new UTF16Reader(inputStream, fBufferSize, false, f, l);
2539             case EncodingInfo.STR_UCS4:
2540                 if(isBigEndian != null) {
2541                     if(isBigEndian) {
2542                         return new UCSReader(inputStream, UCSReader.UCS4BE);
2543                     } else {
2544                         return new UCSReader(inputStream, UCSReader.UCS4LE);
2545                     }
2546                 } else {
2547                     fErrorReporter.reportError(this.getEntityScanner(),
2548                             XMLMessageFormatter.XML_DOMAIN,
2549                             "EncodingByteOrderUnsupported",
2550                             new Object[] { encoding },
2551                             XMLErrorReporter.SEVERITY_FATAL_ERROR);
2552                 }
2553                 break;
2554             case EncodingInfo.STR_UCS2:
2555                 if(isBigEndian != null) {
2556                     if(isBigEndian) {
2557                         return new UCSReader(inputStream, UCSReader.UCS2BE);
2558                     } else {
2559                         return new UCSReader(inputStream, UCSReader.UCS2LE);
2560                     }
2561                 } else {
2562                     fErrorReporter.reportError(this.getEntityScanner(),
2563                             XMLMessageFormatter.XML_DOMAIN,
2564                             "EncodingByteOrderUnsupported",
2565                             new Object[] { encoding },
2566                             XMLErrorReporter.SEVERITY_FATAL_ERROR);
2567                 }
2568                 break;
2569         }
2570 
2571         // check for valid name
2572         boolean validIANA = XMLChar.isValidIANAEncoding(encoding);
2573         boolean validJava = XMLChar.isValidJavaEncoding(encoding);
2574         if (!validIANA || (fAllowJavaEncodings && !validJava)) {
2575             fErrorReporter.reportError(this.getEntityScanner(),
2576                     XMLMessageFormatter.XML_DOMAIN,
2577                     "EncodingDeclInvalid",
2578                     new Object[] { encoding },
2579                     XMLErrorReporter.SEVERITY_FATAL_ERROR);
2580             // NOTE: AndyH suggested that, on failure, we use ISO Latin 1
2581             //       because every byte is a valid ISO Latin 1 character.
2582             //       It may not translate correctly but if we failed on
2583             //       the encoding anyway, then we're expecting the content
2584             //       of the document to be bad. This will just prevent an
2585             //       invalid UTF-8 sequence to be detected. This is only
2586             //       important when continue-after-fatal-error is turned
2587             //       on. -Ac
2588                     encoding = "ISO-8859-1";
2589         }
2590 
2591         // try to use a Java reader
2592         String javaEncoding = EncodingMap.getIANA2JavaMapping(enc);
2593         if (javaEncoding == null) {
2594             if (fAllowJavaEncodings) {
2595                 javaEncoding = encoding;
2596             } else {
2597                 fErrorReporter.reportError(this.getEntityScanner(),
2598                         XMLMessageFormatter.XML_DOMAIN,
2599                         "EncodingDeclInvalid",
2600                         new Object[] { encoding },
2601                         XMLErrorReporter.SEVERITY_FATAL_ERROR);
2602                 // see comment above.
2603                 javaEncoding = "ISO8859_1";
2604             }
2605         }
2606         if (DEBUG_ENCODINGS) {
2607             System.out.print("$$$ creating Java InputStreamReader: encoding="+javaEncoding);
2608             if (javaEncoding == encoding) {
2609                 System.out.print(" (IANA encoding)");
2610             }
2611             System.out.println();
2612         }
2613         return new BufferedReader( new InputStreamReader(inputStream, javaEncoding));
2614 
2615     } // createReader(InputStream,String, Boolean): Reader
2616 
2617 
2618     /**
2619      * Return the public identifier for the current document event.
2620      * <p>
2621      * The return value is the public identifier of the document
2622      * entity or of the external parsed entity in which the markup
2623      * triggering the event appears.
2624      *
2625      * @return A string containing the public identifier, or
2626      *         null if none is available.
2627      */
2628     public String getPublicId() {
2629         return (fCurrentEntity != null && fCurrentEntity.entityLocation != null) ? fCurrentEntity.entityLocation.getPublicId() : null;
2630     } // getPublicId():String
2631 
2632     /**
2633      * Return the expanded system identifier for the current document event.
2634      * <p>
2635      * The return value is the expanded system identifier of the document
2636      * entity or of the external parsed entity in which the markup
2637      * triggering the event appears.
2638      * <p>
2639      * If the system identifier is a URL, the parser must resolve it
2640      * fully before passing it to the application.
2641      *
2642      * @return A string containing the expanded system identifier, or null
2643      *         if none is available.
2644      */
2645     public String getExpandedSystemId() {
2646         if (fCurrentEntity != null) {
2647             if (fCurrentEntity.entityLocation != null &&
2648                     fCurrentEntity.entityLocation.getExpandedSystemId() != null ) {
2649                 return fCurrentEntity.entityLocation.getExpandedSystemId();
2650             } else {
2651                 // search for the first external entity on the stack
2652                 int size = fEntityStack.size();
2653                 for (int i = size - 1; i >= 0 ; i--) {
2654                     Entity.ScannedEntity externalEntity =
2655                             (Entity.ScannedEntity)fEntityStack.get(i);
2656 
2657                     if (externalEntity.entityLocation != null &&
2658                             externalEntity.entityLocation.getExpandedSystemId() != null) {
2659                         return externalEntity.entityLocation.getExpandedSystemId();
2660                     }
2661                 }
2662             }
2663         }
2664         return null;
2665     } // getExpandedSystemId():String
2666 
2667     /**
2668      * Return the literal system identifier for the current document event.
2669      * <p>
2670      * The return value is the literal system identifier of the document
2671      * entity or of the external parsed entity in which the markup
2672      * triggering the event appears.
2673      * <p>
2674      * @return A string containing the literal system identifier, or null
2675      *         if none is available.
2676      */
2677     public String getLiteralSystemId() {
2678         if (fCurrentEntity != null) {
2679             if (fCurrentEntity.entityLocation != null &&
2680                     fCurrentEntity.entityLocation.getLiteralSystemId() != null ) {
2681                 return fCurrentEntity.entityLocation.getLiteralSystemId();
2682             } else {
2683                 // search for the first external entity on the stack
2684                 int size = fEntityStack.size();
2685                 for (int i = size - 1; i >= 0 ; i--) {
2686                     Entity.ScannedEntity externalEntity =
2687                             (Entity.ScannedEntity)fEntityStack.get(i);
2688 
2689                     if (externalEntity.entityLocation != null &&
2690                             externalEntity.entityLocation.getLiteralSystemId() != null) {
2691                         return externalEntity.entityLocation.getLiteralSystemId();
2692                     }
2693                 }
2694             }
2695         }
2696         return null;
2697     } // getLiteralSystemId():String
2698 
2699     /**
2700      * Return the line number where the current document event ends.
2701      * <p>
2702      * <strong>Warning:</strong> The return value from the method
2703      * is intended only as an approximation for the sake of error
2704      * reporting; it is not intended to provide sufficient information
2705      * to edit the character content of the original XML document.
2706      * <p>
2707      * The return value is an approximation of the line number
2708      * in the document entity or external parsed entity where the
2709      * markup triggering the event appears.
2710      * <p>
2711      * If possible, the SAX driver should provide the line position
2712      * of the first character after the text associated with the document
2713      * event.  The first line in the document is line 1.
2714      *
2715      * @return The line number, or -1 if none is available.
2716      */
2717     public int getLineNumber() {
2718         if (fCurrentEntity != null) {
2719             if (fCurrentEntity.isExternal()) {
2720                 return fCurrentEntity.lineNumber;
2721             } else {
2722                 // search for the first external entity on the stack
2723                 int size = fEntityStack.size();
2724                 for (int i=size-1; i>0 ; i--) {
2725                     Entity.ScannedEntity firstExternalEntity = (Entity.ScannedEntity)fEntityStack.get(i);
2726                     if (firstExternalEntity.isExternal()) {
2727                         return firstExternalEntity.lineNumber;
2728                     }
2729                 }
2730             }
2731         }
2732 
2733         return -1;
2734 
2735     } // getLineNumber():int
2736 
2737     /**
2738      * Return the column number where the current document event ends.
2739      * <p>
2740      * <strong>Warning:</strong> The return value from the method
2741      * is intended only as an approximation for the sake of error
2742      * reporting; it is not intended to provide sufficient information
2743      * to edit the character content of the original XML document.
2744      * <p>
2745      * The return value is an approximation of the column number
2746      * in the document entity or external parsed entity where the
2747      * markup triggering the event appears.
2748      * <p>
2749      * If possible, the SAX driver should provide the line position
2750      * of the first character after the text associated with the document
2751      * event.
2752      * <p>
2753      * If possible, the SAX driver should provide the line position
2754      * of the first character after the text associated with the document
2755      * event.  The first column in each line is column 1.
2756      *
2757      * @return The column number, or -1 if none is available.
2758      */
2759     public int getColumnNumber() {
2760         if (fCurrentEntity != null) {
2761             if (fCurrentEntity.isExternal()) {
2762                 return fCurrentEntity.columnNumber;
2763             } else {
2764                 // search for the first external entity on the stack
2765                 int size = fEntityStack.size();
2766                 for (int i=size-1; i>0 ; i--) {
2767                     Entity.ScannedEntity firstExternalEntity = (Entity.ScannedEntity)fEntityStack.get(i);
2768                     if (firstExternalEntity.isExternal()) {
2769                         return firstExternalEntity.columnNumber;
2770                     }
2771                 }
2772             }
2773         }
2774 
2775         return -1;
2776     } // getColumnNumber():int
2777 
2778 
2779     //
2780     // Protected static methods
2781     //
2782 
2783     /**
2784      * Fixes a platform dependent filename to standard URI form.
2785      *
2786      * @param str The string to fix.
2787      *
2788      * @return Returns the fixed URI string.
2789      */
2790     protected static String fixURI(String str) {
2791 
2792         // handle platform dependent strings
2793         str = str.replace(java.io.File.separatorChar, '/');
2794 
2795         // Windows fix
2796         if (str.length() >= 2) {
2797             char ch1 = str.charAt(1);
2798             // change "C:blah" to "/C:blah"
2799             if (ch1 == ':') {
2800                 char ch0 = Character.toUpperCase(str.charAt(0));
2801                 if (ch0 >= 'A' && ch0 <= 'Z') {
2802                     str = "/" + str;
2803                 }
2804             }
2805             // change "//blah" to "file://blah"
2806             else if (ch1 == '/' && str.charAt(0) == '/') {
2807                 str = "file:" + str;
2808             }
2809         }
2810 
2811         // replace spaces in file names with %20.
2812         // Original comment from JDK5: the following algorithm might not be
2813         // very performant, but people who want to use invalid URI's have to
2814         // pay the price.
2815         int pos = str.indexOf(' ');
2816         if (pos >= 0) {
2817             StringBuilder sb = new StringBuilder(str.length());
2818             // put characters before ' ' into the string builder
2819             for (int i = 0; i < pos; i++)
2820                 sb.append(str.charAt(i));
2821             // and %20 for the space
2822             sb.append("%20");
2823             // for the remamining part, also convert ' ' to "%20".
2824             for (int i = pos+1; i < str.length(); i++) {
2825                 if (str.charAt(i) == ' ')
2826                     sb.append("%20");
2827                 else
2828                     sb.append(str.charAt(i));
2829             }
2830             str = sb.toString();
2831         }
2832 
2833         // done
2834         return str;
2835 
2836     } // fixURI(String):String
2837 
2838 
2839     //
2840     // Package visible methods
2841     //
2842     /** Prints the contents of the buffer. */
2843     final void print() {
2844         if (DEBUG_BUFFER) {
2845             if (fCurrentEntity != null) {
2846                 System.out.print('[');
2847                 System.out.print(fCurrentEntity.count);
2848                 System.out.print(' ');
2849                 System.out.print(fCurrentEntity.position);
2850                 if (fCurrentEntity.count > 0) {
2851                     System.out.print(" \"");
2852                     for (int i = 0; i < fCurrentEntity.count; i++) {
2853                         if (i == fCurrentEntity.position) {
2854                             System.out.print('^');
2855                         }
2856                         char c = fCurrentEntity.ch[i];
2857                         switch (c) {
2858                             case '\n': {
2859                                 System.out.print("\\n");
2860                                 break;
2861                             }
2862                             case '\r': {
2863                                 System.out.print("\\r");
2864                                 break;
2865                             }
2866                             case '\t': {
2867                                 System.out.print("\\t");
2868                                 break;
2869                             }
2870                             case '\\': {
2871                                 System.out.print("\\\\");
2872                                 break;
2873                             }
2874                             default: {
2875                                 System.out.print(c);
2876                             }
2877                         }
2878                     }
2879                     if (fCurrentEntity.position == fCurrentEntity.count) {
2880                         System.out.print('^');
2881                     }
2882                     System.out.print('"');
2883                 }
2884                 System.out.print(']');
2885                 System.out.print(" @ ");
2886                 System.out.print(fCurrentEntity.lineNumber);
2887                 System.out.print(',');
2888                 System.out.print(fCurrentEntity.columnNumber);
2889             } else {
2890                 System.out.print("*NO CURRENT ENTITY*");
2891             }
2892         }
2893     } // print()
2894 
2895     /**
2896      * Information about auto-detectable encodings.
2897      *
2898      * @xerces.internal
2899      *
2900      * @author Michael Glavassevich, IBM
2901      */
2902     private static class EncodingInfo {
2903         public static final String STR_UTF8 = "UTF-8";
2904         public static final String STR_UTF16 = "UTF-16";
2905         public static final String STR_UTF16BE = "UTF-16BE";
2906         public static final String STR_UTF16LE = "UTF-16LE";
2907         public static final String STR_UCS4 = "ISO-10646-UCS-4";
2908         public static final String STR_UCS2 = "ISO-10646-UCS-2";
2909         public static final String STR_CP037 = "CP037";
2910 
2911         /** UTF-8 **/
2912         public static final EncodingInfo UTF_8 =
2913                 new EncodingInfo(STR_UTF8, null, false);
2914 
2915         /** UTF-8, with BOM **/
2916         public static final EncodingInfo UTF_8_WITH_BOM =
2917                 new EncodingInfo(STR_UTF8, null, true);
2918 
2919         /** UTF-16, big-endian **/
2920         public static final EncodingInfo UTF_16_BIG_ENDIAN =
2921                 new EncodingInfo(STR_UTF16BE, STR_UTF16, Boolean.TRUE, false);
2922 
2923         /** UTF-16, big-endian with BOM **/
2924         public static final EncodingInfo UTF_16_BIG_ENDIAN_WITH_BOM =
2925                 new EncodingInfo(STR_UTF16BE, STR_UTF16, Boolean.TRUE, true);
2926 
2927         /** UTF-16, little-endian **/
2928         public static final EncodingInfo UTF_16_LITTLE_ENDIAN =
2929                 new EncodingInfo(STR_UTF16LE, STR_UTF16, Boolean.FALSE, false);
2930 
2931         /** UTF-16, little-endian with BOM **/
2932         public static final EncodingInfo UTF_16_LITTLE_ENDIAN_WITH_BOM =
2933                 new EncodingInfo(STR_UTF16LE, STR_UTF16, Boolean.FALSE, true);
2934 
2935         /** UCS-4, big-endian **/
2936         public static final EncodingInfo UCS_4_BIG_ENDIAN =
2937                 new EncodingInfo(STR_UCS4, Boolean.TRUE, false);
2938 
2939         /** UCS-4, little-endian **/
2940         public static final EncodingInfo UCS_4_LITTLE_ENDIAN =
2941                 new EncodingInfo(STR_UCS4, Boolean.FALSE, false);
2942 
2943         /** UCS-4, unusual byte-order (2143) or (3412) **/
2944         public static final EncodingInfo UCS_4_UNUSUAL_BYTE_ORDER =
2945                 new EncodingInfo(STR_UCS4, null, false);
2946 
2947         /** EBCDIC **/
2948         public static final EncodingInfo EBCDIC = new EncodingInfo(STR_CP037, null, false);
2949 
2950         public final String autoDetectedEncoding;
2951         public final String readerEncoding;
2952         public final Boolean isBigEndian;
2953         public final boolean hasBOM;
2954 
2955         private EncodingInfo(String autoDetectedEncoding, Boolean isBigEndian, boolean hasBOM) {
2956             this(autoDetectedEncoding, autoDetectedEncoding, isBigEndian, hasBOM);
2957         } // <init>(String,Boolean,boolean)
2958 
2959         private EncodingInfo(String autoDetectedEncoding, String readerEncoding,
2960                 Boolean isBigEndian, boolean hasBOM) {
2961             this.autoDetectedEncoding = autoDetectedEncoding;
2962             this.readerEncoding = readerEncoding;
2963             this.isBigEndian = isBigEndian;
2964             this.hasBOM = hasBOM;
2965         } // <init>(String,String,Boolean,boolean)
2966 
2967     } // class EncodingInfo
2968 
2969     /**
2970     * This class wraps the byte inputstreams we're presented with.
2971     * We need it because java.io.InputStreams don't provide
2972     * functionality to reread processed bytes, and they have a habit
2973     * of reading more than one character when you call their read()
2974     * methods.  This means that, once we discover the true (declared)
2975     * encoding of a document, we can neither backtrack to read the
2976     * whole doc again nor start reading where we are with a new
2977     * reader.
2978     *
2979     * This class allows rewinding an inputStream by allowing a mark
2980     * to be set, and the stream reset to that position.  <strong>The
2981     * class assumes that it needs to read one character per
2982     * invocation when it's read() method is inovked, but uses the
2983     * underlying InputStream's read(char[], offset length) method--it
2984     * won't buffer data read this way!</strong>
2985     *
2986     * @xerces.internal
2987     *
2988     * @author Neil Graham, IBM
2989     * @author Glenn Marcy, IBM
2990     */
2991 
2992     protected final class RewindableInputStream extends InputStream {
2993 
2994         private InputStream fInputStream;
2995         private byte[] fData;
2996         private int fStartOffset;
2997         private int fEndOffset;
2998         private int fOffset;
2999         private int fLength;
3000         private int fMark;
3001 
3002         public RewindableInputStream(InputStream is) {
3003             fData = new byte[DEFAULT_XMLDECL_BUFFER_SIZE];
3004             fInputStream = is;
3005             fStartOffset = 0;
3006             fEndOffset = -1;
3007             fOffset = 0;
3008             fLength = 0;
3009             fMark = 0;
3010         }
3011 
3012         public void setStartOffset(int offset) {
3013             fStartOffset = offset;
3014         }
3015 
3016         public void rewind() {
3017             fOffset = fStartOffset;
3018         }
3019 
3020         public int readAndBuffer() throws IOException {
3021             if (fOffset == fData.length) {
3022                 byte[] newData = new byte[fOffset << 1];
3023                 System.arraycopy(fData, 0, newData, 0, fOffset);
3024                 fData = newData;
3025             }
3026             final int b = fInputStream.read();
3027             if (b == -1) {
3028                 fEndOffset = fOffset;
3029                 return -1;
3030             }
3031             fData[fLength++] = (byte)b;
3032             fOffset++;
3033             return b & 0xff;
3034         }
3035 
3036         public int read() throws IOException {
3037             if (fOffset < fLength) {
3038                 return fData[fOffset++] & 0xff;
3039             }
3040             if (fOffset == fEndOffset) {
3041                 return -1;
3042             }
3043             if (fCurrentEntity.mayReadChunks) {
3044                 return fInputStream.read();
3045             }
3046             return readAndBuffer();
3047         }
3048 
3049         public int read(byte[] b, int off, int len) throws IOException {
3050             final int bytesLeft = fLength - fOffset;
3051             if (bytesLeft == 0) {
3052                 if (fOffset == fEndOffset) {
3053                     return -1;
3054                 }
3055 
3056                 // read a block of data as requested
3057                 if(fCurrentEntity.mayReadChunks || !fCurrentEntity.xmlDeclChunkRead) {
3058 
3059                     if (!fCurrentEntity.xmlDeclChunkRead)
3060                     {
3061                         fCurrentEntity.xmlDeclChunkRead = true;
3062                         len = Entity.ScannedEntity.DEFAULT_XMLDECL_BUFFER_SIZE;
3063                     }
3064                     return fInputStream.read(b, off, len);
3065                 }
3066                 int returnedVal = readAndBuffer();
3067                 if (returnedVal == -1) {
3068                     fEndOffset = fOffset;
3069                     return -1;
3070                 }
3071                 b[off] = (byte)returnedVal;
3072                 return 1;
3073             }
3074             if (len < bytesLeft) {
3075                 if (len <= 0) {
3076                     return 0;
3077                 }
3078             } else {
3079                 len = bytesLeft;
3080             }
3081             if (b != null) {
3082                 System.arraycopy(fData, fOffset, b, off, len);
3083             }
3084             fOffset += len;
3085             return len;
3086         }
3087 
3088         public long skip(long n) throws IOException {
3089             int bytesLeft;
3090             if (n <= 0) {
3091                 return 0;
3092             }
3093             bytesLeft = fLength - fOffset;
3094             if (bytesLeft == 0) {
3095                 if (fOffset == fEndOffset) {
3096                     return 0;
3097                 }
3098                 return fInputStream.skip(n);
3099             }
3100             if (n <= bytesLeft) {
3101                 fOffset += n;
3102                 return n;
3103             }
3104             fOffset += bytesLeft;
3105             if (fOffset == fEndOffset) {
3106                 return bytesLeft;
3107             }
3108             n -= bytesLeft;
3109            /*
3110             * In a manner of speaking, when this class isn't permitting more
3111             * than one byte at a time to be read, it is "blocking".  The
3112             * available() method should indicate how much can be read without
3113             * blocking, so while we're in this mode, it should only indicate
3114             * that bytes in its buffer are available; otherwise, the result of
3115             * available() on the underlying InputStream is appropriate.
3116             */
3117             return fInputStream.skip(n) + bytesLeft;
3118         }
3119 
3120         public int available() throws IOException {
3121             final int bytesLeft = fLength - fOffset;
3122             if (bytesLeft == 0) {
3123                 if (fOffset == fEndOffset) {
3124                     return -1;
3125                 }
3126                 return fCurrentEntity.mayReadChunks ? fInputStream.available()
3127                                                     : 0;
3128             }
3129             return bytesLeft;
3130         }
3131 
3132         public void mark(int howMuch) {
3133             fMark = fOffset;
3134         }
3135 
3136         public void reset() {
3137             fOffset = fMark;
3138         }
3139 
3140         public boolean markSupported() {
3141             return true;
3142         }
3143 
3144         public void close() throws IOException {
3145             if (fInputStream != null) {
3146                 fInputStream.close();
3147                 fInputStream = null;
3148             }
3149         }
3150     } // end of RewindableInputStream class
3151 
3152     public void test(){
3153         //System.out.println("TESTING: Added familytree to entityManager");
3154         //Usecase1
3155         fEntityStorage.addExternalEntity("entityUsecase1",null,
3156                 "/space/home/stax/sun/6thJan2004/zephyr/data/test.txt",
3157                 "/space/home/stax/sun/6thJan2004/zephyr/data/entity.xml");
3158 
3159         //Usecase2
3160         fEntityStorage.addInternalEntity("entityUsecase2","<Test>value</Test>");
3161         fEntityStorage.addInternalEntity("entityUsecase3","value3");
3162         fEntityStorage.addInternalEntity("text", "Hello World.");
3163         fEntityStorage.addInternalEntity("empty-element", "<foo/>");
3164         fEntityStorage.addInternalEntity("balanced-element", "<foo></foo>");
3165         fEntityStorage.addInternalEntity("balanced-element-with-text", "<foo>Hello, World</foo>");
3166         fEntityStorage.addInternalEntity("balanced-element-with-entity", "<foo>&text;</foo>");
3167         fEntityStorage.addInternalEntity("unbalanced-entity", "<foo>");
3168         fEntityStorage.addInternalEntity("recursive-entity", "<foo>&recursive-entity2;</foo>");
3169         fEntityStorage.addInternalEntity("recursive-entity2", "<bar>&recursive-entity3;</bar>");
3170         fEntityStorage.addInternalEntity("recursive-entity3", "<baz>&recursive-entity;</baz>");
3171         fEntityStorage.addInternalEntity("ch","©");
3172         fEntityStorage.addInternalEntity("ch1","T");
3173         fEntityStorage.addInternalEntity("% ch2","param");
3174     }
3175 
3176 } // class XMLEntityManager