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