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