1 /* 2 * Copyright (c) 2005, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.xml.internal.stream; 27 28 import com.sun.org.apache.xerces.internal.impl.Constants; 29 import com.sun.org.apache.xerces.internal.impl.PropertyManager; 30 import com.sun.org.apache.xerces.internal.impl.XMLEntityManager; 31 import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter; 32 import com.sun.org.apache.xerces.internal.impl.msg.XMLMessageFormatter; 33 import com.sun.org.apache.xerces.internal.util.URI; 34 import com.sun.org.apache.xerces.internal.util.XMLResourceIdentifierImpl; 35 import com.sun.org.apache.xerces.internal.utils.SecuritySupport; 36 import com.sun.org.apache.xerces.internal.xni.parser.XMLComponentManager; 37 import com.sun.org.apache.xerces.internal.xni.parser.XMLConfigurationException; 38 import java.util.Collections; 39 import java.util.Enumeration; 40 import java.util.HashMap; 41 import java.util.Map; 42 43 /** 44 * 45 * @author K.Venugopal SUN Microsystems 46 * @author Neeraj Bajaj SUN Microsystems 47 * @author Andy Clark, IBM 48 * 49 */ 50 public class XMLEntityStorage { 51 52 /** Property identifier: error reporter. */ 53 protected static final String ERROR_REPORTER = 54 Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY; 55 56 /** Feature identifier: warn on duplicate EntityDef */ 57 protected static final String WARN_ON_DUPLICATE_ENTITYDEF = 58 Constants.XERCES_FEATURE_PREFIX +Constants.WARN_ON_DUPLICATE_ENTITYDEF_FEATURE; 59 60 /** warn on duplicate Entity declaration. 61 * http://apache.org/xml/features/warn-on-duplicate-entitydef 62 */ 63 protected boolean fWarnDuplicateEntityDef; 64 65 /** Entities. */ 66 protected Map<String, Entity> fEntities = new HashMap<>(); 67 68 protected Entity.ScannedEntity fCurrentEntity ; 69 70 private XMLEntityManager fEntityManager; 71 /** 72 * Error reporter. This property identifier is: 73 * http://apache.org/xml/properties/internal/error-reporter 74 */ 75 protected XMLErrorReporter fErrorReporter; 76 protected PropertyManager fPropertyManager ; 77 78 /* To keep track whether an entity is declared in external or internal subset*/ 79 protected boolean fInExternalSubset = false; 80 81 /** Creates a new instance of XMLEntityStorage */ XMLEntityStorage(PropertyManager propertyManager)82 public XMLEntityStorage(PropertyManager propertyManager) { 83 fPropertyManager = propertyManager ; 84 } 85 86 /** Creates a new instance of XMLEntityStorage */ 87 /*public XMLEntityStorage(Entity.ScannedEntity currentEntity) { 88 fCurrentEntity = currentEntity ;*/ XMLEntityStorage(XMLEntityManager entityManager)89 public XMLEntityStorage(XMLEntityManager entityManager) { 90 fEntityManager = entityManager; 91 } 92 reset(PropertyManager propertyManager)93 public void reset(PropertyManager propertyManager){ 94 95 fErrorReporter = (XMLErrorReporter)propertyManager.getProperty(Constants.XERCES_PROPERTY_PREFIX + Constants.ERROR_REPORTER_PROPERTY); 96 fEntities.clear(); 97 fCurrentEntity = null; 98 99 } 100 reset()101 public void reset(){ 102 fEntities.clear(); 103 fCurrentEntity = null; 104 } 105 /** 106 * Resets the component. The component can query the component manager 107 * about any features and properties that affect the operation of the 108 * component. 109 * 110 * @param componentManager The component manager. 111 * 112 * @throws SAXException Thrown by component on initialization error. 113 * For example, if a feature or property is 114 * required for the operation of the component, the 115 * component manager may throw a 116 * SAXNotRecognizedException or a 117 * SAXNotSupportedException. 118 */ reset(XMLComponentManager componentManager)119 public void reset(XMLComponentManager componentManager) 120 throws XMLConfigurationException { 121 122 123 // xerces features 124 125 fWarnDuplicateEntityDef = componentManager.getFeature(WARN_ON_DUPLICATE_ENTITYDEF, false); 126 127 fErrorReporter = (XMLErrorReporter)componentManager.getProperty(ERROR_REPORTER); 128 129 fEntities.clear(); 130 fCurrentEntity = null; 131 132 } // reset(XMLComponentManager) 133 134 /** 135 * Returns entity declaration. 136 * 137 * @param name The name of the entity. 138 * 139 * @see SymbolTable 140 */ getEntity(String name)141 public Entity getEntity(String name) { 142 return fEntities.get(name); 143 } // getEntity(String) 144 hasEntities()145 public boolean hasEntities() { 146 return (fEntities!=null); 147 } // getEntity(String) 148 getEntitySize()149 public int getEntitySize() { 150 return fEntities.size(); 151 } // getEntity(String) 152 getEntityKeys()153 public Enumeration getEntityKeys() { 154 return Collections.enumeration(fEntities.keySet()); 155 } 156 /** 157 * Adds an internal entity declaration. 158 * <p> 159 * <strong>Note:</strong> This method ignores subsequent entity 160 * declarations. 161 * <p> 162 * <strong>Note:</strong> The name should be a unique symbol. The 163 * SymbolTable can be used for this purpose. 164 * 165 * @param name The name of the entity. 166 * @param text The text of the entity. 167 * 168 * @see SymbolTable 169 */ addInternalEntity(String name, String text)170 public void addInternalEntity(String name, String text) { 171 if (!fEntities.containsKey(name)) { 172 Entity entity = new Entity.InternalEntity(name, text, fInExternalSubset); 173 fEntities.put(name, entity); 174 } 175 else{ 176 if(fWarnDuplicateEntityDef){ 177 fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, 178 "MSG_DUPLICATE_ENTITY_DEFINITION", 179 new Object[]{ name }, 180 XMLErrorReporter.SEVERITY_WARNING ); 181 } 182 } 183 } // addInternalEntity(String,String) 184 185 /** 186 * Adds an external entity declaration. 187 * <p> 188 * <strong>Note:</strong> This method ignores subsequent entity 189 * declarations. 190 * <p> 191 * <strong>Note:</strong> The name should be a unique symbol. The 192 * SymbolTable can be used for this purpose. 193 * 194 * @param name The name of the entity. 195 * @param publicId The public identifier of the entity. 196 * @param literalSystemId The system identifier of the entity. 197 * @param baseSystemId The base system identifier of the entity. 198 * This is the system identifier of the entity 199 * where <em>the entity being added</em> and 200 * is used to expand the system identifier when 201 * the system identifier is a relative URI. 202 * When null the system identifier of the first 203 * external entity on the stack is used instead. 204 * 205 * @see SymbolTable 206 */ addExternalEntity(String name, String publicId, String literalSystemId, String baseSystemId)207 public void addExternalEntity(String name, 208 String publicId, String literalSystemId, 209 String baseSystemId) { 210 if (!fEntities.containsKey(name)) { 211 if (baseSystemId == null) { 212 // search for the first external entity on the stack 213 //xxx commenting the 'size' variable.. 214 /** 215 * int size = fEntityStack.size(); 216 * if (size == 0 && fCurrentEntity != null && fCurrentEntity.entityLocation != null) { 217 * baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId(); 218 * } 219 */ 220 221 //xxx we need to have information about the current entity. 222 if (fCurrentEntity != null && fCurrentEntity.entityLocation != null) { 223 baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId(); 224 } 225 /** 226 * for (int i = size - 1; i >= 0 ; i--) { 227 * ScannedEntity externalEntity = 228 * (ScannedEntity)fEntityStack.elementAt(i); 229 * if (externalEntity.entityLocation != null && externalEntity.entityLocation.getExpandedSystemId() != null) { 230 * baseSystemId = externalEntity.entityLocation.getExpandedSystemId(); 231 * break; 232 * } 233 * } 234 */ 235 } 236 237 fCurrentEntity = fEntityManager.getCurrentEntity(); 238 Entity entity = new Entity.ExternalEntity(name, 239 new XMLResourceIdentifierImpl(publicId, literalSystemId, 240 baseSystemId, expandSystemId(literalSystemId, baseSystemId)), 241 null, fInExternalSubset); 242 //TODO :: Forced to pass true above remove it. 243 //(fCurrentEntity == null) ? fasle : fCurrentEntity.isEntityDeclInExternalSubset()); 244 // null, fCurrentEntity.isEntityDeclInExternalSubset()); 245 fEntities.put(name, entity); 246 } 247 else{ 248 if(fWarnDuplicateEntityDef){ 249 fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, 250 "MSG_DUPLICATE_ENTITY_DEFINITION", 251 new Object[]{ name }, 252 XMLErrorReporter.SEVERITY_WARNING ); 253 } 254 } 255 256 } // addExternalEntity(String,String,String,String) 257 258 /** 259 * Checks whether an entity given by name is external. 260 * 261 * @param entityName The name of the entity to check. 262 * @returns True if the entity is external, false otherwise 263 * (including when the entity is not declared). 264 */ isExternalEntity(String entityName)265 public boolean isExternalEntity(String entityName) { 266 267 Entity entity = fEntities.get(entityName); 268 if (entity == null) { 269 return false; 270 } 271 return entity.isExternal(); 272 } 273 274 /** 275 * Checks whether the declaration of an entity given by name is 276 * // in the external subset. 277 * 278 * @param entityName The name of the entity to check. 279 * @returns True if the entity was declared in the external subset, false otherwise 280 * (including when the entity is not declared). 281 */ isEntityDeclInExternalSubset(String entityName)282 public boolean isEntityDeclInExternalSubset(String entityName) { 283 284 Entity entity = fEntities.get(entityName); 285 if (entity == null) { 286 return false; 287 } 288 return entity.isEntityDeclInExternalSubset(); 289 } 290 291 /** 292 * Adds an unparsed entity declaration. 293 * <p> 294 * <strong>Note:</strong> This method ignores subsequent entity 295 * declarations. 296 * <p> 297 * <strong>Note:</strong> The name should be a unique symbol. The 298 * SymbolTable can be used for this purpose. 299 * 300 * @param name The name of the entity. 301 * @param publicId The public identifier of the entity. 302 * @param systemId The system identifier of the entity. 303 * @param notation The name of the notation. 304 * 305 * @see SymbolTable 306 */ addUnparsedEntity(String name, String publicId, String systemId, String baseSystemId, String notation)307 public void addUnparsedEntity(String name, 308 String publicId, String systemId, 309 String baseSystemId, String notation) { 310 311 fCurrentEntity = fEntityManager.getCurrentEntity(); 312 if (!fEntities.containsKey(name)) { 313 Entity entity = new Entity.ExternalEntity(name, new XMLResourceIdentifierImpl(publicId, systemId, baseSystemId, null), notation, fInExternalSubset); 314 // (fCurrentEntity == null) ? fasle : fCurrentEntity.isEntityDeclInExternalSubset()); 315 // fCurrentEntity.isEntityDeclInExternalSubset()); 316 fEntities.put(name, entity); 317 } 318 else{ 319 if(fWarnDuplicateEntityDef){ 320 fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, 321 "MSG_DUPLICATE_ENTITY_DEFINITION", 322 new Object[]{ name }, 323 XMLErrorReporter.SEVERITY_WARNING ); 324 } 325 } 326 } // addUnparsedEntity(String,String,String,String) 327 328 /** 329 * Checks whether an entity given by name is unparsed. 330 * 331 * @param entityName The name of the entity to check. 332 * @returns True if the entity is unparsed, false otherwise 333 * (including when the entity is not declared). 334 */ isUnparsedEntity(String entityName)335 public boolean isUnparsedEntity(String entityName) { 336 337 Entity entity = fEntities.get(entityName); 338 if (entity == null) { 339 return false; 340 } 341 return entity.isUnparsed(); 342 } 343 344 /** 345 * Checks whether an entity given by name is declared. 346 * 347 * @param entityName The name of the entity to check. 348 * @returns True if the entity is declared, false otherwise. 349 */ isDeclaredEntity(String entityName)350 public boolean isDeclaredEntity(String entityName) { 351 352 Entity entity = fEntities.get(entityName); 353 return entity != null; 354 } 355 /** 356 * Expands a system id and returns the system id as a URI, if 357 * it can be expanded. A return value of null means that the 358 * identifier is already expanded. An exception thrown 359 * indicates a failure to expand the id. 360 * 361 * @param systemId The systemId to be expanded. 362 * 363 * @return Returns the URI string representing the expanded system 364 * identifier. A null value indicates that the given 365 * system identifier is already expanded. 366 * 367 */ expandSystemId(String systemId)368 public static String expandSystemId(String systemId) { 369 return expandSystemId(systemId, null); 370 } // expandSystemId(String):String 371 372 // current value of the "user.dir" property 373 private static String gUserDir; 374 // escaped value of the current "user.dir" property 375 private static String gEscapedUserDir; 376 // which ASCII characters need to be escaped 377 private static boolean gNeedEscaping[] = new boolean[128]; 378 // the first hex character if a character needs to be escaped 379 private static char gAfterEscaping1[] = new char[128]; 380 // the second hex character if a character needs to be escaped 381 private static char gAfterEscaping2[] = new char[128]; 382 private static char[] gHexChs = {'0', '1', '2', '3', '4', '5', '6', '7', 383 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 384 // initialize the above 3 arrays 385 static { 386 for (int i = 0; i <= 0x1f; i++) { 387 gNeedEscaping[i] = true; 388 gAfterEscaping1[i] = gHexChs[i >> 4]; 389 gAfterEscaping2[i] = gHexChs[i & 0xf]; 390 } 391 gNeedEscaping[0x7f] = true; 392 gAfterEscaping1[0x7f] = '7'; 393 gAfterEscaping2[0x7f] = 'F'; 394 char[] escChs = {' ', '<', '>', '#', '%', '"', '{', '}', 395 '|', '\\', '^', '~', '[', ']', '`'}; 396 int len = escChs.length; 397 char ch; 398 for (int i = 0; i < len; i++) { 399 ch = escChs[i]; 400 gNeedEscaping[ch] = true; 401 gAfterEscaping1[ch] = gHexChs[ch >> 4]; 402 gAfterEscaping2[ch] = gHexChs[ch & 0xf]; 403 } 404 } 405 // To escape the "user.dir" system property, by using %HH to represent 406 // special ASCII characters: 0x00~0x1F, 0x7F, ' ', '<', '>', '#', '%' 407 // and '"'. It's a static method, so needs to be synchronized. 408 // this method looks heavy, but since the system property isn't expected 409 // to change often, so in most cases, we only need to return the string 410 // that was escaped before. 411 // According to the URI spec, non-ASCII characters (whose value >= 128) 412 // need to be escaped too. 413 // REVISIT: don't know how to escape non-ASCII characters, especially 414 // which encoding to use. Leave them for now. getUserDir()415 private static synchronized String getUserDir() { 416 // get the user.dir property 417 String userDir = ""; 418 try { 419 userDir = SecuritySupport.getSystemProperty("user.dir"); 420 } 421 catch (SecurityException se) { 422 } 423 424 // return empty string if property value is empty string. 425 if (userDir.length() == 0) 426 return ""; 427 428 // compute the new escaped value if the new property value doesn't 429 // match the previous one 430 if (userDir.equals(gUserDir)) { 431 return gEscapedUserDir; 432 } 433 434 // record the new value as the global property value 435 gUserDir = userDir; 436 437 char separator = java.io.File.separatorChar; 438 userDir = userDir.replace(separator, '/'); 439 440 int len = userDir.length(), ch; 441 StringBuffer buffer = new StringBuffer(len*3); 442 // change C:/blah to /C:/blah 443 if (len >= 2 && userDir.charAt(1) == ':') { 444 ch = Character.toUpperCase(userDir.charAt(0)); 445 if (ch >= 'A' && ch <= 'Z') { 446 buffer.append('/'); 447 } 448 } 449 450 // for each character in the path 451 int i = 0; 452 for (; i < len; i++) { 453 ch = userDir.charAt(i); 454 // if it's not an ASCII character, break here, and use UTF-8 encoding 455 if (ch >= 128) 456 break; 457 if (gNeedEscaping[ch]) { 458 buffer.append('%'); 459 buffer.append(gAfterEscaping1[ch]); 460 buffer.append(gAfterEscaping2[ch]); 461 // record the fact that it's escaped 462 } 463 else { 464 buffer.append((char)ch); 465 } 466 } 467 468 // we saw some non-ascii character 469 if (i < len) { 470 // get UTF-8 bytes for the remaining sub-string 471 byte[] bytes = null; 472 byte b; 473 try { 474 bytes = userDir.substring(i).getBytes("UTF-8"); 475 } catch (java.io.UnsupportedEncodingException e) { 476 // should never happen 477 return userDir; 478 } 479 len = bytes.length; 480 481 // for each byte 482 for (i = 0; i < len; i++) { 483 b = bytes[i]; 484 // for non-ascii character: make it positive, then escape 485 if (b < 0) { 486 ch = b + 256; 487 buffer.append('%'); 488 buffer.append(gHexChs[ch >> 4]); 489 buffer.append(gHexChs[ch & 0xf]); 490 } 491 else if (gNeedEscaping[b]) { 492 buffer.append('%'); 493 buffer.append(gAfterEscaping1[b]); 494 buffer.append(gAfterEscaping2[b]); 495 } 496 else { 497 buffer.append((char)b); 498 } 499 } 500 } 501 502 // change blah/blah to blah/blah/ 503 if (!userDir.endsWith("/")) 504 buffer.append('/'); 505 506 gEscapedUserDir = buffer.toString(); 507 508 return gEscapedUserDir; 509 } 510 511 /** 512 * Expands a system id and returns the system id as a URI, if 513 * it can be expanded. A return value of null means that the 514 * identifier is already expanded. An exception thrown 515 * indicates a failure to expand the id. 516 * 517 * @param systemId The systemId to be expanded. 518 * 519 * @return Returns the URI string representing the expanded system 520 * identifier. A null value indicates that the given 521 * system identifier is already expanded. 522 * 523 */ expandSystemId(String systemId, String baseSystemId)524 public static String expandSystemId(String systemId, String baseSystemId) { 525 526 // check for bad parameters id 527 if (systemId == null || systemId.length() == 0) { 528 return systemId; 529 } 530 // if id already expanded, return 531 try { 532 new URI(systemId); 533 return systemId; 534 } catch (URI.MalformedURIException e) { 535 // continue on... 536 } 537 // normalize id 538 String id = fixURI(systemId); 539 540 // normalize base 541 URI base = null; 542 URI uri = null; 543 try { 544 if (baseSystemId == null || baseSystemId.length() == 0 || 545 baseSystemId.equals(systemId)) { 546 String dir = getUserDir(); 547 base = new URI("file", "", dir, null, null); 548 } 549 else { 550 try { 551 base = new URI(fixURI(baseSystemId)); 552 } 553 catch (URI.MalformedURIException e) { 554 if (baseSystemId.indexOf(':') != -1) { 555 // for xml schemas we might have baseURI with 556 // a specified drive 557 base = new URI("file", "", fixURI(baseSystemId), null, null); 558 } 559 else { 560 String dir = getUserDir(); 561 dir = dir + fixURI(baseSystemId); 562 base = new URI("file", "", dir, null, null); 563 } 564 } 565 } 566 // expand id 567 uri = new URI(base, id); 568 } 569 catch (Exception e) { 570 // let it go through 571 572 } 573 574 if (uri == null) { 575 return systemId; 576 } 577 return uri.toString(); 578 579 } // expandSystemId(String,String):String 580 // 581 // Protected static methods 582 // 583 584 /** 585 * Fixes a platform dependent filename to standard URI form. 586 * 587 * @param str The string to fix. 588 * 589 * @return Returns the fixed URI string. 590 */ fixURI(String str)591 protected static String fixURI(String str) { 592 593 // handle platform dependent strings 594 str = str.replace(java.io.File.separatorChar, '/'); 595 596 // Windows fix 597 if (str.length() >= 2) { 598 char ch1 = str.charAt(1); 599 // change "C:blah" to "/C:blah" 600 if (ch1 == ':') { 601 char ch0 = Character.toUpperCase(str.charAt(0)); 602 if (ch0 >= 'A' && ch0 <= 'Z') { 603 str = "/" + str; 604 } 605 } 606 // change "//blah" to "file://blah" 607 else if (ch1 == '/' && str.charAt(0) == '/') { 608 str = "file:" + str; 609 } 610 } 611 612 // done 613 return str; 614 615 } // fixURI(String):String 616 617 // indicate start of external subset startExternalSubset()618 public void startExternalSubset() { 619 fInExternalSubset = true; 620 } 621 endExternalSubset()622 public void endExternalSubset() { 623 fInExternalSubset = false; 624 } 625 } 626