App Engine Python SDK version 1.8.9
[gae.git] / python / google / appengine / tools / web_xml_parser.py
blob494ffb8d862092acbefb3ef63d076b7647538c41
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Directly processes text of web.xml.
19 WebXmlParser is called with Xml string to produce a WebXml object containing
20 the data from that string.
22 WebXmlParser: Converts xml to AppEngineWebXml object.
23 WebXml: Contains relevant information from web.xml.
24 SecurityConstraint: Contains information about specified security constraints.
26 """
28 from xml.etree import ElementTree
30 from google.appengine.tools import xml_parser_utils
31 from google.appengine.tools.app_engine_config_exception import AppEngineConfigException
32 from google.appengine.tools.value_mixin import ValueMixin
35 class WebXmlParser(object):
36 """Provides logic for walking down XML tree and pulling data."""
38 def ProcessXml(self, xml_str):
39 """Parses XML string and returns object representation of relevant info.
41 Uses ElementTree parser to return a tree representation of XML.
42 Then walks down that tree and extracts important info and adds it to the
43 object.
45 Args:
46 xml_str: The XML string itself.
48 Returns:
49 If there is well-formed but illegal XML, returns a list of
50 errors. Otherwise, returns an AppEngineWebXml object containing
51 information from XML.
53 Raises:
54 AppEngineConfigException: In case of malformed XML or illegal inputs.
55 """
56 try:
57 self.web_xml = WebXml()
58 self.errors = []
59 xml_root = ElementTree.fromstring(xml_str)
60 for node in xml_root.getchildren():
61 self.ProcessSecondLevelNode(node)
63 if self.errors:
64 raise AppEngineConfigException('\n'.join(self.errors))
66 return self.web_xml
68 except ElementTree.ParseError:
69 raise AppEngineConfigException('Bad input -- not valid XML')
71 def ProcessSecondLevelNode(self, node):
72 element_name = xml_parser_utils.GetTag(node)
73 if element_name in ['servlet', 'filter', 'display-name', 'taglib']:
76 return
77 camel_case_name = ''.join(part.title() for part in element_name.split('-'))
78 method_name = 'Process%sNode' % camel_case_name
79 if (hasattr(self, method_name) and
80 method_name is not 'ProcessSecondLevelNode'):
81 getattr(self, method_name)(node)
82 else:
83 self.errors.append('Second-level tag not recognized: %s' % element_name)
85 def ProcessServletMappingNode(self, node):
86 self._ProcessUrlMappingNode(node)
88 def ProcessFilterMappingNode(self, node):
89 self._ProcessUrlMappingNode(node)
91 def _ProcessUrlMappingNode(self, node):
92 """Parses out URL and possible ID for filter-mapping and servlet-mapping.
94 Pulls url-pattern text out of node and adds to WebXml object. If url-pattern
95 has an id attribute, adds that as well. This is done for <servlet-mapping>
96 and <filter-mapping> nodes.
98 Args:
99 node: An ElementTreeNode which looks something like the following:
101 <servlet-mapping>
102 <servlet-name>redteam</servlet-name>
103 <url-pattern>/red/*</url-pattern>
104 </servlet-mapping>
106 url_pattern_node = xml_parser_utils.GetChild(node, 'url-pattern')
107 if url_pattern_node is not None:
108 self.web_xml.patterns.append(url_pattern_node.text)
109 id_attr = xml_parser_utils.GetAttribute(url_pattern_node, 'id')
110 if id_attr:
111 self.web_xml.pattern_to_id[url_pattern_node.text] = id_attr
113 def ProcessErrorPageNode(self, node):
114 """Process error page specifications.
116 If one of the supplied error codes is 404, allow fall through to runtime.
118 Args:
119 node: An ElementTreeNode which looks something like the following.
120 <error-page>
121 <error-code>500</error-code>
122 <location>/errors/servererror.jsp</location>
123 </error-page>
126 error_code = xml_parser_utils.GetChildNodeText(node, 'error-code')
127 if error_code == '404':
128 self.web_xml.fall_through_to_runtime = True
130 def ProcessWelcomeFileListNode(self, node):
131 for welcome_node in xml_parser_utils.GetNodes(node, 'welcome-file'):
132 welcome_file = welcome_node.text
133 if welcome_file and welcome_file[0] == '/':
134 self.errors.append('Welcome files must be relative paths: %s' %
135 welcome_file)
136 continue
137 self.web_xml.welcome_files.append(welcome_file)
139 def ProcessMimeMappingNode(self, node):
140 extension = xml_parser_utils.GetChildNodeText(node, 'extension')
141 mime_type = xml_parser_utils.GetChildNodeText(node, 'mime-type')
143 if not extension:
144 self.errors.append('<mime-type> without extension')
145 return
146 self.web_xml.mime_mappings[extension] = mime_type
148 def ProcessSecurityConstraintNode(self, node):
149 """Pulls data from the security constraint node and adds to WebXml object.
151 Args:
152 node: An ElementTree Xml node that looks something like the following:
154 <security-constraint>
155 <web-resource-collection>
156 <url-pattern>/profile/*</url-pattern>
157 </web-resource-collection>
158 <user-data-constraint>
159 <transport-guarantee>CONFIDENTIAL</transport-guarantee>
160 </user-data-constraint>
161 </security-constraint>
163 security_constraint = SecurityConstraint()
164 resources_node = xml_parser_utils.GetChild(node, 'web-resource-collection')
165 security_constraint.patterns = [
166 sub_node.text for sub_node in xml_parser_utils.GetNodes(
167 resources_node, 'url-pattern')]
168 constraint = xml_parser_utils.GetChild(node, 'auth-constraint')
169 if constraint is not None:
170 role_name = xml_parser_utils.GetChildNodeText(
171 constraint, 'role-name').lower()
172 if role_name not in ('none', '*', 'admin'):
173 self.errors.append('Bad value for <role-name> (%s), must be none, '
174 '*, or admin' % role_name)
175 security_constraint.required_role = role_name
177 user_constraint = xml_parser_utils.GetChild(node, 'user-data-constraint')
178 if user_constraint is not None:
179 guarantee = xml_parser_utils.GetChildNodeText(
180 user_constraint, 'transport-guarantee').lower()
181 if guarantee not in ('none', 'integral', 'confidential'):
182 self.errors.append('Bad value for <transport-guarantee> (%s), must be'
183 ' none, integral, or confidential' % guarantee)
184 security_constraint.transport_guarantee = guarantee
186 self.web_xml.security_constraints.append(security_constraint)
189 class WebXml(ValueMixin):
190 """Contains information about web.xml relevant for translation to app.yaml."""
192 def __init__(self):
193 self.patterns = []
194 self.security_constraints = []
195 self.welcome_files = []
196 self.mime_mappings = {}
197 self.pattern_to_id = {}
198 self.fall_through_to_runtime = False
200 def GetMimeTypeForPath(self, path):
201 if '.' not in path:
202 return None
203 return self.mime_mappings.get(path.split('.')[-1], None)
206 class SecurityConstraint(ValueMixin):
207 """Contains information about security constraints in web.xml."""
209 def __init__(self):
210 self.patterns = []
211 self.transport_guarantee = 'none'
212 self.required_role = 'none'