View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.internal.config;
21  
22  import org.apache.myfaces.tobago.context.ThemeImpl;
23  import org.apache.myfaces.tobago.context.ThemeScript;
24  import org.apache.myfaces.tobago.context.ThemeStyle;
25  import org.apache.myfaces.tobago.internal.util.IoUtils;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  import org.xml.sax.Attributes;
29  import org.xml.sax.SAXException;
30  import org.xml.sax.SAXParseException;
31  
32  import javax.xml.XMLConstants;
33  import javax.xml.parsers.ParserConfigurationException;
34  import javax.xml.parsers.SAXParser;
35  import javax.xml.parsers.SAXParserFactory;
36  import javax.xml.transform.Source;
37  import javax.xml.transform.stream.StreamSource;
38  import javax.xml.validation.Schema;
39  import javax.xml.validation.SchemaFactory;
40  import javax.xml.validation.Validator;
41  import java.io.IOException;
42  import java.io.InputStream;
43  import java.net.URISyntaxException;
44  import java.net.URL;
45  import java.util.Properties;
46  import java.util.Stack;
47  
48  public class TobagoConfigParser extends TobagoConfigEntityResolver {
49  
50    private static final Logger LOG = LoggerFactory.getLogger(TobagoConfigParser.class);
51  
52    private static final int TOBAGO_CONFIG = -1498874611;
53    private static final int NAME = 3373707;
54    private static final int ORDERING = 1234314708;
55    private static final int BEFORE = -1392885889;
56    private static final int AFTER = 92734940;
57    private static final int THEME_CONFIG = 1930630086;
58    private static final int DEFAULT_THEME = -114431171;
59    private static final int SUPPORTED_THEME = -822303766;
60    private static final int CREATE_SESSION_SECRET = 413906616;
61    private static final int CHECK_SESSION_SECRET = 275994924;
62    private static final int PREVENT_FRAME_ATTACKS = 270456726;
63    private static final int SET_NOSNIFF_HEADER = -1238451304;
64    private static final int CONTENT_SECURITY_POLICY = 1207440139;
65    private static final int CHECK_SECURITY_ANNOTATIONS = -1870701636;
66    private static final int DIRECTIVE = -962590641;
67    private static final int RENDERERS = 1839650832;
68    private static final int RENDERER = -494845757;
69    private static final int SUPPORTED_MARKUP = 71904295;
70    private static final int MARKUP = -1081305560;
71    private static final int THEME_DEFINITIONS = -255617156;
72    private static final int THEME_DEFINITION = 1515774935;
73    private static final int DISPLAY_NAME = 1568910518;
74    private static final int FALLBACK = 761243362;
75    private static final int VERSIONED = -1407102089;
76    private static final int RESOURCES = -1983070683;
77    private static final int EXCLUDES = 1994055129;
78    private static final int SANITIZER = 1807639849;
79    private static final int SANITIZER_CLASS = -974266412;
80    private static final int SCRIPT = -907685685;
81    private static final int STYLE = 109780401;
82    private static final int PROPERTIES = -926053069;
83    private static final int ENTRY = 96667762;
84    private static final int MIME_TYPES = 1081186720;
85    private static final int MIME_TYPE = -242217677;
86    private static final int EXTENSION = -612557761;
87    private static final int TYPE = 3575610;
88  
89    private TobagoConfigFragment tobagoConfig;
90    private RendererConfig currentRenderer;
91    private ThemeImpl currentTheme;
92    private Boolean production;
93    private boolean exclude;
94    private StringBuilder buffer;
95    private Properties properties;
96    private String entryKey;
97    private String extension;
98    private String type;
99  
100   private Stack<String> stack;
101 
102   public TobagoConfigParser() {
103   }
104 
105   public TobagoConfigFragment parse(final URL url)
106       throws IOException, SAXException, ParserConfigurationException, URISyntaxException {
107 
108     if (LOG.isInfoEnabled()) {
109       LOG.info("Parsing configuration file: '{}'", url);
110     }
111 
112     final TobagoConfigVersion version = new TobagoConfigVersion(url);
113 
114     // todo: Is there a solution that validate with both, DTD and XSD?
115 
116     if (version.isSchema()) {
117       validate(url, version);
118     }
119 
120     InputStream inputStream = null;
121     try {
122       inputStream = url.openStream();
123       final SAXParserFactory factory = SAXParserFactory.newInstance();
124       if (!version.isSchema()) {
125         factory.setValidating(true);
126       }
127       final SAXParser saxParser = factory.newSAXParser();
128       saxParser.parse(inputStream, this);
129     } finally {
130       IoUtils.closeQuietly(inputStream);
131     }
132     return tobagoConfig;
133   }
134 
135   @Override
136   public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
137     super.ignorableWhitespace(ch, start, length);
138   }
139 
140   @Override
141   public void startDocument() throws SAXException {
142 
143     buffer = new StringBuilder();
144     stack = new Stack<String>();
145   }
146 
147   @Override
148   public void endDocument() throws SAXException {
149     assert stack.empty();
150     stack = null;
151   }
152 
153   @Override
154   public void startElement(final String uri, final String localName, final String qName, final Attributes attributes)
155       throws SAXException {
156 
157     // No unused content should be collected, specially text mixed with tags.
158     assert buffer.toString().trim().length() == 0;
159 
160     buffer.setLength(0);
161     stack.add(qName);
162 
163     switch (qName.hashCode()) {
164 
165       case TOBAGO_CONFIG:
166         tobagoConfig = new TobagoConfigFragment();
167         break;
168 
169       case CONTENT_SECURITY_POLICY:
170         final String mode = attributes.getValue("mode");
171         tobagoConfig.setContentSecurityPolicy(new ContentSecurityPolicy(mode));
172         break;
173 
174       case RENDERERS:
175         if (currentTheme != null) {
176           currentTheme.setRenderersConfig(new RenderersConfigImpl());
177         } else {
178           tobagoConfig.setRenderersConfig(new RenderersConfigImpl());
179         }
180         break;
181 
182       case RENDERER:
183         currentRenderer = new RendererConfig();
184         break;
185 
186       case THEME_DEFINITION:
187         currentTheme = new ThemeImpl();
188         tobagoConfig.addThemeDefinition(currentTheme);
189         break;
190 
191       case RESOURCES:
192         production = Boolean.parseBoolean(attributes.getValue("production"));
193         break;
194 
195       case EXCLUDES:
196         exclude = true;
197         break;
198 
199       case SCRIPT:
200         final ThemeScript script = new ThemeScript();
201         script.setName(attributes.getValue("name"));
202         if (production) {
203           currentTheme.getProductionResources().addScript(script, exclude);
204         } else {
205           currentTheme.getResources().addScript(script, exclude);
206         }
207         break;
208 
209       case STYLE:
210         final ThemeStyle style = new ThemeStyle();
211         style.setName(attributes.getValue("name"));
212         if (production) {
213           currentTheme.getProductionResources().addStyle(style, exclude);
214         } else {
215           currentTheme.getResources().addStyle(style, exclude);
216         }
217         break;
218 
219       case PROPERTIES:
220         properties = new Properties();
221         break;
222 
223       case ENTRY:
224         entryKey = attributes.getValue("key");
225         break;
226 
227       case NAME:
228       case ORDERING:
229       case BEFORE:
230       case AFTER:
231       case THEME_CONFIG:
232       case DEFAULT_THEME:
233       case SUPPORTED_THEME:
234       case SUPPORTED_MARKUP:
235       case MARKUP:
236       case CREATE_SESSION_SECRET:
237       case CHECK_SESSION_SECRET:
238       case CHECK_SECURITY_ANNOTATIONS:
239       case PREVENT_FRAME_ATTACKS:
240       case SET_NOSNIFF_HEADER:
241       case DIRECTIVE:
242       case THEME_DEFINITIONS:
243       case DISPLAY_NAME:
244       case VERSIONED:
245       case FALLBACK:
246       case SANITIZER:
247       case SANITIZER_CLASS:
248       case MIME_TYPES:
249       case MIME_TYPE:
250       case EXTENSION:
251       case TYPE:
252         break;
253 
254       default:
255         LOG.warn("Ignoring unknown start tag <" + qName + "> with hashCode=" + qName.hashCode());
256     }
257   }
258 
259   @Override
260   public void characters(final char[] ch, final int start, final int length) throws SAXException {
261     buffer.append(ch, start, length);
262   }
263 
264   @Override
265   public void endElement(final String uri, final String localName, final String qName) throws SAXException {
266     assert qName.equals(stack.peek());
267 
268     final String text = buffer.toString().trim();
269     buffer.setLength(0);
270 
271     switch (qName.hashCode()) {
272 
273       case NAME:
274         final String parent = stack.get(stack.size() - 2);
275         switch (parent.hashCode()) {
276 
277           case TOBAGO_CONFIG:
278             tobagoConfig.setName(text);
279             break;
280 
281           case BEFORE:
282             tobagoConfig.addBefore(text);
283             break;
284 
285           case AFTER:
286             tobagoConfig.addAfter(text);
287             break;
288 
289           case RENDERER:
290             currentRenderer.setName(text);
291             if (currentTheme != null) {
292               ((RenderersConfigImpl) currentTheme.getRenderersConfig()).addRenderer(currentRenderer);
293             } else {
294               ((RenderersConfigImpl) tobagoConfig.getRenderersConfig()).addRenderer(currentRenderer);
295             }
296             break;
297 
298           case THEME_DEFINITION:
299             currentTheme.setName(text);
300             break;
301 
302           default:
303             LOG.warn("Ignoring unknown parent <" + qName + "> of tag <name>");
304         }
305         break;
306 
307       case DEFAULT_THEME:
308         tobagoConfig.setDefaultThemeName(text);
309         break;
310 
311       case SUPPORTED_THEME:
312         tobagoConfig.addSupportedThemeName(text);
313         break;
314 
315       case CREATE_SESSION_SECRET:
316         tobagoConfig.setCreateSessionSecret(text);
317         break;
318 
319       case CHECK_SESSION_SECRET:
320         tobagoConfig.setCheckSessionSecret(text);
321         break;
322 
323       case PREVENT_FRAME_ATTACKS:
324         tobagoConfig.setPreventFrameAttacks(Boolean.parseBoolean(text));
325         break;
326 
327       case SET_NOSNIFF_HEADER:
328         tobagoConfig.setSetNosniffHeader(Boolean.parseBoolean(text));
329         break;
330 
331       case CHECK_SECURITY_ANNOTATIONS:
332         tobagoConfig.setCheckSecurityAnnotations(Boolean.parseBoolean(text));
333         break;
334 
335       case DIRECTIVE:
336         tobagoConfig.getContentSecurityPolicy().getDirectiveList().add(text);
337         break;
338 
339       case MARKUP:
340         currentRenderer.addSupportedMarkup(text);
341         break;
342 
343       case DISPLAY_NAME:
344         currentTheme.setDisplayName(text);
345         break;
346 
347       case FALLBACK:
348         currentTheme.setFallbackName(text);
349         break;
350 
351       case THEME_DEFINITION:
352         currentTheme = null;
353         break;
354 
355       case VERSIONED:
356         currentTheme.setVersioned(Boolean.parseBoolean(text));
357         break;
358 
359       case RESOURCES:
360         production = null;
361         break;
362 
363       case EXCLUDES:
364         exclude = false;
365         break;
366 
367       case SANITIZER_CLASS:
368         tobagoConfig.setSanitizerClass(text);
369         break;
370 
371       case SANITIZER:
372         if (properties != null) {
373           tobagoConfig.setSanitizerProperties(properties);
374         }
375         properties = null;
376         break;
377 
378       case ENTRY:
379         properties.setProperty(entryKey, text);
380         entryKey = null;
381         break;
382 
383       case EXTENSION:
384         extension = text;
385         break;
386 
387       case TYPE:
388         type = text;
389         break;
390 
391       case MIME_TYPE:
392         tobagoConfig.addMimeType(extension, type);
393         break;
394 
395       case TOBAGO_CONFIG:
396       case THEME_CONFIG:
397       case ORDERING:
398       case BEFORE:
399       case AFTER:
400       case SUPPORTED_MARKUP:
401       case CONTENT_SECURITY_POLICY:
402       case THEME_DEFINITIONS:
403       case RENDERERS:
404       case RENDERER:
405       case SCRIPT:
406       case STYLE:
407       case PROPERTIES:
408       case MIME_TYPES:
409         break;
410 
411       default:
412         LOG.warn("Ignoring unknown end tag <" + qName + ">");
413     }
414 
415     stack.pop();
416   }
417 
418   @Override
419   public void warning(final SAXParseException e) throws SAXException {
420     throw e;
421   }
422 
423   @Override
424   public void error(final SAXParseException e) throws SAXException {
425     throw e;
426   }
427 
428   @Override
429   public void fatalError(final SAXParseException e) throws SAXException {
430     throw e;
431   }
432 
433   private void validate(final URL url, final TobagoConfigVersion version)
434       throws URISyntaxException, SAXException, IOException {
435 
436     final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
437     final Schema schema;
438     if ("3.1".equals(version.getVersion())) {
439       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_3_1));
440     } else if ("3.0".equals(version.getVersion())) {
441       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_3_0));
442     } else if ("2.0.6".equals(version.getVersion())) {
443       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_2_0_6));
444     } else if ("2.0".equals(version.getVersion())) {
445       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_2_0));
446     } else if ("1.6".equals(version.getVersion())) {
447       LOG.warn("Using deprecated schema with version attribute 1.6 in file: '" + url + "'");
448       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_1_6));
449     } else if ("1.5".equals(version.getVersion())) {
450       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_1_5));
451     } else {
452       throw new SAXException("Using unknown version attribute '" + version.getVersion() + "' in file: '" + url + "'");
453     }
454     final Validator validator = schema.newValidator();
455     final Source source = new StreamSource(url.openStream());
456 
457     validator.validate(source);
458   }
459 
460 }