1/*
2 * Copyright (c) 2015, 2017, 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 */
25package javax.xml.catalog;
26
27import com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl;
28import java.io.IOException;
29import java.io.InputStream;
30import java.io.Reader;
31import java.io.StringReader;
32import java.net.URL;
33import javax.xml.parsers.ParserConfigurationException;
34import javax.xml.parsers.SAXParserFactory;
35import javax.xml.transform.Source;
36import javax.xml.transform.sax.SAXSource;
37import org.w3c.dom.ls.LSInput;
38import org.xml.sax.InputSource;
39import org.xml.sax.SAXException;
40import org.xml.sax.XMLReader;
41
42/**
43 * Implements CatalogResolver.
44 *
45 * <p>
46 * This class implements a SAX EntityResolver, StAX XMLResolver,
47 * Schema Validation LSResourceResolver and Transform URIResolver.
48 *
49 *
50 * @since 9
51 */
52final class CatalogResolverImpl implements CatalogResolver {
53    Catalog catalog;
54
55    /**
56     * Construct an instance of the CatalogResolver from a Catalog.
57     *
58     * @param catalog A Catalog.
59     */
60    public CatalogResolverImpl(Catalog catalog) {
61        this.catalog = catalog;
62    }
63
64    /*
65       Implements the EntityResolver interface
66    */
67    @Override
68    public InputSource resolveEntity(String publicId, String systemId) {
69        //8150187: NPE expected if the system identifier is null for CatalogResolver
70        CatalogMessages.reportNPEOnNull("systemId", systemId);
71
72        //Normalize publicId and systemId
73        systemId = Normalizer.normalizeURI(Util.getNotNullOrEmpty(systemId));
74        publicId = Normalizer.normalizePublicId(Normalizer.decodeURN(Util.getNotNullOrEmpty(publicId)));
75
76        //check whether systemId is a urn
77        if (systemId != null && systemId.startsWith(Util.URN)) {
78            systemId = Normalizer.decodeURN(systemId);
79            if (publicId != null && !publicId.equals(systemId)) {
80                systemId = null;
81            } else {
82                publicId = systemId;
83                systemId = null;
84            }
85        }
86
87        CatalogImpl c = (CatalogImpl)catalog;
88        String resolvedSystemId = Util.resolve(c, publicId, systemId);
89
90        if (resolvedSystemId != null) {
91            return new InputSource(resolvedSystemId);
92        }
93
94        GroupEntry.ResolveType resolveType = ((CatalogImpl) catalog).getResolve();
95        switch (resolveType) {
96            case IGNORE:
97                return new InputSource(new StringReader(""));
98            case STRICT:
99                CatalogMessages.reportError(CatalogMessages.ERR_NO_MATCH,
100                        new Object[]{publicId, systemId});
101        }
102
103        //no action, allow the parser to continue
104        return null;
105    }
106
107    /*
108        Implements the URIResolver interface
109    */
110    CatalogResolverImpl entityResolver;
111
112    @Override
113    public Source resolve(String href, String base) {
114        CatalogMessages.reportNPEOnNull("href", href);
115
116        href = Util.getNotNullOrEmpty(href);
117        base = Util.getNotNullOrEmpty(base);
118
119        String result = null;
120        CatalogImpl c = (CatalogImpl)catalog;
121        String uri = Normalizer.normalizeURI(href);
122        if (uri == null) {
123            return null;
124        }
125
126        //check whether uri is a urn
127        if (uri != null && uri.startsWith(Util.URN)) {
128            String publicId = Normalizer.decodeURN(uri);
129            if (publicId != null) {
130                result = Util.resolve(c, publicId, null);
131            }
132        }
133
134        //if no match with a public id, continue search for a URI
135        if (result == null) {
136            //remove fragment if any.
137            int hashPos = uri.indexOf("#");
138            if (hashPos >= 0) {
139                uri = uri.substring(0, hashPos);
140            }
141
142            //search the current catalog
143            result = Util.resolve(c, null, uri);
144        }
145
146        //Report error or return the URI as is when no match is found
147        if (result == null) {
148            GroupEntry.ResolveType resolveType = c.getResolve();
149            switch (resolveType) {
150                case IGNORE:
151                    return new SAXSource(new InputSource(new StringReader("")));
152                case STRICT:
153                    CatalogMessages.reportError(CatalogMessages.ERR_NO_URI_MATCH,
154                            new Object[]{href, base});
155            }
156            try {
157                URL url = null;
158
159                if (base == null) {
160                    url = new URL(uri);
161                    result = url.toString();
162                } else {
163                    URL baseURL = new URL(base);
164                    url = (href.length() == 0 ? baseURL : new URL(baseURL, uri));
165                    result = url.toString();
166                }
167            } catch (java.net.MalformedURLException mue) {
168                    CatalogMessages.reportError(CatalogMessages.ERR_CREATING_URI,
169                            new Object[]{href, base});
170            }
171        }
172
173        SAXSource source = new SAXSource();
174        source.setInputSource(new InputSource(result));
175        setEntityResolver(source);
176        return source;
177    }
178
179    /**
180     * Establish an entityResolver for newly resolved URIs.
181     * <p>
182     * This is called from the URIResolver to set an EntityResolver on the SAX
183     * parser to be used for new XML documents that are encountered as a result
184     * of the document() function, xsl:import, or xsl:include. This is done
185     * because the XSLT processor calls out to the SAXParserFactory itself to
186     * create a new SAXParser to parse the new document. The new parser does not
187     * automatically inherit the EntityResolver of the original (although
188     * arguably it should). Quote from JAXP specification on Class
189     * SAXTransformerFactory:
190     * <p>
191     * {@code If an application wants to set the ErrorHandler or EntityResolver
192     * for an XMLReader used during a transformation, it should use a URIResolver
193     * to return the SAXSource which provides (with getXMLReader) a reference to
194     * the XMLReader}
195     *
196     */
197    private void setEntityResolver(SAXSource source) {
198        XMLReader reader = source.getXMLReader();
199        if (reader == null) {
200            SAXParserFactory spFactory = new SAXParserFactoryImpl();
201            spFactory.setNamespaceAware(true);
202            try {
203                reader = spFactory.newSAXParser().getXMLReader();
204            } catch (ParserConfigurationException | SAXException ex) {
205                CatalogMessages.reportRunTimeError(CatalogMessages.ERR_PARSER_CONF, ex);
206            }
207        }
208        if (entityResolver != null) {
209            entityResolver = new CatalogResolverImpl(catalog);
210        }
211        reader.setEntityResolver(entityResolver);
212        source.setXMLReader(reader);
213    }
214
215    @Override
216    public InputStream resolveEntity(String publicId, String systemId, String baseUri, String namespace) {
217        InputSource is = resolveEntity(publicId, systemId);
218
219        if (is != null && !is.isEmpty()) {
220
221            try {
222                return new URL(is.getSystemId()).openStream();
223            } catch (IOException ex) {
224                //considered as no mapping.
225            }
226
227        }
228
229        GroupEntry.ResolveType resolveType = ((CatalogImpl) catalog).getResolve();
230        switch (resolveType) {
231            case IGNORE:
232                return null;
233            case STRICT:
234                CatalogMessages.reportError(CatalogMessages.ERR_NO_MATCH,
235                        new Object[]{publicId, systemId});
236        }
237
238        //no action, allow the parser to continue
239        return null;
240    }
241
242    @Override
243    public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
244        InputSource is = resolveEntity(publicId, systemId);
245
246        if (is != null && !is.isEmpty()) {
247            return new LSInputImpl(is.getSystemId());
248        }
249
250        GroupEntry.ResolveType resolveType = ((CatalogImpl) catalog).getResolve();
251        switch (resolveType) {
252            case IGNORE:
253                return null;
254            case STRICT:
255                CatalogMessages.reportError(CatalogMessages.ERR_NO_MATCH,
256                        new Object[]{publicId, systemId});
257        }
258
259        //no action, allow the parser to continue
260        return null;
261    }
262
263    /**
264     * Implements LSInput. All that we need is the systemId since the Catalog
265     * has already resolved it.
266     */
267    class LSInputImpl implements LSInput {
268
269        private String systemId;
270
271        public LSInputImpl(String systemId) {
272            this.systemId = systemId;
273        }
274
275        @Override
276        public Reader getCharacterStream() {
277            return null;
278        }
279
280        @Override
281        public void setCharacterStream(Reader characterStream) {
282        }
283
284        @Override
285        public InputStream getByteStream() {
286            return null;
287        }
288
289        @Override
290        public void setByteStream(InputStream byteStream) {
291        }
292
293        @Override
294        public String getStringData() {
295            return null;
296        }
297
298        @Override
299        public void setStringData(String stringData) {
300        }
301
302        @Override
303        public String getSystemId() {
304            return systemId;
305        }
306
307        @Override
308        public void setSystemId(String systemId) {
309            this.systemId = systemId;
310        }
311
312        @Override
313        public String getPublicId() {
314            return null;
315        }
316
317        @Override
318        public void setPublicId(String publicId) {
319        }
320
321        @Override
322        public String getBaseURI() {
323            return null;
324        }
325
326        @Override
327        public void setBaseURI(String baseURI) {
328        }
329
330        @Override
331        public String getEncoding() {
332            return null;
333        }
334
335        @Override
336        public void setEncoding(String encoding) {
337        }
338
339        @Override
340        public boolean getCertifiedText() {
341            return false;
342        }
343
344        @Override
345        public void setCertifiedText(boolean certifiedText) {
346        }
347    }
348
349}
350