don't use autoinstall to import pywebsocket but check it in WebKit directly.
[WebKit-https.git] / Tools / Scripts / webkitpy / thirdparty / mod_pywebsocket / handshake / hybi00.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 version HyBi 00.
33
34 Specification:
35 http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
36 """
37
38
39 # Note: request.connection.write/read are used in this module, even though
40 # mod_python document says that they should be used only in connection
41 # handlers. Unfortunately, we have no other options. For example,
42 # request.write/read are not suitable because they don't allow direct raw bytes
43 # writing/reading.
44
45
46 import logging
47 import re
48 import struct
49
50 from mod_pywebsocket import common
51 from mod_pywebsocket.stream import StreamHixie75
52 from mod_pywebsocket import util
53 from mod_pywebsocket.handshake._base import HandshakeException
54 from mod_pywebsocket.handshake._base import build_location
55 from mod_pywebsocket.handshake._base import check_header_lines
56 from mod_pywebsocket.handshake._base import format_header
57 from mod_pywebsocket.handshake._base import get_mandatory_header
58 from mod_pywebsocket.handshake._base import validate_subprotocol
59
60
61 _MANDATORY_HEADERS = [
62     # key, expected value or None
63     [common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75],
64     [common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE],
65 ]
66
67
68 class Handshaker(object):
69     """Opening handshake processor for the WebSocket protocol version HyBi 00.
70     """
71
72     def __init__(self, request, dispatcher):
73         """Construct an instance.
74
75         Args:
76             request: mod_python request.
77             dispatcher: Dispatcher (dispatch.Dispatcher).
78
79         Handshaker will add attributes such as ws_resource in performing
80         handshake.
81         """
82
83         self._logger = util.get_class_logger(self)
84
85         self._request = request
86         self._dispatcher = dispatcher
87
88     def do_handshake(self):
89         """Perform WebSocket Handshake.
90
91         On _request, we set
92             ws_resource, ws_protocol, ws_location, ws_origin, ws_challenge,
93             ws_challenge_md5: WebSocket handshake information.
94             ws_stream: Frame generation/parsing class.
95             ws_version: Protocol version.
96
97         Raises:
98             HandshakeException: when any error happened in parsing the opening
99                                 handshake request.
100         """
101
102         # 5.1 Reading the client's opening handshake.
103         # dispatcher sets it in self._request.
104         check_header_lines(self._request, _MANDATORY_HEADERS)
105         self._set_resource()
106         self._set_subprotocol()
107         self._set_location()
108         self._set_origin()
109         self._set_challenge_response()
110         self._set_protocol_version()
111
112         self._dispatcher.do_extra_handshake(self._request)
113
114         self._send_handshake()
115
116     def _set_resource(self):
117         self._request.ws_resource = self._request.uri
118
119     def _set_subprotocol(self):
120         # |Sec-WebSocket-Protocol|
121         subprotocol = self._request.headers_in.get(
122             common.SEC_WEBSOCKET_PROTOCOL_HEADER)
123         if subprotocol is not None:
124             validate_subprotocol(subprotocol, hixie=True)
125         self._request.ws_protocol = subprotocol
126
127     def _set_location(self):
128         # |Host|
129         host = self._request.headers_in.get(common.HOST_HEADER)
130         if host is not None:
131             self._request.ws_location = build_location(self._request)
132         # TODO(ukai): check host is this host.
133
134     def _set_origin(self):
135         # |Origin|
136         origin = self._request.headers_in.get(common.ORIGIN_HEADER)
137         if origin is not None:
138             self._request.ws_origin = origin
139
140     def _set_protocol_version(self):
141         # |Sec-WebSocket-Draft|
142         draft = self._request.headers_in.get(common.SEC_WEBSOCKET_DRAFT_HEADER)
143         if draft is not None and draft != '0':
144             raise HandshakeException('Illegal value for %s: %s' %
145                                      (common.SEC_WEBSOCKET_DRAFT_HEADER,
146                                       draft))
147
148         self._logger.debug('Protocol version is HyBi 00')
149         self._request.ws_version = common.VERSION_HYBI00
150         self._request.ws_stream = StreamHixie75(self._request, True)
151
152     def _set_challenge_response(self):
153         # 5.2 4-8.
154         self._request.ws_challenge = self._get_challenge()
155         # 5.2 9. let /response/ be the MD5 finterprint of /challenge/
156         self._request.ws_challenge_md5 = util.md5_hash(
157             self._request.ws_challenge).digest()
158         self._logger.debug(
159             'Challenge: %r (%s)',
160             self._request.ws_challenge,
161             util.hexify(self._request.ws_challenge))
162         self._logger.debug(
163             'Challenge response: %r (%s)',
164             self._request.ws_challenge_md5,
165             util.hexify(self._request.ws_challenge_md5))
166
167     def _get_key_value(self, key_field):
168         key_value = get_mandatory_header(self._request, key_field)
169
170         self._logger.debug('%s: %r', key_field, key_value)
171
172         # 5.2 4. let /key-number_n/ be the digits (characters in the range
173         # U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)) in /key_n/,
174         # interpreted as a base ten integer, ignoring all other characters
175         # in /key_n/.
176         try:
177             key_number = int(re.sub("\\D", "", key_value))
178         except:
179             raise HandshakeException('%s field contains no digit' % key_field)
180         # 5.2 5. let /spaces_n/ be the number of U+0020 SPACE characters
181         # in /key_n/.
182         spaces = re.subn(" ", "", key_value)[1]
183         if spaces == 0:
184             raise HandshakeException('%s field contains no space' % key_field)
185
186         self._logger.debug(
187             '%s: Key-number is %d and number of spaces is %d',
188             key_field, key_number, spaces)
189
190         # 5.2 6. if /key-number_n/ is not an integral multiple of /spaces_n/
191         # then abort the WebSocket connection.
192         if key_number % spaces != 0:
193             raise HandshakeException(
194                 '%s: Key-number (%d) is not an integral multiple of spaces '
195                 '(%d)' % (key_field, key_number, spaces))
196         # 5.2 7. let /part_n/ be /key-number_n/ divided by /spaces_n/.
197         part = key_number / spaces
198         self._logger.debug('%s: Part is %d', key_field, part)
199         return part
200
201     def _get_challenge(self):
202         # 5.2 4-7.
203         key1 = self._get_key_value(common.SEC_WEBSOCKET_KEY1_HEADER)
204         key2 = self._get_key_value(common.SEC_WEBSOCKET_KEY2_HEADER)
205         # 5.2 8. let /challenge/ be the concatenation of /part_1/,
206         challenge = ''
207         challenge += struct.pack('!I', key1)  # network byteorder int
208         challenge += struct.pack('!I', key2)  # network byteorder int
209         challenge += self._request.connection.read(8)
210         return challenge
211
212     def _send_handshake(self):
213         response = []
214
215         # 5.2 10. send the following line.
216         response.append('HTTP/1.1 101 WebSocket Protocol Handshake\r\n')
217
218         # 5.2 11. send the following fields to the client.
219         response.append(format_header(
220             common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75))
221         response.append(format_header(
222             common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE))
223         response.append(format_header(
224             common.SEC_WEBSOCKET_LOCATION_HEADER, self._request.ws_location))
225         response.append(format_header(
226             common.SEC_WEBSOCKET_ORIGIN_HEADER, self._request.ws_origin))
227         if self._request.ws_protocol:
228             response.append(format_header(
229                 common.SEC_WEBSOCKET_PROTOCOL_HEADER,
230                 self._request.ws_protocol))
231         # 5.2 12. send two bytes 0x0D 0x0A.
232         response.append('\r\n')
233         # 5.2 13. send /response/
234         response.append(self._request.ws_challenge_md5)
235
236         raw_response = ''.join(response)
237         self._request.connection.write(raw_response)
238         self._logger.debug('Sent server\'s opening handshake: %r',
239                            raw_response)
240
241
242 # vi:sts=4 sw=4 et