don't use autoinstall to import pywebsocket but check it in WebKit directly.
[WebKit-https.git] / Tools / Scripts / webkitpy / thirdparty / mod_pywebsocket / handshake / hybi.py
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.
29
30
31 """This file provides the opening handshake processor for the WebSocket
32 protocol (RFC 6455).
33
34 Specification:
35 http://tools.ietf.org/html/rfc6455
36 """
37
38
39 # Note: request.connection.write is used in this module, even though mod_python
40 # document says that it should be used only in connection handlers.
41 # Unfortunately, we have no other options. For example, request.write is not
42 # suitable because it doesn't allow direct raw bytes writing.
43
44
45 import base64
46 import logging
47 import os
48 import re
49
50 from mod_pywebsocket import common
51 from mod_pywebsocket.extensions import get_extension_processor
52 from mod_pywebsocket.handshake._base import check_request_line
53 from mod_pywebsocket.handshake._base import format_extensions
54 from mod_pywebsocket.handshake._base import format_header
55 from mod_pywebsocket.handshake._base import get_mandatory_header
56 from mod_pywebsocket.handshake._base import HandshakeException
57 from mod_pywebsocket.handshake._base import parse_extensions
58 from mod_pywebsocket.handshake._base import parse_token_list
59 from mod_pywebsocket.handshake._base import validate_mandatory_header
60 from mod_pywebsocket.handshake._base import validate_subprotocol
61 from mod_pywebsocket.handshake._base import VersionException
62 from mod_pywebsocket.stream import Stream
63 from mod_pywebsocket.stream import StreamOptions
64 from mod_pywebsocket import util
65
66
67 # Used to validate the value in the Sec-WebSocket-Key header strictly. RFC 4648
68 # disallows non-zero padding, so the character right before == must be any of
69 # A, Q, g and w.
70 _SEC_WEBSOCKET_KEY_REGEX = re.compile('^[+/0-9A-Za-z]{21}[AQgw]==$')
71
72 # Defining aliases for values used frequently.
73 _VERSION_HYBI08 = common.VERSION_HYBI08
74 _VERSION_HYBI08_STRING = str(_VERSION_HYBI08)
75 _VERSION_LATEST = common.VERSION_HYBI_LATEST
76 _VERSION_LATEST_STRING = str(_VERSION_LATEST)
77 _SUPPORTED_VERSIONS = [
78     _VERSION_LATEST,
79     _VERSION_HYBI08,
80 ]
81
82
83 def compute_accept(key):
84     """Computes value for the Sec-WebSocket-Accept header from value of the
85     Sec-WebSocket-Key header.
86     """
87
88     accept_binary = util.sha1_hash(
89         key + common.WEBSOCKET_ACCEPT_UUID).digest()
90     accept = base64.b64encode(accept_binary)
91
92     return (accept, accept_binary)
93
94
95 class Handshaker(object):
96     """Opening handshake processor for the WebSocket protocol (RFC 6455)."""
97
98     def __init__(self, request, dispatcher):
99         """Construct an instance.
100
101         Args:
102             request: mod_python request.
103             dispatcher: Dispatcher (dispatch.Dispatcher).
104
105         Handshaker will add attributes such as ws_resource during handshake.
106         """
107
108         self._logger = util.get_class_logger(self)
109
110         self._request = request
111         self._dispatcher = dispatcher
112
113     def _validate_connection_header(self):
114         connection = get_mandatory_header(
115             self._request, common.CONNECTION_HEADER)
116
117         try:
118             connection_tokens = parse_token_list(connection)
119         except HandshakeException, e:
120             raise HandshakeException(
121                 'Failed to parse %s: %s' % (common.CONNECTION_HEADER, e))
122
123         connection_is_valid = False
124         for token in connection_tokens:
125             if token.lower() == common.UPGRADE_CONNECTION_TYPE.lower():
126                 connection_is_valid = True
127                 break
128         if not connection_is_valid:
129             raise HandshakeException(
130                 '%s header doesn\'t contain "%s"' %
131                 (common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE))
132
133     def do_handshake(self):
134         self._request.ws_close_code = None
135         self._request.ws_close_reason = None
136
137         # Parsing.
138
139         check_request_line(self._request)
140
141         validate_mandatory_header(
142             self._request,
143             common.UPGRADE_HEADER,
144             common.WEBSOCKET_UPGRADE_TYPE)
145
146         self._validate_connection_header()
147
148         self._request.ws_resource = self._request.uri
149
150         unused_host = get_mandatory_header(self._request, common.HOST_HEADER)
151
152         self._request.ws_version = self._check_version()
153
154         # This handshake must be based on latest hybi. We are responsible to
155         # fallback to HTTP on handshake failure as latest hybi handshake
156         # specifies.
157         try:
158             self._get_origin()
159             self._set_protocol()
160             self._parse_extensions()
161
162             # Key validation, response generation.
163
164             key = self._get_key()
165             (accept, accept_binary) = compute_accept(key)
166             self._logger.debug(
167                 '%s: %r (%s)',
168                 common.SEC_WEBSOCKET_ACCEPT_HEADER,
169                 accept,
170                 util.hexify(accept_binary))
171
172             self._logger.debug('Protocol version is RFC 6455')
173
174             # Setup extension processors.
175
176             processors = []
177             if self._request.ws_requested_extensions is not None:
178                 for extension_request in self._request.ws_requested_extensions:
179                     processor = get_extension_processor(extension_request)
180                     # Unknown extension requests are just ignored.
181                     if processor is not None:
182                         processors.append(processor)
183             self._request.ws_extension_processors = processors
184
185             # Extra handshake handler may modify/remove processors.
186             self._dispatcher.do_extra_handshake(self._request)
187
188             stream_options = StreamOptions()
189
190             self._request.ws_extensions = None
191             for processor in self._request.ws_extension_processors:
192                 if processor is None:
193                     # Some processors may be removed by extra handshake
194                     # handler.
195                     continue
196
197                 extension_response = processor.get_extension_response()
198                 if extension_response is None:
199                     # Rejected.
200                     continue
201
202                 if self._request.ws_extensions is None:
203                     self._request.ws_extensions = []
204                 self._request.ws_extensions.append(extension_response)
205
206                 processor.setup_stream_options(stream_options)
207
208             if self._request.ws_extensions is not None:
209                 self._logger.debug(
210                     'Extensions accepted: %r',
211                     map(common.ExtensionParameter.name,
212                         self._request.ws_extensions))
213
214             self._request.ws_stream = Stream(self._request, stream_options)
215
216             if self._request.ws_requested_protocols is not None:
217                 if self._request.ws_protocol is None:
218                     raise HandshakeException(
219                         'do_extra_handshake must choose one subprotocol from '
220                         'ws_requested_protocols and set it to ws_protocol')
221                 validate_subprotocol(self._request.ws_protocol, hixie=False)
222
223                 self._logger.debug(
224                     'Subprotocol accepted: %r',
225                     self._request.ws_protocol)
226             else:
227                 if self._request.ws_protocol is not None:
228                     raise HandshakeException(
229                         'ws_protocol must be None when the client didn\'t '
230                         'request any subprotocol')
231
232             self._send_handshake(accept)
233         except HandshakeException, e:
234             if not e.status:
235                 # Fallback to 400 bad request by default.
236                 e.status = common.HTTP_STATUS_BAD_REQUEST
237             raise e
238
239     def _get_origin(self):
240         if self._request.ws_version is _VERSION_HYBI08:
241             origin_header = common.SEC_WEBSOCKET_ORIGIN_HEADER
242         else:
243             origin_header = common.ORIGIN_HEADER
244         origin = self._request.headers_in.get(origin_header)
245         if origin is None:
246             self._logger.debug('Client request does not have origin header')
247         self._request.ws_origin = origin
248
249     def _check_version(self):
250         version = get_mandatory_header(self._request,
251                                        common.SEC_WEBSOCKET_VERSION_HEADER)
252         if version == _VERSION_HYBI08_STRING:
253             return _VERSION_HYBI08
254         if version == _VERSION_LATEST_STRING:
255             return _VERSION_LATEST
256
257         if version.find(',') >= 0:
258             raise HandshakeException(
259                 'Multiple versions (%r) are not allowed for header %s' %
260                 (version, common.SEC_WEBSOCKET_VERSION_HEADER),
261                 status=common.HTTP_STATUS_BAD_REQUEST)
262         raise VersionException(
263             'Unsupported version %r for header %s' %
264             (version, common.SEC_WEBSOCKET_VERSION_HEADER),
265             supported_versions=', '.join(map(str, _SUPPORTED_VERSIONS)))
266
267     def _set_protocol(self):
268         self._request.ws_protocol = None
269
270         protocol_header = self._request.headers_in.get(
271             common.SEC_WEBSOCKET_PROTOCOL_HEADER)
272
273         if not protocol_header:
274             self._request.ws_requested_protocols = None
275             return
276
277         self._request.ws_requested_protocols = parse_token_list(
278             protocol_header)
279         self._logger.debug('Subprotocols requested: %r',
280                            self._request.ws_requested_protocols)
281
282     def _parse_extensions(self):
283         extensions_header = self._request.headers_in.get(
284             common.SEC_WEBSOCKET_EXTENSIONS_HEADER)
285         if not extensions_header:
286             self._request.ws_requested_extensions = None
287             return
288
289         if self._request.ws_version is common.VERSION_HYBI08:
290             allow_quoted_string=False
291         else:
292             allow_quoted_string=True
293         self._request.ws_requested_extensions = parse_extensions(
294             extensions_header, allow_quoted_string=allow_quoted_string)
295
296         self._logger.debug(
297             'Extensions requested: %r',
298             map(common.ExtensionParameter.name,
299                 self._request.ws_requested_extensions))
300
301     def _validate_key(self, key):
302         if key.find(',') >= 0:
303             raise HandshakeException('Request has multiple %s header lines or '
304                                      'contains illegal character \',\': %r' %
305                                      (common.SEC_WEBSOCKET_KEY_HEADER, key))
306
307         # Validate
308         key_is_valid = False
309         try:
310             # Validate key by quick regex match before parsing by base64
311             # module. Because base64 module skips invalid characters, we have
312             # to do this in advance to make this server strictly reject illegal
313             # keys.
314             if _SEC_WEBSOCKET_KEY_REGEX.match(key):
315                 decoded_key = base64.b64decode(key)
316                 if len(decoded_key) == 16:
317                     key_is_valid = True
318         except TypeError, e:
319             pass
320
321         if not key_is_valid:
322             raise HandshakeException(
323                 'Illegal value for header %s: %r' %
324                 (common.SEC_WEBSOCKET_KEY_HEADER, key))
325
326         return decoded_key
327
328     def _get_key(self):
329         key = get_mandatory_header(
330             self._request, common.SEC_WEBSOCKET_KEY_HEADER)
331
332         decoded_key = self._validate_key(key)
333
334         self._logger.debug(
335             '%s: %r (%s)',
336             common.SEC_WEBSOCKET_KEY_HEADER,
337             key,
338             util.hexify(decoded_key))
339
340         return key
341
342     def _send_handshake(self, accept):
343         response = []
344
345         response.append('HTTP/1.1 101 Switching Protocols\r\n')
346
347         response.append(format_header(
348             common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE))
349         response.append(format_header(
350             common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE))
351         response.append(format_header(
352             common.SEC_WEBSOCKET_ACCEPT_HEADER, accept))
353         if self._request.ws_protocol is not None:
354             response.append(format_header(
355                 common.SEC_WEBSOCKET_PROTOCOL_HEADER,
356                 self._request.ws_protocol))
357         if (self._request.ws_extensions is not None and
358             len(self._request.ws_extensions) != 0):
359             response.append(format_header(
360                 common.SEC_WEBSOCKET_EXTENSIONS_HEADER,
361                 format_extensions(self._request.ws_extensions)))
362         response.append('\r\n')
363
364         raw_response = ''.join(response)
365         self._request.connection.write(raw_response)
366         self._logger.debug('Sent server\'s opening handshake: %r',
367                            raw_response)
368
369
370 # vi:sts=4 sw=4 et