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 SECURITY_ANNOTATION = 1744426972;
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 DECODE_LINE_FEED = -1764519240;
81    private static final int SCRIPT = -907685685;
82    private static final int STYLE = 109780401;
83    private static final int PROPERTIES = -926053069;
84    private static final int ENTRY = 96667762;
85    private static final int MIME_TYPES = 1081186720;
86    private static final int MIME_TYPE = -242217677;
87    private static final int EXTENSION = -612557761;
88    private static final int TYPE = 3575610;
89  
90    private TobagoConfigFragment tobagoConfig;
91    private RendererConfig currentRenderer;
92    private ThemeImpl currentTheme;
93    private Boolean production;
94    private boolean exclude;
95    private StringBuilder buffer;
96    private Properties properties;
97    private String entryKey;
98    private String extension;
99    private String type;
100 
101   private Stack<String> stack;
102 
103   public TobagoConfigParser() {
104   }
105 
106   public TobagoConfigFragment parse(final URL url)
107       throws IOException, SAXException, ParserConfigurationException, URISyntaxException {
108 
109     if (LOG.isInfoEnabled()) {
110       LOG.info("Parsing configuration file: '{}'", url);
111     }
112 
113     final TobagoConfigVersion version = new TobagoConfigVersion(url);
114 
115     // todo: Is there a solution that validate with both, DTD and XSD?
116 
117     if (version.isSchema()) {
118       validate(url, version);
119     }
120 
121     InputStream inputStream = null;
122     try {
123       inputStream = url.openStream();
124       final SAXParserFactory factory = SAXParserFactory.newInstance();
125       if (!version.isSchema()) {
126         factory.setValidating(true);
127       }
128       final SAXParser saxParser = factory.newSAXParser();
129       saxParser.parse(inputStream, this);
130     } finally {
131       IoUtils.closeQuietly(inputStream);
132     }
133     return tobagoConfig;
134   }
135 
136   @Override
137   public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
138     super.ignorableWhitespace(ch, start, length);
139   }
140 
141   @Override
142   public void startDocument() throws SAXException {
143 
144     buffer = new StringBuilder();
145     stack = new Stack<String>();
146   }
147 
148   @Override
149   public void endDocument() throws SAXException {
150     assert stack.empty();
151     stack = null;
152   }
153 
154   @Override
155   public void startElement(final String uri, final String localName, final String qName, final Attributes attributes)
156       throws SAXException {
157 
158     // No unused content should be collected, specially text mixed with tags.
159     assert buffer.toString().trim().length() == 0;
160 
161     buffer.setLength(0);
162     stack.add(qName);
163 
164     switch (qName.hashCode()) {
165 
166       case TOBAGO_CONFIG:
167         tobagoConfig = new TobagoConfigFragment();
168         break;
169 
170       case CONTENT_SECURITY_POLICY:
171         final String mode = attributes.getValue("mode");
172         tobagoConfig.setContentSecurityPolicy(new ContentSecurityPolicy(mode));
173         break;
174 
175       case RENDERERS:
176         if (currentTheme != null) {
177           currentTheme.setRenderersConfig(new RenderersConfigImpl());
178         } else {
179           tobagoConfig.setRenderersConfig(new RenderersConfigImpl());
180         }
181         break;
182 
183       case RENDERER:
184         currentRenderer = new RendererConfig();
185         break;
186 
187       case THEME_DEFINITION:
188         currentTheme = new ThemeImpl();
189         tobagoConfig.addThemeDefinition(currentTheme);
190         break;
191 
192       case RESOURCES:
193         production = Boolean.parseBoolean(attributes.getValue("production"));
194         break;
195 
196       case EXCLUDES:
197         exclude = true;
198         break;
199 
200       case SCRIPT:
201         final ThemeScript script = new ThemeScript();
202         script.setName(attributes.getValue("name"));
203         if (production) {
204           currentTheme.getProductionResources().addScript(script, exclude);
205         } else {
206           currentTheme.getResources().addScript(script, exclude);
207         }
208         break;
209 
210       case STYLE:
211         final ThemeStyle style = new ThemeStyle();
212         style.setName(attributes.getValue("name"));
213         if (production) {
214           currentTheme.getProductionResources().addStyle(style, exclude);
215         } else {
216           currentTheme.getResources().addStyle(style, exclude);
217         }
218         break;
219 
220       case PROPERTIES:
221         properties = new Properties();
222         break;
223 
224       case ENTRY:
225         entryKey = attributes.getValue("key");
226         break;
227 
228       case NAME:
229       case ORDERING:
230       case BEFORE:
231       case AFTER:
232       case THEME_CONFIG:
233       case DEFAULT_THEME:
234       case SUPPORTED_THEME:
235       case SUPPORTED_MARKUP:
236       case MARKUP:
237       case CREATE_SESSION_SECRET:
238       case CHECK_SESSION_SECRET:
239       case SECURITY_ANNOTATION:
240       case PREVENT_FRAME_ATTACKS:
241       case SET_NOSNIFF_HEADER:
242       case DIRECTIVE:
243       case THEME_DEFINITIONS:
244       case DISPLAY_NAME:
245       case VERSIONED:
246       case FALLBACK:
247       case SANITIZER:
248       case SANITIZER_CLASS:
249       case MIME_TYPES:
250       case MIME_TYPE:
251       case EXTENSION:
252       case TYPE:
253         break;
254 
255       default:
256         LOG.warn("Ignoring unknown start tag <" + qName + "> with hashCode=" + qName.hashCode());
257     }
258   }
259 
260   @Override
261   public void characters(final char[] ch, final int start, final int length) throws SAXException {
262     buffer.append(ch, start, length);
263   }
264 
265   @Override
266   public void endElement(final String uri, final String localName, final String qName) throws SAXException {
267     assert qName.equals(stack.peek());
268 
269     final String text = buffer.toString().trim();
270     buffer.setLength(0);
271 
272     switch (qName.hashCode()) {
273 
274       case NAME:
275         final String parent = stack.get(stack.size() - 2);
276         switch (parent.hashCode()) {
277 
278           case TOBAGO_CONFIG:
279             tobagoConfig.setName(text);
280             break;
281 
282           case BEFORE:
283             tobagoConfig.addBefore(text);
284             break;
285 
286           case AFTER:
287             tobagoConfig.addAfter(text);
288             break;
289 
290           case RENDERER:
291             currentRenderer.setName(text);
292             if (currentTheme != null) {
293               ((RenderersConfigImpl) currentTheme.getRenderersConfig()).addRenderer(currentRenderer);
294             } else {
295               ((RenderersConfigImpl) tobagoConfig.getRenderersConfig()).addRenderer(currentRenderer);
296             }
297             break;
298 
299           case THEME_DEFINITION:
300             currentTheme.setName(text);
301             break;
302 
303           default:
304             LOG.warn("Ignoring unknown parent <" + qName + "> of tag <name>");
305         }
306         break;
307 
308       case DEFAULT_THEME:
309         tobagoConfig.setDefaultThemeName(text);
310         break;
311 
312       case SUPPORTED_THEME:
313         tobagoConfig.addSupportedThemeName(text);
314         break;
315 
316       case CREATE_SESSION_SECRET:
317         tobagoConfig.setCreateSessionSecret(text);
318         break;
319 
320       case CHECK_SESSION_SECRET:
321         tobagoConfig.setCheckSessionSecret(text);
322         break;
323 
324       case PREVENT_FRAME_ATTACKS:
325         tobagoConfig.setPreventFrameAttacks(Boolean.parseBoolean(text));
326         break;
327 
328       case SET_NOSNIFF_HEADER:
329         tobagoConfig.setSetNosniffHeader(Boolean.parseBoolean(text));
330         break;
331 
332       case SECURITY_ANNOTATION:
333         tobagoConfig.setSecurityAnnotation(SecurityAnnotation.valueOf(text));
334         break;
335 
336       case DIRECTIVE:
337         tobagoConfig.getContentSecurityPolicy().getDirectiveList().add(text);
338         break;
339 
340       case MARKUP:
341         currentRenderer.addSupportedMarkup(text);
342         break;
343 
344       case DISPLAY_NAME:
345         currentTheme.setDisplayName(text);
346         break;
347 
348       case FALLBACK:
349         currentTheme.setFallbackName(text);
350         break;
351 
352       case THEME_DEFINITION:
353         currentTheme = null;
354         break;
355 
356       case VERSIONED:
357         currentTheme.setVersioned(Boolean.parseBoolean(text));
358         break;
359 
360       case RESOURCES:
361         production = null;
362         break;
363 
364       case EXCLUDES:
365         exclude = false;
366         break;
367 
368       case SANITIZER_CLASS:
369         tobagoConfig.setSanitizerClass(text);
370         break;
371 
372       case SANITIZER:
373         if (properties != null) {
374           tobagoConfig.setSanitizerProperties(properties);
375         }
376         properties = null;
377         break;
378 
379       case DECODE_LINE_FEED:
380         tobagoConfig.setDecodeLineFeed(Boolean.parseBoolean(text));
381         break;
382 
383       case ENTRY:
384         properties.setProperty(entryKey, text);
385         entryKey = null;
386         break;
387 
388       case EXTENSION:
389         extension = text;
390         break;
391 
392       case TYPE:
393         type = text;
394         break;
395 
396       case MIME_TYPE:
397         tobagoConfig.addMimeType(extension, type);
398         break;
399 
400       case TOBAGO_CONFIG:
401       case THEME_CONFIG:
402       case ORDERING:
403       case BEFORE:
404       case AFTER:
405       case SUPPORTED_MARKUP:
406       case CONTENT_SECURITY_POLICY:
407       case THEME_DEFINITIONS:
408       case RENDERERS:
409       case RENDERER:
410       case SCRIPT:
411       case STYLE:
412       case PROPERTIES:
413       case MIME_TYPES:
414         break;
415 
416       default:
417         LOG.warn("Ignoring unknown end tag <" + qName + ">");
418     }
419 
420     stack.pop();
421   }
422 
423   @Override
424   public void warning(final SAXParseException e) throws SAXException {
425     throw e;
426   }
427 
428   @Override
429   public void error(final SAXParseException e) throws SAXException {
430     throw e;
431   }
432 
433   @Override
434   public void fatalError(final SAXParseException e) throws SAXException {
435     throw e;
436   }
437 
438   private void validate(final URL url, final TobagoConfigVersion version)
439       throws URISyntaxException, SAXException, IOException {
440 
441     final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
442     final Schema schema;
443     if ("4.0".equals(version.getVersion())) {
444       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_4_0));
445     } else if ("3.0".equals(version.getVersion())) {
446       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_3_0));
447     } else if ("2.0.6".equals(version.getVersion())) {
448       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_2_0_6));
449     } else if ("2.0".equals(version.getVersion())) {
450       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_2_0));
451     } else if ("1.6".equals(version.getVersion())) {
452       LOG.warn("Using deprecated schema with version attribute 1.6 in file: '" + url + "'");
453       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_1_6));
454     } else if ("1.5".equals(version.getVersion())) {
455       schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_1_5));
456     } else {
457       throw new SAXException("Using unknown version attribute '" + version.getVersion() + "' in file: '" + url + "'");
458     }
459     final Validator validator = schema.newValidator();
460     final Source source = new StreamSource(url.openStream());
461 
462     validator.validate(source);
463   }
464 
465 }