| #!/usr/bin/env python |
| # |
| # Copyright 2007 Google Inc. |
| # |
| # Licensed 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. |
| # |
| """Directly processes text of web.xml. |
| |
| WebXmlParser is called with Xml string to produce a WebXml object containing |
| the data from that string. |
| |
| WebXmlParser: Converts xml to AppEngineWebXml object. |
| WebXml: Contains relevant information from web.xml. |
| SecurityConstraint: Contains information about specified security constraints. |
| |
| """ |
| |
| import logging |
| |
| from xml.etree import ElementTree |
| |
| from google.appengine.tools import xml_parser_utils |
| from google.appengine.tools.app_engine_config_exception import AppEngineConfigException |
| from google.appengine.tools.value_mixin import ValueMixin |
| |
| |
| class WebXmlParser(object): |
| """Provides logic for walking down XML tree and pulling data.""" |
| |
| def ProcessXml(self, xml_str): |
| """Parses XML string and returns object representation of relevant info. |
| |
| Uses ElementTree parser to return a tree representation of XML. |
| Then walks down that tree and extracts important info and adds it to the |
| object. |
| |
| Args: |
| xml_str: The XML string itself. |
| |
| Returns: |
| If there is well-formed but illegal XML, returns a list of |
| errors. Otherwise, returns an AppEngineWebXml object containing |
| information from XML. |
| |
| Raises: |
| AppEngineConfigException: In case of malformed XML or illegal inputs. |
| """ |
| try: |
| self.web_xml = WebXml() |
| self.errors = [] |
| xml_root = ElementTree.fromstring(xml_str) |
| for node in xml_root.getchildren(): |
| self.ProcessSecondLevelNode(node) |
| |
| if self.errors: |
| raise AppEngineConfigException('\n'.join(self.errors)) |
| |
| return self.web_xml |
| |
| except ElementTree.ParseError: |
| raise AppEngineConfigException('Bad input -- not valid XML') |
| |
| |
| |
| |
| |
| _IGNORED_NODES = frozenset([ |
| 'context-param', 'description', 'display-name', 'distributable', |
| 'ejb-local-ref', 'ejb-ref', 'env-entry', 'filter', 'icon', |
| 'jsp-config', 'listener', 'locale-encoding-mapping-list', |
| 'login-config', 'message-destination', 'message-destination-ref', |
| 'persistence-context-ref', 'persistence-unit-ref', 'post-construct', |
| 'pre-destroy', 'resource-env-ref', 'resource-ref', 'security-role', |
| 'service-ref', 'servlet', 'session-config', 'taglib', |
| ]) |
| |
| def ProcessSecondLevelNode(self, node): |
| element_name = xml_parser_utils.GetTag(node) |
| if element_name in self._IGNORED_NODES: |
| |
| |
| return |
| camel_case_name = ''.join(part.title() for part in element_name.split('-')) |
| method_name = 'Process%sNode' % camel_case_name |
| if (hasattr(self, method_name) and |
| method_name is not 'ProcessSecondLevelNode'): |
| getattr(self, method_name)(node) |
| else: |
| logging.warning('Second-level tag not recognized: %s', element_name) |
| |
| def ProcessServletMappingNode(self, node): |
| self._ProcessUrlMappingNode(node) |
| |
| def ProcessFilterMappingNode(self, node): |
| self._ProcessUrlMappingNode(node) |
| |
| def _ProcessUrlMappingNode(self, node): |
| """Parses out URL and possible ID for filter-mapping and servlet-mapping. |
| |
| Pulls url-pattern text out of node and adds to WebXml object. If url-pattern |
| has an id attribute, adds that as well. This is done for <servlet-mapping> |
| and <filter-mapping> nodes. |
| |
| Args: |
| node: An ElementTreeNode which looks something like the following: |
| |
| <servlet-mapping> |
| <servlet-name>redteam</servlet-name> |
| <url-pattern>/red/*</url-pattern> |
| </servlet-mapping> |
| """ |
| url_pattern_node = xml_parser_utils.GetChild(node, 'url-pattern') |
| if url_pattern_node is not None: |
| self.web_xml.patterns.append(url_pattern_node.text) |
| id_attr = xml_parser_utils.GetAttribute(url_pattern_node, 'id') |
| if id_attr: |
| self.web_xml.pattern_to_id[url_pattern_node.text] = id_attr |
| |
| def ProcessErrorPageNode(self, node): |
| """Process error page specifications. |
| |
| If one of the supplied error codes is 404, allow fall through to runtime. |
| |
| Args: |
| node: An ElementTreeNode which looks something like the following. |
| <error-page> |
| <error-code>500</error-code> |
| <location>/errors/servererror.jsp</location> |
| </error-page> |
| """ |
| |
| error_code = xml_parser_utils.GetChildNodeText(node, 'error-code') |
| if error_code == '404': |
| self.web_xml.fall_through_to_runtime = True |
| |
| def ProcessWelcomeFileListNode(self, node): |
| for welcome_node in xml_parser_utils.GetNodes(node, 'welcome-file'): |
| welcome_file = welcome_node.text |
| if welcome_file and welcome_file[0] == '/': |
| self.errors.append('Welcome files must be relative paths: %s' % |
| welcome_file) |
| continue |
| self.web_xml.welcome_files.append(welcome_file) |
| |
| def ProcessMimeMappingNode(self, node): |
| extension = xml_parser_utils.GetChildNodeText(node, 'extension') |
| mime_type = xml_parser_utils.GetChildNodeText(node, 'mime-type') |
| |
| if not extension: |
| self.errors.append('<mime-type> without extension') |
| return |
| self.web_xml.mime_mappings[extension] = mime_type |
| |
| def ProcessSecurityConstraintNode(self, node): |
| """Pulls data from the security constraint node and adds to WebXml object. |
| |
| Args: |
| node: An ElementTree Xml node that looks something like the following: |
| |
| <security-constraint> |
| <web-resource-collection> |
| <url-pattern>/profile/*</url-pattern> |
| </web-resource-collection> |
| <user-data-constraint> |
| <transport-guarantee>CONFIDENTIAL</transport-guarantee> |
| </user-data-constraint> |
| </security-constraint> |
| """ |
| security_constraint = SecurityConstraint() |
| resources_node = xml_parser_utils.GetChild(node, 'web-resource-collection') |
| security_constraint.patterns = [ |
| sub_node.text for sub_node in xml_parser_utils.GetNodes( |
| resources_node, 'url-pattern')] |
| constraint = xml_parser_utils.GetChild(node, 'auth-constraint') |
| if constraint is not None: |
| role_name = xml_parser_utils.GetChildNodeText( |
| constraint, 'role-name').lower() |
| if role_name not in ('none', '*', 'admin'): |
| self.errors.append('Bad value for <role-name> (%s), must be none, ' |
| '*, or admin' % role_name) |
| security_constraint.required_role = role_name |
| |
| user_constraint = xml_parser_utils.GetChild(node, 'user-data-constraint') |
| if user_constraint is not None: |
| guarantee = xml_parser_utils.GetChildNodeText( |
| user_constraint, 'transport-guarantee').lower() |
| if guarantee not in ('none', 'integral', 'confidential'): |
| self.errors.append('Bad value for <transport-guarantee> (%s), must be' |
| ' none, integral, or confidential' % guarantee) |
| security_constraint.transport_guarantee = guarantee |
| |
| self.web_xml.security_constraints.append(security_constraint) |
| |
| |
| class WebXml(ValueMixin): |
| """Contains information about web.xml relevant for translation to app.yaml.""" |
| |
| def __init__(self): |
| self.patterns = [] |
| self.security_constraints = [] |
| self.welcome_files = [] |
| self.mime_mappings = {} |
| self.pattern_to_id = {} |
| self.fall_through_to_runtime = False |
| |
| def GetMimeTypeForPath(self, path): |
| if '.' not in path: |
| return None |
| return self.mime_mappings.get(path.split('.')[-1], None) |
| |
| |
| class SecurityConstraint(ValueMixin): |
| """Contains information about security constraints in web.xml.""" |
| |
| def __init__(self): |
| self.patterns = [] |
| self.transport_guarantee = 'none' |
| self.required_role = 'none' |