OSDN Git Service

Split entity resolver from resource resolver to prevent XXE vulnerability.
[mikutoga/TogaGem.git] / src / main / java / jp / sfjp / mikutoga / xml / XmlResourceResolver.java
1 /*
2  * xml resource resolver
3  *
4  * License : The MIT License
5  * Copyright(c) 2009 olyutorskii
6  */
7
8 package jp.sfjp.mikutoga.xml;
9
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.io.Reader;
13 import java.net.URI;
14 import java.net.URISyntaxException;
15 import java.net.URL;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.Map;
19 import org.w3c.dom.ls.LSInput;
20 import org.w3c.dom.ls.LSResourceResolver;
21
22 /**
23  * URL変換マップに従い、XML文書からの外部参照をリダイレクトする。
24  * 相対URIはこのクラスをベースに解決される。
25  * 主な用途は外部スキーマのリソース化など。
26  */
27 public class XmlResourceResolver
28         implements LSResourceResolver{
29
30     /** XML Schema. */
31     public static final String SCHEMA_XML =
32             "http://www.w3.org/2001/xml.xsd";
33
34     /** XSD名前空間。 */
35     public static final String NS_XSD =
36             "http://www.w3.org/2001/XMLSchema-instance";
37
38     private static final String LOCAL_SCHEMA_XML =
39             "resources/xmlspace.xsd";
40
41     private static final URI EMPTY_URI = URI.create("");
42
43     private static final Class<?> THISCLASS = XmlResourceResolver.class;
44
45
46     private final Map<URI, URI> uriMap;
47
48
49     /**
50      * コンストラクタ。
51      */
52     public XmlResourceResolver(){
53         super();
54
55         assert this.getClass().equals(THISCLASS);
56
57         Map<URI, URI> map;
58         map = new HashMap<>();
59         map = Collections.synchronizedMap(map);
60         this.uriMap = map;
61
62         URL redirectRes = THISCLASS.getResource(LOCAL_SCHEMA_XML);
63         String redirectResName = redirectRes.toString();
64
65         URI originalURI = URI.create(SCHEMA_XML);
66         URI redirectURI = URI.create(redirectResName);
67
68         putRedirectedImpl(originalURI, redirectURI);
69
70         return;
71     }
72
73
74     /**
75      * 絶対URIと相対URIを合成したURIを返す。
76      * 正規化も行われる。
77      *
78      * @param base 絶対URIでなければならない。nullでもよい。
79      * @param relative 絶対URIでもよいがその場合baseは無視される。null可。
80      * @return 合成結果のURLオブジェクト。必ず絶対URIになる。
81      * @throws java.net.URISyntaxException URIとして変。
82      * @throws java.lang.IllegalArgumentException 絶対URIが生成できない。
83      */
84     protected static URI buildBaseRelativeURI(String base, String relative)
85             throws URISyntaxException,
86                    IllegalArgumentException {
87         URI baseURI;
88         if(base != null){
89             baseURI = new URI(base);
90             if( ! baseURI.isAbsolute() ){
91                 throw new IllegalArgumentException();
92             }
93         }else{
94             baseURI = null;
95         }
96
97         URI relativeURI;
98         if(relative != null){
99             relativeURI = new URI(relative);
100         }else{
101             relativeURI = EMPTY_URI;
102         }
103
104         URI result = buildBaseRelativeURI(baseURI, relativeURI);
105         return result;
106     }
107
108     /**
109      * 絶対URIと相対URIを合成したURIを返す。
110      * 正規化も行われる。
111      *
112      * @param baseURI 絶対URIでなければならない。nullでもよい。
113      * @param relativeURI 絶対URIでもよいがその場合baseは無視される。
114      * @return 合成結果のURLオブジェクト。必ず絶対URIになる。
115      * @throws java.lang.IllegalArgumentException 絶対URIが生成できない。
116      */
117     private static URI buildBaseRelativeURI(URI baseURI, URI relativeURI)
118             throws IllegalArgumentException {
119         URI resultURI;
120
121         if(baseURI == null || relativeURI.isAbsolute()){
122             resultURI = relativeURI;
123         }else{
124             resultURI = baseURI.resolve(relativeURI);
125         }
126
127         if( ! resultURI.isAbsolute() ){
128             throw new IllegalArgumentException();
129         }
130
131         resultURI = resultURI.normalize();
132
133         return resultURI;
134     }
135
136     /**
137      * LSInput実装を生成する。
138      * @return LSInput実装
139      */
140     public static LSInput createLSInput(){
141         LSInput input = new LSInputImpl();
142         return input;
143     }
144
145
146     /**
147      * オリジナルURIとリダイレクト先のURIを登録する。
148      * オリジナルURIへのアクセスはリダイレクトされる。
149      * @param original オリジナルURI
150      * @param redirect リダイレクトURI
151      */
152     private void putRedirectedImpl(URI original, URI redirect){
153         URI oridinalNorm = original.normalize();
154         URI redirectNorm = redirect.normalize();
155
156         this.uriMap.put(oridinalNorm, redirectNorm);
157
158         return;
159     }
160
161     /**
162      * オリジナルURIとリダイレクト先のURIを登録する。
163      * オリジナルURIへのアクセスはリダイレクトされる。
164      * @param original オリジナルURI
165      * @param redirect リダイレクトURI
166      */
167     public void putRedirected(URI original, URI redirect){
168         putRedirectedImpl(original, redirect);
169         return;
170     }
171
172     /**
173      * ローカル版リソース参照解決を登録する。
174      * @param lsc ローカル版リソース参照解決
175      */
176     public void putRedirected(LocalXmlResource lsc){
177         URI original = lsc.getOriginalResource();
178         if(original == null) return;
179
180         URI local = lsc.getLocalResource();
181
182         putRedirected(original, local);
183
184         return;
185     }
186
187     /**
188      * 別リゾルバの登録内容を追加登録する。
189      * @param other 別リゾルバ
190      */
191     public void putRedirected(XmlResourceResolver other){
192         this.uriMap.putAll(other.uriMap);
193         return;
194     }
195
196     /**
197      * 登録済みリダイレクト先URIを返す。
198      * @param original オリジナルURI
199      * @return リダイレクト先URI。未登録の場合はnull
200      */
201     public URI getRedirected(URI original){
202         URI keyURI = original.normalize();
203         URI resourceURI = this.uriMap.get(keyURI);
204         return resourceURI;
205     }
206
207     /**
208      * 登録済みリダイレクト先URIを返す。
209      * @param original オリジナルURI
210      * @return リダイレクト先URI。未登録の場合はオリジナルを返す
211      */
212     public URI resolveRedirected(URI original){
213         URI result = getRedirected(original);
214         if(result == null) result = original;
215         return result;
216     }
217
218     /**
219      * 登録済みリダイレクト先リソースの入力ストリームを得る。
220      * @param originalURI オリジナルURI
221      * @return 入力ストリーム。リダイレクト先が未登録の場合はnull
222      * @throws java.io.IOException 入出力エラー。
223      *     もしくはリソースが見つからない。
224      */
225     private InputStream getXMLResourceAsStream(URI originalURI)
226             throws IOException{
227         URI resourceURI = getRedirected(originalURI);
228         if(resourceURI == null) return null;
229
230         URL resourceURL = resourceURI.toURL();
231         InputStream is = resourceURL.openStream();
232
233         return is;
234     }
235
236     /**
237      * {@inheritDoc}
238      * URL変換したあとの入力ソースを返す。
239      * @param type {@inheritDoc}
240      * @param namespaceURI {@inheritDoc}
241      * @param publicId {@inheritDoc}
242      * @param systemId {@inheritDoc}
243      * @param baseURI {@inheritDoc}
244      * @return {@inheritDoc}
245      */
246     @Override
247     public LSInput resolveResource(String type,
248                                      String namespaceURI,
249                                      String publicId,
250                                      String systemId,
251                                      String baseURI ){
252         if(systemId == null) return null;
253
254         URI originalURI;
255         try{
256             originalURI = buildBaseRelativeURI(baseURI, systemId);
257         }catch(URISyntaxException e){
258             return null;
259         }
260
261         InputStream is;
262         try{
263             is = getXMLResourceAsStream(originalURI);
264         }catch(IOException e){
265             return null;
266         }
267         if(is == null) return null;
268
269         LSInput input = createLSInput();
270         input.setBaseURI(baseURI);
271         input.setPublicId(publicId);
272         input.setSystemId(systemId);
273         input.setByteStream(is);
274
275         return input;
276     }
277
278
279     /**
280      * JRE1.5用LSInput実装。
281      * JRE1.6なら
282      * org.w3c.dom.ls.DOMImplementationLS#createLSInput()
283      * で生成可能かも。
284      */
285     private static final class LSInputImpl implements LSInput {
286
287         private String baseURI = null;
288         private InputStream byteStream = null;
289         private boolean certifiedText = false;
290         private Reader characterStream = null;
291         private String encoding = null;
292         private String publicId = null;
293         private String stringData = null;
294         private String systemId = null;
295
296         /**
297          * コンストラクタ。
298          */
299         LSInputImpl(){
300             super();
301             return;
302         }
303
304         /**
305          * {@inheritDoc}
306          * @return {@inheritDoc}
307          */
308         @Override
309         public String getBaseURI(){
310             return this.baseURI;
311         }
312
313         /**
314          * {@inheritDoc}
315          * @param baseURI {@inheritDoc}
316          */
317         @Override
318         public void setBaseURI(String baseURI){
319             this.baseURI = baseURI;
320             return;
321         }
322
323         /**
324          * {@inheritDoc}
325          * @return {@inheritDoc}
326          */
327         @Override
328         public InputStream getByteStream(){
329             return this.byteStream;
330         }
331
332         /**
333          * {@inheritDoc}
334          * @param byteStream {@inheritDoc}
335          */
336         @Override
337         public void setByteStream(InputStream byteStream){
338             this.byteStream = byteStream;
339         }
340
341         /**
342          * {@inheritDoc}
343          * @return {@inheritDoc}
344          */
345         @Override
346         public boolean getCertifiedText(){
347             return this.certifiedText;
348         }
349
350         /**
351          * {@inheritDoc}
352          * @param certifiedText {@inheritDoc}
353          */
354         @Override
355         public void setCertifiedText(boolean certifiedText){
356             this.certifiedText = certifiedText;
357             return;
358         }
359
360         /**
361          * {@inheritDoc}
362          * @return {@inheritDoc}
363          */
364         @Override
365         public Reader getCharacterStream(){
366             return this.characterStream;
367         }
368
369         /**
370          * {@inheritDoc}
371          * @param characterStream {@inheritDoc}
372          */
373         @Override
374         public void setCharacterStream(Reader characterStream){
375             this.characterStream = characterStream;
376         }
377
378         /**
379          * {@inheritDoc}
380          * @return {@inheritDoc}
381          */
382         @Override
383         public String getEncoding(){
384             return this.encoding;
385         }
386
387         /**
388          * {@inheritDoc}
389          * @param encoding {@inheritDoc}
390          */
391         @Override
392         public void setEncoding(String encoding){
393             this.encoding = encoding;
394             return;
395         }
396
397         /**
398          * {@inheritDoc}
399          * @return {@inheritDoc}
400          */
401         @Override
402         public String getPublicId(){
403             return this.publicId;
404         }
405
406         /**
407          * {@inheritDoc}
408          * @param publicId {@inheritDoc}
409          */
410         @Override
411         public void setPublicId(String publicId){
412             this.publicId = publicId;
413             return;
414         }
415
416         /**
417          * {@inheritDoc}
418          * @return {@inheritDoc}
419          */
420         @Override
421         public String getStringData(){
422             return this.stringData;
423         }
424
425         /**
426          * {@inheritDoc}
427          * @param stringData {@inheritDoc}
428          */
429         @Override
430         public void setStringData(String stringData){
431             this.stringData = stringData;
432             return;
433         }
434
435         /**
436          * {@inheritDoc}
437          * @return {@inheritDoc}
438          */
439         @Override
440         public String getSystemId(){
441             return this.systemId;
442         }
443
444         /**
445          * {@inheritDoc}
446          * @param systemId {@inheritDoc}
447          */
448         @Override
449         public void setSystemId(String systemId){
450             this.systemId = systemId;
451             return;
452         }
453
454     }
455
456 }