OSDN Git Service

only built-in XML xsd file will be resolved by resolver.
[mikutoga/TogaGem.git] / src / main / java / jp / sfjp / mikutoga / xml / SchemaUtil.java
1 /*
2  * xml schema utility
3  *
4  * License : The MIT License
5  * Copyright(c) 2013 MikuToga Partners
6  */
7
8 package jp.sfjp.mikutoga.xml;
9
10 import java.io.BufferedInputStream;
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.net.MalformedURLException;
14 import java.net.URI;
15 import java.net.URL;
16 import java.util.ArrayList;
17 import java.util.List;
18 import javax.xml.XMLConstants;
19 import javax.xml.transform.Source;
20 import javax.xml.transform.stream.StreamSource;
21 import javax.xml.validation.Schema;
22 import javax.xml.validation.SchemaFactory;
23 import org.w3c.dom.ls.LSResourceResolver;
24 import org.xml.sax.SAXException;
25 import org.xml.sax.SAXNotRecognizedException;
26 import org.xml.sax.SAXNotSupportedException;
27
28 /**
29  * XML schema (XSD) utilities.
30  */
31 public final class SchemaUtil {
32
33
34     /** XML Schema. */
35     public static final String SCHEMA_XML =
36             "http://www.w3.org/2001/xml.xsd";
37
38     /** XSD namespace. */
39     public static final String NS_XSD =
40             "http://www.w3.org/2001/XMLSchema-instance";
41
42     private static final String LOCAL_SCHEMA_XML =
43             "resources/xmlspace.xsd";
44
45     private static final URI URI_XSD_ORIG;
46     private static final URI URI_XSD_LOCAL;
47
48     private static final String ALLOWED_USCHEMA = "http";
49
50     private static final Class<?> THISCLASS = SchemaUtil.class;
51
52
53     static{
54         URL redirectRes = THISCLASS.getResource(LOCAL_SCHEMA_XML);
55         String redirectResName = redirectRes.toString();
56
57         URI_XSD_ORIG  = URI.create(SCHEMA_XML);
58         URI_XSD_LOCAL = URI.create(redirectResName);
59
60         assert ALLOWED_USCHEMA.equalsIgnoreCase(URI_XSD_ORIG.getScheme());
61     }
62
63
64     /**
65      * Hidden constructor.
66      */
67     private SchemaUtil(){
68         assert false;
69         throw new AssertionError();
70     }
71
72
73     /**
74      * build xml.xsd redirection info.
75      *
76      * @return resolver
77      */
78     public static XmlResourceResolver buildXmlXsdResolver(){
79         XmlResourceResolver result = new XmlResourceResolver();
80         result.putRedirected(URI_XSD_ORIG, URI_XSD_LOCAL);
81         return result;
82     }
83
84     /**
85      * Build SchemaFactory for XML Schema but safety.
86      *
87      * <p>Includes some considerations for XXE vulnerabilities.
88      *
89      * <p>Restrict access to
90      * External Entity Reference &amp; external DTDs
91      * in xml schema file.
92      *
93      * <p>Restrict access to External schema file access in xml schema file,
94      * but HTTP access is allowed.
95      * This special limit considers access to
96      * importing http://www.w3.org/2001/xml.xsd
97      * in top of common xml schema file.
98      *
99      * @return schema factory
100      */
101     public static SchemaFactory newSchemaFactory(){
102         SchemaFactory schemaFactory;
103         schemaFactory = SchemaFactory.newInstance(
104                 XMLConstants.W3C_XML_SCHEMA_NS_URI);
105
106         try{
107             // Prevent denial of service attack.
108             schemaFactory.setFeature(
109                     XMLConstants.FEATURE_SECURE_PROCESSING, true);
110         }catch(SAXNotRecognizedException | SAXNotSupportedException e){
111             // FEATURE MUST BE SUPPORTED
112             assert false;
113         }
114
115         try{
116             // Disallow external entity reference &amp; external DTD access.
117             schemaFactory.setProperty(
118                     XMLConstants.ACCESS_EXTERNAL_DTD, "");
119             // Allow only HTTP external schema file.
120             schemaFactory.setProperty(
121                     XMLConstants.ACCESS_EXTERNAL_SCHEMA, ALLOWED_USCHEMA);
122         }catch(SAXNotRecognizedException | SAXNotSupportedException e){
123             // PROPERTY MUST BE SUPPORTED JAXP1.5 or later
124             assert false;
125         }
126
127         LSResourceResolver resolver = buildXmlXsdResolver();
128         schemaFactory.setResourceResolver(resolver);
129
130         schemaFactory.setErrorHandler(BotherHandler.HANDLER);
131
132         return schemaFactory;
133     }
134
135     /**
136      * ローカルリソースをSourceに変換する。
137      * @param resource ローカルリソース
138      * @return XML Source
139      * @throws MalformedURLException 不正なURI
140      * @throws IOException オープンエラー
141      */
142     private static Source toLocalSource(LocalXmlResource resource)
143             throws MalformedURLException, IOException{
144         URI localUri = resource.getLocalResource();
145         URL localUrl = localUri.toURL();
146
147         InputStream is = localUrl.openStream();
148         is = new BufferedInputStream(is);
149
150         Source result = new StreamSource(is);
151         return result;
152     }
153
154     /**
155      * ローカルリソース群をSource群に変換する。
156      * @param resArray ローカルリソースURI並び
157      * @return XML Source並び
158      * @throws MalformedURLException 不正なURI
159      * @throws IOException オープンエラー
160      */
161     private static Source[] toLocalSourceArray(LocalXmlResource... resArray)
162             throws MalformedURLException, IOException{
163         List<Source> sourceList = new ArrayList<>(resArray.length);
164
165         for(LocalXmlResource resource : resArray){
166             Source localSource = toLocalSource(resource);
167             sourceList.add(localSource);
168         }
169
170         Source[] result = new Source[sourceList.size()];
171         result = sourceList.toArray(result);
172         return result;
173     }
174
175     /**
176      * ローカルスキーマをロードする。
177      *
178      * <p>任意のリゾルバを指定可能
179      *
180      * @param resArray ローカルスキーマ情報並び
181      * @return スキーマ
182      */
183     public static Schema newSchema(LocalXmlResource... resArray){
184         Source[] sources;
185         try{
186             sources = toLocalSourceArray(resArray);
187         }catch(IOException e){                   // ビルド障害
188             assert false;
189             throw new AssertionError(e);
190         }
191
192         SchemaFactory schemaFactory = newSchemaFactory();
193
194         Schema result;
195         try{
196             if(sources.length <= 0){
197                 // ドキュメント埋め込みスキーマURLにリゾルバ経由でアクセス
198                 result = schemaFactory.newSchema();
199             }else{
200                 result = schemaFactory.newSchema(sources);
201             }
202         }catch(SAXException e){   // Build error
203             assert false;
204             throw new AssertionError(e);
205         }
206
207         // TODO: Sourceを閉めるのは誰の責務?
208
209         return result;
210     }
211
212 }