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 }