don't use autoinstall to import pywebsocket but check it in WebKit directly.
[WebKit-https.git] / Tools / Scripts / webkitpy / thirdparty / mod_pywebsocket / _stream_hixie75.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 a class for parsing/building frames of the WebSocket
32 protocol version HyBi 00 and Hixie 75.
33
34 Specification:
35 http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
36 """
37
38
39 from mod_pywebsocket import common
40 from mod_pywebsocket._stream_base import BadOperationException
41 from mod_pywebsocket._stream_base import ConnectionTerminatedException
42 from mod_pywebsocket._stream_base import InvalidFrameException
43 from mod_pywebsocket._stream_base import StreamBase
44 from mod_pywebsocket._stream_base import UnsupportedFrameException
45 from mod_pywebsocket import util
46
47
48 class StreamHixie75(StreamBase):
49     """A class for parsing/building frames of the WebSocket protocol version
50     HyBi 00 and Hixie 75.
51     """
52
53     def __init__(self, request, enable_closing_handshake=False):
54         """Construct an instance.
55
56         Args:
57             request: mod_python request.
58             enable_closing_handshake: to let StreamHixie75 perform closing
59                                       handshake as specified in HyBi 00, set
60                                       this option to True.
61         """
62
63         StreamBase.__init__(self, request)
64
65         self._logger = util.get_class_logger(self)
66
67         self._enable_closing_handshake = enable_closing_handshake
68
69         self._request.client_terminated = False
70         self._request.server_terminated = False
71
72     def send_message(self, message, end=True, binary=False):
73         """Send message.
74
75         Args:
76             message: unicode string to send.
77             binary: not used in hixie75.
78
79         Raises:
80             BadOperationException: when called on a server-terminated
81                 connection.
82         """
83
84         if not end:
85             raise BadOperationException(
86                 'StreamHixie75 doesn\'t support send_message with end=False')
87
88         if binary:
89             raise BadOperationException(
90                 'StreamHixie75 doesn\'t support send_message with binary=True')
91
92         if self._request.server_terminated:
93             raise BadOperationException(
94                 'Requested send_message after sending out a closing handshake')
95
96         self._write(''.join(['\x00', message.encode('utf-8'), '\xff']))
97
98     def _read_payload_length_hixie75(self):
99         """Reads a length header in a Hixie75 version frame with length.
100
101         Raises:
102             ConnectionTerminatedException: when read returns empty string.
103         """
104
105         length = 0
106         while True:
107             b_str = self._read(1)
108             b = ord(b_str)
109             length = length * 128 + (b & 0x7f)
110             if (b & 0x80) == 0:
111                 break
112         return length
113
114     def receive_message(self):
115         """Receive a WebSocket frame and return its payload an unicode string.
116
117         Returns:
118             payload unicode string in a WebSocket frame.
119
120         Raises:
121             ConnectionTerminatedException: when read returns empty
122                 string.
123             BadOperationException: when called on a client-terminated
124                 connection.
125         """
126
127         if self._request.client_terminated:
128             raise BadOperationException(
129                 'Requested receive_message after receiving a closing '
130                 'handshake')
131
132         while True:
133             # Read 1 byte.
134             # mp_conn.read will block if no bytes are available.
135             # Timeout is controlled by TimeOut directive of Apache.
136             frame_type_str = self.receive_bytes(1)
137             frame_type = ord(frame_type_str)
138             if (frame_type & 0x80) == 0x80:
139                 # The payload length is specified in the frame.
140                 # Read and discard.
141                 length = self._read_payload_length_hixie75()
142                 if length > 0:
143                     _ = self.receive_bytes(length)
144                 # 5.3 3. 12. if /type/ is 0xFF and /length/ is 0, then set the
145                 # /client terminated/ flag and abort these steps.
146                 if not self._enable_closing_handshake:
147                     continue
148
149                 if frame_type == 0xFF and length == 0:
150                     self._request.client_terminated = True
151
152                     if self._request.server_terminated:
153                         self._logger.debug(
154                             'Received ack for server-initiated closing '
155                             'handshake')
156                         return None
157
158                     self._logger.debug(
159                         'Received client-initiated closing handshake')
160
161                     self._send_closing_handshake()
162                     self._logger.debug(
163                         'Sent ack for client-initiated closing handshake')
164                     return None
165             else:
166                 # The payload is delimited with \xff.
167                 bytes = self._read_until('\xff')
168                 # The WebSocket protocol section 4.4 specifies that invalid
169                 # characters must be replaced with U+fffd REPLACEMENT
170                 # CHARACTER.
171                 message = bytes.decode('utf-8', 'replace')
172                 if frame_type == 0x00:
173                     return message
174                 # Discard data of other types.
175
176     def _send_closing_handshake(self):
177         if not self._enable_closing_handshake:
178             raise BadOperationException(
179                 'Closing handshake is not supported in Hixie 75 protocol')
180
181         self._request.server_terminated = True
182
183         # 5.3 the server may decide to terminate the WebSocket connection by
184         # running through the following steps:
185         # 1. send a 0xFF byte and a 0x00 byte to the client to indicate the
186         # start of the closing handshake.
187         self._write('\xff\x00')
188
189     def close_connection(self, unused_code='', unused_reason=''):
190         """Closes a WebSocket connection.
191
192         Raises:
193             ConnectionTerminatedException: when closing handshake was
194                 not successfull.
195         """
196
197         if self._request.server_terminated:
198             self._logger.debug(
199                 'Requested close_connection but server is already terminated')
200             return
201
202         if not self._enable_closing_handshake:
203             self._request.server_terminated = True
204             self._logger.debug('Connection closed')
205             return
206
207         self._send_closing_handshake()
208         self._logger.debug('Sent server-initiated closing handshake')
209
210         # TODO(ukai): 2. wait until the /client terminated/ flag has been set,
211         # or until a server-defined timeout expires.
212         #
213         # For now, we expect receiving closing handshake right after sending
214         # out closing handshake, and if we couldn't receive non-handshake
215         # frame, we take it as ConnectionTerminatedException.
216         message = self.receive_message()
217         if message is not None:
218             raise ConnectionTerminatedException(
219                 'Didn\'t receive valid ack for closing handshake')
220         # TODO: 3. close the WebSocket connection.
221         # note: mod_python Connection (mp_conn) doesn't have close method.
222
223     def send_ping(self, body):
224         raise BadOperationException(
225             'StreamHixie75 doesn\'t support send_ping')
226
227
228 # vi:sts=4 sw=4 et