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