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.
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
48 xml_str: The XML string itself.
49 has_jsps: True if the application has *.jsp files.
52 If there is well-formed but illegal XML, returns a list of
53 errors. Otherwise, returns an AppEngineWebXml object containing
57 AppEngineConfigException: In case of malformed XML or illegal inputs.
60 self
.web_xml
= WebXml()
61 self
.web_xml
.has_jsps
= has_jsps
63 xml_root
= ElementTree
.fromstring(xml_str
)
64 for node
in xml_root
.getchildren():
65 self
.ProcessSecondLevelNode(node
)
68 raise AppEngineConfigException('\n'.join(self
.errors
))
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
:
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
)
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.
117 node: An ElementTreeNode which looks something like the following:
120 <servlet-name>redteam</servlet-name>
121 <url-pattern>/red/*</url-pattern>
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')
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.
137 node: An ElementTreeNode which looks something like the following.
139 <error-code>500</error-code>
140 <location>/errors/servererror.jsp</location>
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' %
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')
162 self
.errors
.append('<mime-type> without extension')
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.
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()
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."""
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
):
223 return self
.mime_mappings
.get(path
.split('.')[-1], None)
226 class SecurityConstraint(ValueMixin
):
227 """Contains information about security constraints in web.xml."""
231 self
.transport_guarantee
= 'none'
232 self
.required_role
= 'none'