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