Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 # Copyright 2011, Google Inc.
2 # All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 # * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 """Common functions and exceptions used by WebSocket opening handshake
32 processors.
33 """
36 from mod_pywebsocket import common
37 from mod_pywebsocket import http_header_util
40 class AbortedByUserException(Exception):
41 """Exception for aborting a connection intentionally.
43 If this exception is raised in do_extra_handshake handler, the connection
44 will be abandoned. No other WebSocket or HTTP(S) handler will be invoked.
46 If this exception is raised in transfer_data_handler, the connection will
47 be closed without closing handshake. No other WebSocket or HTTP(S) handler
48 will be invoked.
49 """
51 pass
54 class HandshakeException(Exception):
55 """This exception will be raised when an error occurred while processing
56 WebSocket initial handshake.
57 """
59 def __init__(self, name, status=None):
60 super(HandshakeException, self).__init__(name)
61 self.status = status
64 class VersionException(Exception):
65 """This exception will be raised when a version of client request does not
66 match with version the server supports.
67 """
69 def __init__(self, name, supported_versions=''):
70 """Construct an instance.
72 Args:
73 supported_version: a str object to show supported hybi versions.
74 (e.g. '8, 13')
75 """
76 super(VersionException, self).__init__(name)
77 self.supported_versions = supported_versions
80 def get_default_port(is_secure):
81 if is_secure:
82 return common.DEFAULT_WEB_SOCKET_SECURE_PORT
83 else:
84 return common.DEFAULT_WEB_SOCKET_PORT
87 def validate_subprotocol(subprotocol, hixie):
88 """Validate a value in subprotocol fields such as WebSocket-Protocol,
89 Sec-WebSocket-Protocol.
91 See
92 - RFC 6455: Section 4.1., 4.2.2., and 4.3.
93 - HyBi 00: Section 4.1. Opening handshake
94 - Hixie 75: Section 4.1. Handshake
95 """
97 if not subprotocol:
98 raise HandshakeException('Invalid subprotocol name: empty')
99 if hixie:
100 # Parameter should be in the range U+0020 to U+007E.
101 for c in subprotocol:
102 if not 0x20 <= ord(c) <= 0x7e:
103 raise HandshakeException(
104 'Illegal character in subprotocol name: %r' % c)
105 else:
106 # Parameter should be encoded HTTP token.
107 state = http_header_util.ParsingState(subprotocol)
108 token = http_header_util.consume_token(state)
109 rest = http_header_util.peek(state)
110 # If |rest| is not None, |subprotocol| is not one token or invalid. If
111 # |rest| is None, |token| must not be None because |subprotocol| is
112 # concatenation of |token| and |rest| and is not None.
113 if rest is not None:
114 raise HandshakeException('Invalid non-token string in subprotocol '
115 'name: %r' % rest)
118 def parse_host_header(request):
119 fields = request.headers_in['Host'].split(':', 1)
120 if len(fields) == 1:
121 return fields[0], get_default_port(request.is_https())
122 try:
123 return fields[0], int(fields[1])
124 except ValueError, e:
125 raise HandshakeException('Invalid port number format: %r' % e)
128 def format_header(name, value):
129 return '%s: %s\r\n' % (name, value)
132 def build_location(request):
133 """Build WebSocket location for request."""
134 location_parts = []
135 if request.is_https():
136 location_parts.append(common.WEB_SOCKET_SECURE_SCHEME)
137 else:
138 location_parts.append(common.WEB_SOCKET_SCHEME)
139 location_parts.append('://')
140 host, port = parse_host_header(request)
141 connection_port = request.connection.local_addr[1]
142 if port != connection_port:
143 raise HandshakeException('Header/connection port mismatch: %d/%d' %
144 (port, connection_port))
145 location_parts.append(host)
146 if (port != get_default_port(request.is_https())):
147 location_parts.append(':')
148 location_parts.append(str(port))
149 location_parts.append(request.uri)
150 return ''.join(location_parts)
153 def get_mandatory_header(request, key):
154 value = request.headers_in.get(key)
155 if value is None:
156 raise HandshakeException('Header %s is not defined' % key)
157 return value
160 def validate_mandatory_header(request, key, expected_value, fail_status=None):
161 value = get_mandatory_header(request, key)
163 if value.lower() != expected_value.lower():
164 raise HandshakeException(
165 'Expected %r for header %s but found %r (case-insensitive)' %
166 (expected_value, key, value), status=fail_status)
169 def check_request_line(request):
170 # 5.1 1. The three character UTF-8 string "GET".
171 # 5.1 2. A UTF-8-encoded U+0020 SPACE character (0x20 byte).
172 if request.method != 'GET':
173 raise HandshakeException('Method is not GET')
176 def check_header_lines(request, mandatory_headers):
177 check_request_line(request)
179 # The expected field names, and the meaning of their corresponding
180 # values, are as follows.
181 # |Upgrade| and |Connection|
182 for key, expected_value in mandatory_headers:
183 validate_mandatory_header(request, key, expected_value)
186 def parse_token_list(data):
187 """Parses a header value which follows 1#token and returns parsed elements
188 as a list of strings.
190 Leading LWSes must be trimmed.
191 """
193 state = http_header_util.ParsingState(data)
195 token_list = []
197 while True:
198 token = http_header_util.consume_token(state)
199 if token is not None:
200 token_list.append(token)
202 http_header_util.consume_lwses(state)
204 if http_header_util.peek(state) is None:
205 break
207 if not http_header_util.consume_string(state, ','):
208 raise HandshakeException(
209 'Expected a comma but found %r' % http_header_util.peek(state))
211 http_header_util.consume_lwses(state)
213 if len(token_list) == 0:
214 raise HandshakeException('No valid token found')
216 return token_list
219 def _parse_extension_param(state, definition, allow_quoted_string):
220 param_name = http_header_util.consume_token(state)
222 if param_name is None:
223 raise HandshakeException('No valid parameter name found')
225 http_header_util.consume_lwses(state)
227 if not http_header_util.consume_string(state, '='):
228 definition.add_parameter(param_name, None)
229 return
231 http_header_util.consume_lwses(state)
233 if allow_quoted_string:
234 # TODO(toyoshim): Add code to validate that parsed param_value is token
235 param_value = http_header_util.consume_token_or_quoted_string(state)
236 else:
237 param_value = http_header_util.consume_token(state)
238 if param_value is None:
239 raise HandshakeException(
240 'No valid parameter value found on the right-hand side of '
241 'parameter %r' % param_name)
243 definition.add_parameter(param_name, param_value)
246 def _parse_extension(state, allow_quoted_string):
247 extension_token = http_header_util.consume_token(state)
248 if extension_token is None:
249 return None
251 extension = common.ExtensionParameter(extension_token)
253 while True:
254 http_header_util.consume_lwses(state)
256 if not http_header_util.consume_string(state, ';'):
257 break
259 http_header_util.consume_lwses(state)
261 try:
262 _parse_extension_param(state, extension, allow_quoted_string)
263 except HandshakeException, e:
264 raise HandshakeException(
265 'Failed to parse Sec-WebSocket-Extensions header: '
266 'Failed to parse parameter for %r (%r)' %
267 (extension_token, e))
269 return extension
272 def parse_extensions(data, allow_quoted_string=False):
273 """Parses Sec-WebSocket-Extensions header value returns a list of
274 common.ExtensionParameter objects.
276 Leading LWSes must be trimmed.
277 """
279 state = http_header_util.ParsingState(data)
281 extension_list = []
282 while True:
283 extension = _parse_extension(state, allow_quoted_string)
284 if extension is not None:
285 extension_list.append(extension)
287 http_header_util.consume_lwses(state)
289 if http_header_util.peek(state) is None:
290 break
292 if not http_header_util.consume_string(state, ','):
293 raise HandshakeException(
294 'Failed to parse Sec-WebSocket-Extensions header: '
295 'Expected a comma but found %r' %
296 http_header_util.peek(state))
298 http_header_util.consume_lwses(state)
300 if len(extension_list) == 0:
301 raise HandshakeException(
302 'Sec-WebSocket-Extensions header contains no valid extension')
304 return extension_list
307 def format_extensions(extension_list):
308 formatted_extension_list = []
309 for extension in extension_list:
310 formatted_params = [extension.name()]
311 for param_name, param_value in extension.get_parameters():
312 if param_value is None:
313 formatted_params.append(param_name)
314 else:
315 quoted_value = http_header_util.quote_if_necessary(param_value)
316 formatted_params.append('%s=%s' % (param_name, quoted_value))
318 formatted_extension_list.append('; '.join(formatted_params))
320 return ', '.join(formatted_extension_list)
323 # vi:sts=4 sw=4 et