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