View Javadoc
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 }