| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.myfaces.tobago.internal.config; |
| |
| import org.apache.myfaces.tobago.context.ThemeImpl; |
| import org.apache.myfaces.tobago.context.ThemeScript; |
| import org.apache.myfaces.tobago.context.ThemeStyle; |
| import org.apache.myfaces.tobago.exception.TobagoConfigurationException; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.SAXParseException; |
| |
| import javax.xml.XMLConstants; |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.parsers.SAXParser; |
| import javax.xml.parsers.SAXParserFactory; |
| import javax.xml.transform.Source; |
| import javax.xml.transform.stream.StreamSource; |
| import javax.xml.validation.Schema; |
| import javax.xml.validation.SchemaFactory; |
| import javax.xml.validation.Validator; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.invoke.MethodHandles; |
| import java.net.URISyntaxException; |
| import java.net.URL; |
| import java.util.Properties; |
| import java.util.Stack; |
| |
| public class TobagoConfigParser extends TobagoConfigEntityResolver { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| |
| private static final int TOBAGO_CONFIG = -1498874611; |
| private static final int NAME = 3373707; |
| private static final int ORDERING = 1234314708; |
| private static final int BEFORE = -1392885889; |
| private static final int AFTER = 92734940; |
| private static final int THEME_CONFIG = 1930630086; |
| private static final int DEFAULT_THEME = -114431171; |
| private static final int SUPPORTED_THEME = -822303766; |
| private static final int CREATE_SESSION_SECRET = 413906616; |
| private static final int CHECK_SESSION_SECRET = 275994924; |
| private static final int PREVENT_FRAME_ATTACKS = 270456726; |
| private static final int SET_NOSNIFF_HEADER = -1238451304; |
| private static final int CONTENT_SECURITY_POLICY = 1207440139; |
| private static final int SECURITY_ANNOTATION = 1744426972; |
| private static final int DIRECTIVE = -962590641; |
| /** |
| * @deprecated since 4.0.0 |
| */ |
| @Deprecated |
| private static final int RENDERERS = 1839650832; |
| /** |
| * @deprecated since 4.0.0 |
| */ |
| @Deprecated |
| private static final int RENDERER = -494845757; |
| /** |
| * @deprecated since 4.0.0 |
| */ |
| @Deprecated |
| private static final int SUPPORTED_MARKUP = 71904295; |
| /** |
| * @deprecated since 4.0.0 |
| */ |
| @Deprecated |
| private static final int MARKUP = -1081305560; |
| private static final int THEME_DEFINITIONS = -255617156; |
| private static final int THEME_DEFINITION = 1515774935; |
| private static final int DISPLAY_NAME = 1568910518; |
| private static final int FALLBACK = 761243362; |
| /** |
| * @deprecated since 5.0.0 |
| */ |
| @Deprecated |
| private static final int VERSIONED = -1407102089; |
| private static final int VERSION = 351608024; |
| private static final int RESOURCES = -1983070683; |
| private static final int INCLUDES = 90259659; |
| private static final int EXCLUDES = 1994055129; |
| private static final int SANITIZER = 1807639849; |
| private static final int SANITIZER_CLASS = -974266412; |
| private static final int DECODE_LINE_FEED = -1764519240; |
| private static final int SCRIPT = -907685685; |
| private static final int STYLE = 109780401; |
| private static final int PROPERTIES = -926053069; |
| private static final int ENTRY = 96667762; |
| private static final int MIME_TYPES = 1081186720; |
| private static final int MIME_TYPE = -242217677; |
| private static final int EXTENSION = -612557761; |
| private static final int TYPE = 3575610; |
| |
| private static final String ATTR_MODE = "mode"; |
| private static final String ATTR_PRODUCTION = "production"; |
| private static final String ATTR_NAME = "name"; |
| private static final String ATTR_KEY = "key"; |
| private static final String ATTR_PRIORITY = "priority"; |
| private static final String ATTR_TYPE = "type"; |
| |
| private static final int MAX_PRIORITY = 65536; |
| |
| private TobagoConfigFragment tobagoConfig; |
| private ThemeImpl currentTheme; |
| private Boolean production; |
| private boolean exclude; |
| private StringBuilder buffer; |
| private Properties properties; |
| private String entryKey; |
| private String directiveName; |
| private String extension; |
| private String type; |
| |
| private Stack<String> stack; |
| |
| public TobagoConfigParser() { |
| } |
| |
| public TobagoConfigFragment parse(final URL url) |
| throws IOException, SAXException, ParserConfigurationException, URISyntaxException { |
| |
| if (LOG.isInfoEnabled()) { |
| LOG.info("Parsing configuration file: '{}'", url); |
| } |
| |
| final TobagoConfigVersion version = new TobagoConfigVersion(url); |
| |
| // todo: Is there a solution that validate with both, DTD and XSD? |
| |
| if (version.isSchema()) { |
| validate(url, version); |
| } |
| |
| try (final InputStream inputStream = url.openStream()) { |
| final SAXParserFactory factory = SAXParserFactory.newInstance(); |
| if (!version.isSchema()) { |
| factory.setValidating(true); |
| } |
| final SAXParser saxParser = factory.newSAXParser(); |
| saxParser.parse(inputStream, this); |
| } |
| return tobagoConfig; |
| } |
| |
| @Override |
| public void ignorableWhitespace(final char[] ch, final int start, final int length) throws SAXException { |
| super.ignorableWhitespace(ch, start, length); |
| } |
| |
| @Override |
| public void startDocument() throws SAXException { |
| |
| buffer = new StringBuilder(); |
| stack = new Stack<>(); |
| } |
| |
| @Override |
| public void endDocument() throws SAXException { |
| assert stack.empty(); |
| stack = null; |
| } |
| |
| @Override |
| public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) |
| throws SAXException { |
| |
| // No unused content should be collected, specially text mixed with tags. |
| assert buffer.toString().trim().length() == 0; |
| |
| buffer.setLength(0); |
| stack.add(qName); |
| |
| switch (qName.hashCode()) { |
| |
| case TOBAGO_CONFIG: |
| tobagoConfig = new TobagoConfigFragment(); |
| break; |
| |
| case CONTENT_SECURITY_POLICY: |
| final String mode = attributes.getValue(ATTR_MODE); |
| tobagoConfig.setContentSecurityPolicy(new ContentSecurityPolicy(mode)); |
| break; |
| |
| case THEME_DEFINITION: |
| currentTheme = new ThemeImpl(); |
| tobagoConfig.addThemeDefinition(currentTheme); |
| break; |
| |
| case RESOURCES: |
| production = Boolean.parseBoolean(attributes.getValue(ATTR_PRODUCTION)); |
| break; |
| |
| case EXCLUDES: |
| exclude = true; |
| break; |
| |
| case SCRIPT: |
| final ThemeScript script = new ThemeScript(); |
| script.setName(attributes.getValue(ATTR_NAME)); |
| script.setType(attributes.getValue(ATTR_TYPE)); |
| final String scriptPriority = attributes.getValue(ATTR_PRIORITY); |
| script.setPriority(scriptPriority != null ? Integer.parseUnsignedInt(scriptPriority) : MAX_PRIORITY); |
| if (production) { |
| currentTheme.getProductionResources().addScript(script, exclude); |
| } else { |
| currentTheme.getResources().addScript(script, exclude); |
| } |
| break; |
| |
| case STYLE: |
| final ThemeStyle style = new ThemeStyle(); |
| style.setName(attributes.getValue(ATTR_NAME)); |
| final String stylePriority = attributes.getValue(ATTR_PRIORITY); |
| style.setPriority(stylePriority != null ? Integer.parseUnsignedInt(stylePriority) : MAX_PRIORITY); |
| if (production) { |
| currentTheme.getProductionResources().addStyle(style, exclude); |
| } else { |
| currentTheme.getResources().addStyle(style, exclude); |
| } |
| break; |
| |
| case PROPERTIES: |
| properties = new Properties(); |
| break; |
| |
| case ENTRY: |
| entryKey = attributes.getValue(ATTR_KEY); |
| break; |
| |
| case DIRECTIVE: |
| directiveName = attributes.getValue(ATTR_NAME); |
| break; |
| |
| case NAME: |
| case ORDERING: |
| case BEFORE: |
| case AFTER: |
| case THEME_CONFIG: |
| case DEFAULT_THEME: |
| case SUPPORTED_THEME: |
| case SUPPORTED_MARKUP: |
| case MARKUP: |
| case CREATE_SESSION_SECRET: |
| case CHECK_SESSION_SECRET: |
| case SECURITY_ANNOTATION: |
| case PREVENT_FRAME_ATTACKS: |
| case SET_NOSNIFF_HEADER: |
| case THEME_DEFINITIONS: |
| case DISPLAY_NAME: |
| case VERSIONED: |
| case FALLBACK: |
| case SANITIZER: |
| case SANITIZER_CLASS: |
| case MIME_TYPES: |
| case MIME_TYPE: |
| case EXTENSION: |
| case TYPE: |
| case RENDERERS: |
| case RENDERER: |
| case INCLUDES: |
| // nothing to do |
| break; |
| |
| default: |
| LOG.warn("Ignoring unknown start tag <" + qName + "> with hashCode=" + qName.hashCode()); |
| } |
| } |
| |
| @Override |
| public void characters(final char[] ch, final int start, final int length) throws SAXException { |
| buffer.append(ch, start, length); |
| } |
| |
| @Override |
| public void endElement(final String uri, final String localName, final String qName) throws SAXException { |
| assert qName.equals(stack.peek()); |
| |
| final String text = buffer.toString().trim(); |
| buffer.setLength(0); |
| |
| switch (qName.hashCode()) { |
| |
| case NAME: |
| final String parent = stack.get(stack.size() - 2); |
| switch (parent.hashCode()) { |
| |
| case TOBAGO_CONFIG: |
| tobagoConfig.setName(text); |
| break; |
| |
| case BEFORE: |
| tobagoConfig.addBefore(text); |
| break; |
| |
| case AFTER: |
| tobagoConfig.addAfter(text); |
| break; |
| |
| case THEME_DEFINITION: |
| currentTheme.setName(text); |
| break; |
| |
| case RENDERER: |
| // nothing to do |
| break; |
| |
| default: |
| LOG.warn("Ignoring unknown parent <" + parent + "> of tag <name>"); |
| } |
| break; |
| |
| case DEFAULT_THEME: |
| tobagoConfig.setDefaultThemeName(text); |
| break; |
| |
| case SUPPORTED_THEME: |
| tobagoConfig.addSupportedThemeName(text); |
| break; |
| |
| case CREATE_SESSION_SECRET: |
| tobagoConfig.setCreateSessionSecret(text); |
| break; |
| |
| case CHECK_SESSION_SECRET: |
| tobagoConfig.setCheckSessionSecret(text); |
| break; |
| |
| case PREVENT_FRAME_ATTACKS: |
| tobagoConfig.setPreventFrameAttacks(Boolean.parseBoolean(text)); |
| break; |
| |
| case SET_NOSNIFF_HEADER: |
| tobagoConfig.setSetNosniffHeader(Boolean.parseBoolean(text)); |
| break; |
| |
| case SECURITY_ANNOTATION: |
| tobagoConfig.setSecurityAnnotation(SecurityAnnotation.valueOf(text)); |
| break; |
| |
| case DIRECTIVE: |
| if (directiveName == null) { // before Tobago 4.0 |
| final int i = text.indexOf(' '); |
| if (i < 1) { |
| throw new TobagoConfigurationException("CSP directive can't be parsed!"); |
| } |
| tobagoConfig.getContentSecurityPolicy().addDirective(text.substring(0, i), text.substring(i + 1)); |
| } else { |
| tobagoConfig.getContentSecurityPolicy().addDirective(directiveName, text); |
| } |
| directiveName = null; |
| break; |
| |
| case DISPLAY_NAME: |
| currentTheme.setDisplayName(text); |
| break; |
| |
| case FALLBACK: |
| currentTheme.setFallbackName(text); |
| break; |
| |
| case THEME_DEFINITION: |
| currentTheme = null; |
| break; |
| |
| case VERSION: |
| currentTheme.setVersion(text); |
| break; |
| |
| case RESOURCES: |
| production = null; |
| break; |
| |
| case EXCLUDES: |
| exclude = false; |
| break; |
| |
| case SANITIZER_CLASS: |
| tobagoConfig.setSanitizerClass(text); |
| break; |
| |
| case SANITIZER: |
| if (properties != null) { |
| tobagoConfig.setSanitizerProperties(properties); |
| } |
| properties = null; |
| break; |
| |
| case DECODE_LINE_FEED: |
| tobagoConfig.setDecodeLineFeed(Boolean.parseBoolean(text)); |
| break; |
| |
| case ENTRY: |
| properties.setProperty(entryKey, text); |
| entryKey = null; |
| break; |
| |
| case EXTENSION: |
| extension = text; |
| break; |
| |
| case TYPE: |
| type = text; |
| break; |
| |
| case MIME_TYPE: |
| tobagoConfig.addMimeType(extension, type); |
| break; |
| |
| case TOBAGO_CONFIG: |
| case THEME_CONFIG: |
| case ORDERING: |
| case BEFORE: |
| case AFTER: |
| case SUPPORTED_MARKUP: |
| case CONTENT_SECURITY_POLICY: |
| case THEME_DEFINITIONS: |
| case RENDERERS: |
| case RENDERER: |
| case SCRIPT: |
| case STYLE: |
| case PROPERTIES: |
| case MIME_TYPES: |
| case MARKUP: |
| case INCLUDES: |
| case VERSIONED: |
| // nothing to do |
| break; |
| |
| default: |
| LOG.warn("Ignoring unknown end tag <" + qName + ">"); |
| } |
| |
| stack.pop(); |
| } |
| |
| @Override |
| public void warning(final SAXParseException e) throws SAXException { |
| throw e; |
| } |
| |
| @Override |
| public void error(final SAXParseException e) throws SAXException { |
| throw e; |
| } |
| |
| @Override |
| public void fatalError(final SAXParseException e) throws SAXException { |
| throw e; |
| } |
| |
| private void validate(final URL url, final TobagoConfigVersion version) |
| throws URISyntaxException, SAXException, IOException { |
| |
| final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); |
| final Schema schema; |
| if ("5.0".equals(version.getVersion())) { |
| schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_5_0)); |
| } else if ("4.0".equals(version.getVersion())) { |
| schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_4_0)); |
| } else if ("3.0".equals(version.getVersion())) { |
| schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_3_0)); |
| } else if ("2.0.6".equals(version.getVersion())) { |
| schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_2_0_6)); |
| } else if ("2.0".equals(version.getVersion())) { |
| schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_2_0)); |
| } else if ("1.6".equals(version.getVersion())) { |
| LOG.warn("Using deprecated schema with version attribute 1.6 in file: '" + url + "'"); |
| schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_1_6)); |
| } else if ("1.5".equals(version.getVersion())) { |
| schema = schemaFactory.newSchema(getClass().getResource(TOBAGO_CONFIG_XSD_1_5)); |
| } else { |
| throw new SAXException("Using unknown version attribute '" + version.getVersion() + "' in file: '" + url + "'"); |
| } |
| final Validator validator = schema.newValidator(); |
| final Source source = new StreamSource(url.openStream()); |
| |
| validator.validate(source); |
| } |
| |
| } |