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