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