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