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