1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.exception.TobagoConfigurationException;
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.lang.invoke.MethodHandles;
44 import java.net.URISyntaxException;
45 import java.net.URL;
46 import java.util.Properties;
47 import java.util.Stack;
48
49 public class TobagoConfigParser extends TobagoConfigEntityResolver {
50
51 private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
52
53 private static final int TOBAGO_CONFIG = -1498874611;
54 private static final int NAME = 3373707;
55 private static final int ORDERING = 1234314708;
56 private static final int BEFORE = -1392885889;
57 private static final int AFTER = 92734940;
58 private static final int THEME_CONFIG = 1930630086;
59 private static final int DEFAULT_THEME = -114431171;
60 private static final int SUPPORTED_THEME = -822303766;
61 private static final int CREATE_SESSION_SECRET = 413906616;
62 private static final int CHECK_SESSION_SECRET = 275994924;
63 private static final int PREVENT_FRAME_ATTACKS = 270456726;
64 private static final int SET_NOSNIFF_HEADER = -1238451304;
65 private static final int CONTENT_SECURITY_POLICY = 1207440139;
66 private static final int SECURITY_ANNOTATION = 1744426972;
67 private static final int DIRECTIVE = -962590641;
68
69
70
71 @Deprecated
72 private static final int RENDERERS = 1839650832;
73
74
75
76 @Deprecated
77 private static final int RENDERER = -494845757;
78
79
80
81 @Deprecated
82 private static final int SUPPORTED_MARKUP = 71904295;
83
84
85
86 @Deprecated
87 private static final int MARKUP = -1081305560;
88 private static final int THEME_DEFINITIONS = -255617156;
89 private static final int THEME_DEFINITION = 1515774935;
90 private static final int DISPLAY_NAME = 1568910518;
91 private static final int FALLBACK = 761243362;
92
93
94
95 @Deprecated
96 private static final int VERSIONED = -1407102089;
97 private static final int VERSION = 351608024;
98 private static final int RESOURCES = -1983070683;
99 private static final int INCLUDES = 90259659;
100 private static final int EXCLUDES = 1994055129;
101 private static final int SANITIZER = 1807639849;
102 private static final int SANITIZER_CLASS = -974266412;
103 private static final int DECODE_LINE_FEED = -1764519240;
104 private static final int SCRIPT = -907685685;
105 private static final int STYLE = 109780401;
106 private static final int PROPERTIES = -926053069;
107 private static final int ENTRY = 96667762;
108 private static final int MIME_TYPES = 1081186720;
109 private static final int MIME_TYPE = -242217677;
110 private static final int EXTENSION = -612557761;
111 private static final int TYPE = 3575610;
112
113 private static final String ATTR_MODE = "mode";
114 private static final String ATTR_PRODUCTION = "production";
115 private static final String ATTR_NAME = "name";
116 private static final String ATTR_KEY = "key";
117 private static final String ATTR_PRIORITY = "priority";
118 private static final String ATTR_TYPE = "type";
119
120 private static final int MAX_PRIORITY = 65536;
121
122 private TobagoConfigFragment tobagoConfig;
123 private ThemeImpl currentTheme;
124 private Boolean production;
125 private boolean exclude;
126 private StringBuilder buffer;
127 private Properties properties;
128 private String entryKey;
129 private String directiveName;
130 private String extension;
131 private String type;
132
133 private Stack<String> stack;
134
135 public TobagoConfigParser() {
136 }
137
138 public TobagoConfigFragment parse(final URL url)
139 throws IOException, SAXException, ParserConfigurationException, URISyntaxException {
140
141 if (LOG.isInfoEnabled()) {
142 LOG.info("Parsing configuration file: '{}'", url);
143 }
144
145 final TobagoConfigVersion version = new TobagoConfigVersion(url);
146
147
148
149 if (version.isSchema()) {
150 validate(url, version);
151 }
152
153 try (final InputStream inputStream = url.openStream()) {
154 final SAXParserFactory factory = SAXParserFactory.newInstance();
155 if (!version.isSchema()) {
156 factory.setValidating(true);
157 }
158 final SAXParser saxParser = factory.newSAXParser();
159 saxParser.parse(inputStream, this);
160 }
161 return tobagoConfig;
162 }
163
164 @Override
165 public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException {
166 super.ignorableWhitespace(ch, start, length);
167 }
168
169 @Override
170 public void startDocument() throws SAXException {
171
172 buffer = new StringBuilder();
173 stack = new Stack<>();
174 }
175
176 @Override
177 public void endDocument() throws SAXException {
178 assert stack.empty();
179 stack = null;
180 }
181
182 @Override
183 public void startElement(final String uri, final String localName, final String qName, final Attributes attributes)
184 throws SAXException {
185
186
187 assert buffer.toString().trim().length() == 0;
188
189 buffer.setLength(0);
190 stack.add(qName);
191
192 switch (qName.hashCode()) {
193
194 case TOBAGO_CONFIG:
195 tobagoConfig = new TobagoConfigFragment();
196 break;
197
198 case CONTENT_SECURITY_POLICY:
199 final String mode = attributes.getValue(ATTR_MODE);
200 tobagoConfig.setContentSecurityPolicy(new ContentSecurityPolicy(mode));
201 break;
202
203 case THEME_DEFINITION:
204 currentTheme = new ThemeImpl();
205 tobagoConfig.addThemeDefinition(currentTheme);
206 break;
207
208 case RESOURCES:
209 production = Boolean.parseBoolean(attributes.getValue(ATTR_PRODUCTION));
210 break;
211
212 case EXCLUDES:
213 exclude = true;
214 break;
215
216 case SCRIPT:
217 final ThemeScript script = new ThemeScript();
218 script.setName(attributes.getValue(ATTR_NAME));
219 script.setType(attributes.getValue(ATTR_TYPE));
220 final String scriptPriority = attributes.getValue(ATTR_PRIORITY);
221 script.setPriority(scriptPriority != null ? Integer.parseUnsignedInt(scriptPriority) : MAX_PRIORITY);
222 if (production) {
223 if (exclude) {
224 currentTheme.getProductionResources().addExcludeScript(script);
225 } else {
226 currentTheme.getProductionResources().addIncludeScript(script);
227 }
228 } else {
229 if (exclude) {
230 currentTheme.getDevelopmentResources().addExcludeScript(script);
231 } else {
232 currentTheme.getDevelopmentResources().addIncludeScript(script);
233 }
234 }
235 break;
236
237 case STYLE:
238 final ThemeStyle style = new ThemeStyle();
239 style.setName(attributes.getValue(ATTR_NAME));
240 final String stylePriority = attributes.getValue(ATTR_PRIORITY);
241 style.setPriority(stylePriority != null ? Integer.parseUnsignedInt(stylePriority) : MAX_PRIORITY);
242 if (production) {
243 if (exclude) {
244 currentTheme.getProductionResources().addExcludeStyle(style);
245 } else {
246 currentTheme.getProductionResources().addIncludeStyle(style);
247 }
248 } else {
249 if (exclude) {
250 currentTheme.getDevelopmentResources().addExcludeStyle(style);
251 } else {
252 currentTheme.getDevelopmentResources().addIncludeStyle(style);
253 }
254 }
255 break;
256
257 case PROPERTIES:
258 properties = new Properties();
259 break;
260
261 case ENTRY:
262 entryKey = attributes.getValue(ATTR_KEY);
263 break;
264
265 case DIRECTIVE:
266 directiveName = attributes.getValue(ATTR_NAME);
267 break;
268
269 case NAME:
270 case ORDERING:
271 case BEFORE:
272 case AFTER:
273 case THEME_CONFIG:
274 case DEFAULT_THEME:
275 case SUPPORTED_THEME:
276 case SUPPORTED_MARKUP:
277 case MARKUP:
278 case CREATE_SESSION_SECRET:
279 case CHECK_SESSION_SECRET:
280 case SECURITY_ANNOTATION:
281 case PREVENT_FRAME_ATTACKS:
282 case SET_NOSNIFF_HEADER:
283 case THEME_DEFINITIONS:
284 case DISPLAY_NAME:
285 case VERSION:
286 case VERSIONED:
287 case FALLBACK:
288 case SANITIZER:
289 case SANITIZER_CLASS:
290 case MIME_TYPES:
291 case MIME_TYPE:
292 case EXTENSION:
293 case TYPE:
294 case RENDERERS:
295 case RENDERER:
296 case INCLUDES:
297
298 break;
299
300 default:
301 LOG.warn("Ignoring unknown start tag <" + qName + "> with hashCode=" + qName.hashCode());
302 }
303 }
304
305 @Override
306 public void characters(final char[] ch, final int start, final int length) throws SAXException {
307 buffer.append(ch, start, length);
308 }
309
310 @Override
311 public void endElement(final String uri, final String localName, final String qName) throws SAXException {
312 assert qName.equals(stack.peek());
313
314 final String text = buffer.toString().trim();
315 buffer.setLength(0);
316
317 switch (qName.hashCode()) {
318
319 case NAME:
320 final String parent = stack.get(stack.size() - 2);
321 switch (parent.hashCode()) {
322
323 case TOBAGO_CONFIG:
324 tobagoConfig.setName(text);
325 break;
326
327 case BEFORE:
328 tobagoConfig.addBefore(text);
329 break;
330
331 case AFTER:
332 tobagoConfig.addAfter(text);
333 break;
334
335 case THEME_DEFINITION:
336 currentTheme.setName(text);
337 break;
338
339 case RENDERER:
340
341 break;
342
343 default:
344 LOG.warn("Ignoring unknown parent <" + parent + "> of tag <name>");
345 }
346 break;
347
348 case DEFAULT_THEME:
349 tobagoConfig.setDefaultThemeName(text);
350 break;
351
352 case SUPPORTED_THEME:
353 tobagoConfig.addSupportedThemeName(text);
354 break;
355
356 case CREATE_SESSION_SECRET:
357 tobagoConfig.setCreateSessionSecret(text);
358 break;
359
360 case CHECK_SESSION_SECRET:
361 tobagoConfig.setCheckSessionSecret(text);
362 break;
363
364 case PREVENT_FRAME_ATTACKS:
365 tobagoConfig.setPreventFrameAttacks(Boolean.parseBoolean(text));
366 break;
367
368 case SET_NOSNIFF_HEADER:
369 tobagoConfig.setSetNosniffHeader(Boolean.parseBoolean(text));
370 break;
371
372 case SECURITY_ANNOTATION:
373 tobagoConfig.setSecurityAnnotation(SecurityAnnotation.valueOf(text));
374 break;
375
376 case DIRECTIVE:
377 if (directiveName == null) {
378 final int i = text.indexOf(' ');
379 if (i < 1) {
380 throw new TobagoConfigurationException("CSP directive can't be parsed!");
381 }
382 tobagoConfig.getContentSecurityPolicy().addDirective(text.substring(0, i), text.substring(i + 1));
383 } else {
384 tobagoConfig.getContentSecurityPolicy().addDirective(directiveName, text);
385 }
386 directiveName = null;
387 break;
388
389 case DISPLAY_NAME:
390 currentTheme.setDisplayName(text);
391 break;
392
393 case FALLBACK:
394 currentTheme.setFallbackName(text);
395 break;
396
397 case THEME_DEFINITION:
398 currentTheme = null;
399 break;
400
401 case VERSION:
402 currentTheme.setVersion(text);
403 break;
404
405 case RESOURCES:
406 production = null;
407 break;
408
409 case EXCLUDES:
410 exclude = false;
411 break;
412
413 case SANITIZER_CLASS:
414 tobagoConfig.setSanitizerClass(text);
415 break;
416
417 case SANITIZER:
418 if (properties != null) {
419 tobagoConfig.setSanitizerProperties(properties);
420 }
421 properties = null;
422 break;
423
424 case DECODE_LINE_FEED:
425 tobagoConfig.setDecodeLineFeed(Boolean.parseBoolean(text));
426 break;
427
428 case ENTRY:
429 properties.setProperty(entryKey, text);
430 entryKey = null;
431 break;
432
433 case EXTENSION:
434 extension = text;
435 break;
436
437 case TYPE:
438 type = text;
439 break;
440
441 case MIME_TYPE:
442 tobagoConfig.addMimeType(extension, type);
443 break;
444
445 case TOBAGO_CONFIG:
446 case THEME_CONFIG:
447 case ORDERING:
448 case BEFORE:
449 case AFTER:
450 case SUPPORTED_MARKUP:
451 case CONTENT_SECURITY_POLICY:
452 case THEME_DEFINITIONS:
453 case RENDERERS:
454 case RENDERER:
455 case SCRIPT:
456 case STYLE:
457 case PROPERTIES:
458 case MIME_TYPES:
459 case MARKUP:
460 case INCLUDES:
461 case VERSIONED:
462
463 break;
464
465 default:
466 LOG.warn("Ignoring unknown end tag <" + qName + ">");
467 }
468
469 stack.pop();
470 }
471
472 @Override
473 public void warning(final SAXParseException e) throws SAXException {
474 throw e;
475 }
476
477 @Override
478 public void error(final SAXParseException e) throws SAXException {
479 throw e;
480 }
481
482 @Override
483 public void fatalError(final SAXParseException e) throws SAXException {
484 throw e;
485 }
486
487 private void validate(final URL url, final TobagoConfigVersion version)
488 throws URISyntaxException, SAXException, IOException {
489
490 final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
491 final Schema schema;
492 if ("5.0".equals(version.getVersion())) {
493 schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_5_0));
494 } else if ("4.0".equals(version.getVersion())) {
495 schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_4_0));
496 } else if ("3.0".equals(version.getVersion())) {
497 schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_3_0));
498 } else if ("2.0.6".equals(version.getVersion())) {
499 schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_2_0_6));
500 } else if ("2.0".equals(version.getVersion())) {
501 schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_2_0));
502 } else if ("1.6".equals(version.getVersion())) {
503 LOG.warn("Using deprecated schema with version attribute 1.6 in file: '" + url + "'");
504 schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_1_6));
505 } else if ("1.5".equals(version.getVersion())) {
506 schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_1_5));
507 } else {
508 throw new SAXException("Using unknown version attribute '" + version.getVersion() + "' in file: '" + url + "'");
509 }
510 final Validator validator = schema.newValidator();
511 final Source source = new StreamSource(url.openStream());
512
513 validator.validate(source);
514 }
515
516 }