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