OSDN Git Service

Move out xml-xsd info from 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スキーマの各種ビルダ。
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      * 隠しコンストラクタ。
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      * If HTTP access controll is needed, customize resolver yourself.
99      *
100      * @param resolver Custom resolver for reading xml schema.
101      *     Resolve reference to nothing if null.
102      * @return schema factory
103      */
104     public static SchemaFactory newSchemaFactory(
105             LSResourceResolver resolver ){
106         SchemaFactory schemaFactory;
107         schemaFactory = SchemaFactory.newInstance(
108                 XMLConstants.W3C_XML_SCHEMA_NS_URI);
109
110         try{
111             // Prevent denial of service attack.
112             schemaFactory.setFeature(
113                     XMLConstants.FEATURE_SECURE_PROCESSING, true);
114         }catch(SAXNotRecognizedException | SAXNotSupportedException e){
115             // FEATURE MUST BE SUPPORTED
116             assert false;
117         }
118
119         try{
120             // Disallow external entity reference &amp; external DTD access.
121             schemaFactory.setProperty(
122                     XMLConstants.ACCESS_EXTERNAL_DTD, "");
123             // Allow only HTTP external schema file.
124             schemaFactory.setProperty(
125                     XMLConstants.ACCESS_EXTERNAL_SCHEMA, ALLOWED_USCHEMA);
126         }catch(SAXNotRecognizedException | SAXNotSupportedException e){
127             // PROPERTY MUST BE SUPPORTED JAXP1.5 or later
128             assert false;
129         }
130
131         schemaFactory.setResourceResolver(resolver);
132
133         schemaFactory.setErrorHandler(BotherHandler.HANDLER);
134
135         return schemaFactory;
136     }
137
138     /**
139      * ローカルリソースをSourceに変換する。
140      * @param resource ローカルリソース
141      * @return XML Source
142      * @throws MalformedURLException 不正なURI
143      * @throws IOException オープンエラー
144      */
145     private static Source toLocalSource(LocalXmlResource resource)
146             throws MalformedURLException, IOException{
147         URI localUri = resource.getLocalResource();
148         URL localUrl = localUri.toURL();
149
150         InputStream is = localUrl.openStream();
151         is = new BufferedInputStream(is);
152
153         Source result = new StreamSource(is);
154         return result;
155     }
156
157     /**
158      * ローカルリソース群をSource群に変換する。
159      * @param resArray ローカルリソースURI並び
160      * @return XML Source並び
161      * @throws MalformedURLException 不正なURI
162      * @throws IOException オープンエラー
163      */
164     private static Source[] toLocalSourceArray(LocalXmlResource... resArray)
165             throws MalformedURLException, IOException{
166         List<Source> sourceList = new ArrayList<>(resArray.length);
167
168         for(LocalXmlResource resource : resArray){
169             Source localSource = toLocalSource(resource);
170             sourceList.add(localSource);
171         }
172
173         Source[] result = new Source[sourceList.size()];
174         result = sourceList.toArray(result);
175         return result;
176     }
177
178     /**
179      * ローカルスキーマをロードする。
180      *
181      * <p>任意のリゾルバを指定可能
182      *
183      * @param resolver リゾルバ
184      * @param resArray ローカルスキーマ情報並び
185      * @return スキーマ
186      */
187     public static Schema newSchema(XmlResourceResolver resolver,
188                                     LocalXmlResource... resArray ){
189         for(LocalXmlResource resource : resArray){
190             resolver.putRedirected(resource);
191         }
192
193         Source[] sources;
194         try{
195             sources = toLocalSourceArray(resArray);
196         }catch(IOException e){                   // ビルド障害
197             assert false;
198             throw new AssertionError(e);
199         }
200
201         SchemaFactory schemaFactory = newSchemaFactory(resolver);
202
203         Schema result;
204         try{
205             if(sources.length <= 0){
206                 // ドキュメント埋め込みスキーマURLにリゾルバ経由でアクセス
207                 result = schemaFactory.newSchema();
208             }else{
209                 result = schemaFactory.newSchema(sources);
210             }
211         }catch(SAXException e){   // Build error
212             assert false;
213             throw new AssertionError(e);
214         }
215
216         // TODO: Sourceを閉めるのは誰の責務?
217
218         return result;
219     }
220
221 }