don't use autoinstall to import pywebsocket but check it in WebKit directly.
[WebKit-https.git] / Tools / Scripts / webkitpy / thirdparty / mod_pywebsocket / handshake / draft75.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 """WebSocket handshaking defined in draft-hixie-thewebsocketprotocol-75."""
32
33
34 # Note: request.connection.write is used in this module, even though mod_python
35 # document says that it should be used only in connection handlers.
36 # Unfortunately, we have no other options. For example, request.write is not
37 # suitable because it doesn't allow direct raw bytes writing.
38
39
40 import logging
41 import re
42
43 from mod_pywebsocket import common
44 from mod_pywebsocket.stream import StreamHixie75
45 from mod_pywebsocket import util
46 from mod_pywebsocket.handshake._base import HandshakeException
47 from mod_pywebsocket.handshake._base import build_location
48 from mod_pywebsocket.handshake._base import validate_subprotocol
49
50
51 _MANDATORY_HEADERS = [
52     # key, expected value or None
53     ['Upgrade', 'WebSocket'],
54     ['Connection', 'Upgrade'],
55     ['Host', None],
56     ['Origin', None],
57 ]
58
59 _FIRST_FIVE_LINES = map(re.compile, [
60     r'^GET /[\S]* HTTP/1.1\r\n$',
61     r'^Upgrade: WebSocket\r\n$',
62     r'^Connection: Upgrade\r\n$',
63     r'^Host: [\S]+\r\n$',
64     r'^Origin: [\S]+\r\n$',
65 ])
66
67 _SIXTH_AND_LATER = re.compile(
68     r'^'
69     r'(WebSocket-Protocol: [\x20-\x7e]+\r\n)?'
70     r'(Cookie: [^\r]*\r\n)*'
71     r'(Cookie2: [^\r]*\r\n)?'
72     r'(Cookie: [^\r]*\r\n)*'
73     r'\r\n')
74
75
76 class Handshaker(object):
77     """This class performs WebSocket handshake."""
78
79     def __init__(self, request, dispatcher, strict=False):
80         """Construct an instance.
81
82         Args:
83             request: mod_python request.
84             dispatcher: Dispatcher (dispatch.Dispatcher).
85             strict: Strictly check handshake request. Default: False.
86                 If True, request.connection must provide get_memorized_lines
87                 method.
88
89         Handshaker will add attributes such as ws_resource in performing
90         handshake.
91         """
92
93         self._logger = util.get_class_logger(self)
94
95         self._request = request
96         self._dispatcher = dispatcher
97         self._strict = strict
98
99     def do_handshake(self):
100         """Perform WebSocket Handshake.
101
102         On _request, we set
103             ws_resource, ws_origin, ws_location, ws_protocol
104             ws_challenge_md5: WebSocket handshake information.
105             ws_stream: Frame generation/parsing class.
106             ws_version: Protocol version.
107         """
108
109         self._check_header_lines()
110         self._set_resource()
111         self._set_origin()
112         self._set_location()
113         self._set_subprotocol()
114         self._set_protocol_version()
115
116         self._dispatcher.do_extra_handshake(self._request)
117
118         self._send_handshake()
119
120         self._logger.debug('Sent opening handshake response')
121
122     def _set_resource(self):
123         self._request.ws_resource = self._request.uri
124
125     def _set_origin(self):
126         self._request.ws_origin = self._request.headers_in['Origin']
127
128     def _set_location(self):
129         self._request.ws_location = build_location(self._request)
130
131     def _set_subprotocol(self):
132         subprotocol = self._request.headers_in.get('WebSocket-Protocol')
133         if subprotocol is not None:
134             validate_subprotocol(subprotocol, hixie=True)
135         self._request.ws_protocol = subprotocol
136
137     def _set_protocol_version(self):
138         self._logger.debug('IETF Hixie 75 protocol')
139         self._request.ws_version = common.VERSION_HIXIE75
140         self._request.ws_stream = StreamHixie75(self._request)
141
142     def _sendall(self, data):
143         self._request.connection.write(data)
144
145     def _send_handshake(self):
146         self._sendall('HTTP/1.1 101 Web Socket Protocol Handshake\r\n')
147         self._sendall('Upgrade: WebSocket\r\n')
148         self._sendall('Connection: Upgrade\r\n')
149         self._sendall('WebSocket-Origin: %s\r\n' % self._request.ws_origin)
150         self._sendall('WebSocket-Location: %s\r\n' % self._request.ws_location)
151         if self._request.ws_protocol:
152             self._sendall(
153                 'WebSocket-Protocol: %s\r\n' % self._request.ws_protocol)
154         self._sendall('\r\n')
155
156     def _check_header_lines(self):
157         for key, expected_value in _MANDATORY_HEADERS:
158             actual_value = self._request.headers_in.get(key)
159             if not actual_value:
160                 raise HandshakeException('Header %s is not defined' % key)
161             if expected_value:
162                 if actual_value != expected_value:
163                     raise HandshakeException(
164                         'Expected %r for header %s but found %r' %
165                         (expected_value, key, actual_value))
166         if self._strict:
167             try:
168                 lines = self._request.connection.get_memorized_lines()
169             except AttributeError, e:
170                 raise AttributeError(
171                     'Strict handshake is specified but the connection '
172                     'doesn\'t provide get_memorized_lines()')
173             self._check_first_lines(lines)
174
175     def _check_first_lines(self, lines):
176         if len(lines) < len(_FIRST_FIVE_LINES):
177             raise HandshakeException('Too few header lines: %d' % len(lines))
178         for line, regexp in zip(lines, _FIRST_FIVE_LINES):
179             if not regexp.search(line):
180                 raise HandshakeException(
181                     'Unexpected header: %r doesn\'t match %r'
182                     % (line, regexp.pattern))
183         sixth_and_later = ''.join(lines[5:])
184         if not _SIXTH_AND_LATER.search(sixth_and_later):
185             raise HandshakeException(
186                 'Unexpected header: %r doesn\'t match %r'
187                 % (sixth_and_later, _SIXTH_AND_LATER.pattern))
188
189
190 # vi:sts=4 sw=4 et