1.9.30 sync.
[gae.git] / python / google / appengine / tools / web_xml_parser.py
blob0cd824b7283fe1ada6c105c52033124abbf23151
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 import logging
30 from xml.etree import ElementTree
32 from google.appengine.tools import xml_parser_utils
33 from google.appengine.tools.app_engine_config_exception import AppEngineConfigException
34 from google.appengine.tools.value_mixin import ValueMixin
37 class WebXmlParser(object):
38 """Provides logic for walking down XML tree and pulling data."""
40 def ProcessXml(self, xml_str, has_jsps=False):
41 """Parses XML string and returns object representation of relevant info.
43 Uses ElementTree parser to return a tree representation of XML.
44 Then walks down that tree and extracts important info and adds it to the
45 object.
47 Args:
48 xml_str: The XML string itself.
49 has_jsps: True if the application has *.jsp files.
51 Returns:
52 If there is well-formed but illegal XML, returns a list of
53 errors. Otherwise, returns an AppEngineWebXml object containing
54 information from XML.
56 Raises:
57 AppEngineConfigException: In case of malformed XML or illegal inputs.
58 """
59 try:
60 self.web_xml = WebXml()
61 self.web_xml.has_jsps = has_jsps
62 self.errors = []
63 xml_root = ElementTree.fromstring(xml_str)
64 for node in xml_root.getchildren():
65 self.ProcessSecondLevelNode(node)
67 if self.errors:
68 raise AppEngineConfigException('\n'.join(self.errors))
70 return self.web_xml
72 except ElementTree.ParseError:
73 raise AppEngineConfigException('Bad input -- not valid XML')
79 _IGNORED_NODES = frozenset([
80 'context-param', 'description', 'display-name', 'distributable',
81 'ejb-local-ref', 'ejb-ref', 'env-entry', 'filter', 'icon',
82 'jsp-config', 'listener', 'locale-encoding-mapping-list',
83 'login-config', 'message-destination', 'message-destination-ref',
84 'persistence-context-ref', 'persistence-unit-ref', 'post-construct',
85 'pre-destroy', 'resource-env-ref', 'resource-ref', 'security-role',
86 'service-ref', 'servlet', 'session-config', 'taglib',
89 def ProcessSecondLevelNode(self, node):
90 element_name = xml_parser_utils.GetTag(node)
91 if element_name in self._IGNORED_NODES:
94 return
95 camel_case_name = ''.join(part.title() for part in element_name.split('-'))
96 method_name = 'Process%sNode' % camel_case_name
97 if (hasattr(self, method_name) and
98 method_name is not 'ProcessSecondLevelNode'):
99 getattr(self, method_name)(node)
100 else:
101 logging.warning('Second-level tag not recognized: %s', element_name)
103 def ProcessServletMappingNode(self, node):
104 self._ProcessUrlMappingNode(node)
106 def ProcessFilterMappingNode(self, node):
107 self._ProcessUrlMappingNode(node)
109 def _ProcessUrlMappingNode(self, node):
110 """Parses out URL and possible ID for filter-mapping and servlet-mapping.
112 Pulls url-pattern text out of node and adds to WebXml object. If url-pattern
113 has an id attribute, adds that as well. This is done for <servlet-mapping>
114 and <filter-mapping> nodes.
116 Args:
117 node: An ElementTreeNode which looks something like the following:
119 <servlet-mapping>
120 <servlet-name>redteam</servlet-name>
121 <url-pattern>/red/*</url-pattern>
122 </servlet-mapping>
124 url_pattern_node = xml_parser_utils.GetChild(node, 'url-pattern')
125 if url_pattern_node is not None:
126 self.web_xml.patterns.append(url_pattern_node.text)
127 id_attr = xml_parser_utils.GetAttribute(url_pattern_node, 'id')
128 if id_attr:
129 self.web_xml.pattern_to_id[url_pattern_node.text] = id_attr
131 def ProcessErrorPageNode(self, node):
132 """Process error page specifications.
134 If one of the supplied error codes is 404, allow fall through to runtime.
136 Args:
137 node: An ElementTreeNode which looks something like the following.
138 <error-page>
139 <error-code>500</error-code>
140 <location>/errors/servererror.jsp</location>
141 </error-page>
144 error_code = xml_parser_utils.GetChildNodeText(node, 'error-code')
145 if error_code == '404':
146 self.web_xml.fall_through_to_runtime = True
148 def ProcessWelcomeFileListNode(self, node):
149 for welcome_node in xml_parser_utils.GetNodes(node, 'welcome-file'):
150 welcome_file = welcome_node.text
151 if welcome_file and welcome_file[0] == '/':
152 self.errors.append('Welcome files must be relative paths: %s' %
153 welcome_file)
154 continue
155 self.web_xml.welcome_files.append(welcome_file)
157 def ProcessMimeMappingNode(self, node):
158 extension = xml_parser_utils.GetChildNodeText(node, 'extension')
159 mime_type = xml_parser_utils.GetChildNodeText(node, 'mime-type')
161 if not extension:
162 self.errors.append('<mime-type> without extension')
163 return
164 self.web_xml.mime_mappings[extension] = mime_type
166 def ProcessSecurityConstraintNode(self, node):
167 """Pulls data from the security constraint node and adds to WebXml object.
169 Args:
170 node: An ElementTree Xml node that looks something like the following:
172 <security-constraint>
173 <web-resource-collection>
174 <url-pattern>/profile/*</url-pattern>
175 </web-resource-collection>
176 <user-data-constraint>
177 <transport-guarantee>CONFIDENTIAL</transport-guarantee>
178 </user-data-constraint>
179 </security-constraint>
181 security_constraint = SecurityConstraint()
182 resources_node = xml_parser_utils.GetChild(node, 'web-resource-collection')
183 security_constraint.patterns = [
184 sub_node.text for sub_node in xml_parser_utils.GetNodes(
185 resources_node, 'url-pattern')]
186 constraint = xml_parser_utils.GetChild(node, 'auth-constraint')
187 if constraint is not None:
188 role_name = xml_parser_utils.GetChildNodeText(
189 constraint, 'role-name').lower()
190 if role_name:
191 if role_name not in ('none', '*', 'admin'):
192 self.errors.append('Bad value for <role-name> (%s), must be none, '
193 '*, or admin' % role_name)
194 security_constraint.required_role = role_name
196 user_constraint = xml_parser_utils.GetChild(node, 'user-data-constraint')
197 if user_constraint is not None:
198 guarantee = xml_parser_utils.GetChildNodeText(
199 user_constraint, 'transport-guarantee').lower()
200 if guarantee not in ('none', 'integral', 'confidential'):
201 self.errors.append('Bad value for <transport-guarantee> (%s), must be'
202 ' none, integral, or confidential' % guarantee)
203 security_constraint.transport_guarantee = guarantee
205 self.web_xml.security_constraints.append(security_constraint)
208 class WebXml(ValueMixin):
209 """Contains information about web.xml relevant for translation to app.yaml."""
211 def __init__(self):
212 self.patterns = []
213 self.security_constraints = []
214 self.welcome_files = []
215 self.mime_mappings = {}
216 self.pattern_to_id = {}
217 self.fall_through_to_runtime = False
218 self.has_jsps = False
220 def GetMimeTypeForPath(self, path):
221 if '.' not in path:
222 return None
223 return self.mime_mappings.get(path.split('.')[-1], None)
226 class SecurityConstraint(ValueMixin):
227 """Contains information about security constraints in web.xml."""
229 def __init__(self):
230 self.patterns = []
231 self.transport_guarantee = 'none'
232 self.required_role = 'none'