blob: 494ffb8d862092acbefb3ef63d076b7647538c41 [file] [log] [blame]
#!/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.
"""
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')
def ProcessSecondLevelNode(self, node):
element_name = xml_parser_utils.GetTag(node)
if element_name in ['servlet', 'filter', 'display-name', 'taglib']:
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:
self.errors.append('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'