12da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Copyright 2011, Google Inc. 22da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# All rights reserved. 32da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# 42da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Redistribution and use in source and binary forms, with or without 52da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# modification, are permitted provided that the following conditions are 62da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# met: 72da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# 82da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# * Redistributions of source code must retain the above copyright 92da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# notice, this list of conditions and the following disclaimer. 102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# * Redistributions in binary form must reproduce the above 112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# copyright notice, this list of conditions and the following disclaimer 122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# in the documentation and/or other materials provided with the 132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# distribution. 142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# * Neither the name of Google Inc. nor the names of its 152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# contributors may be used to endorse or promote products derived from 162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# this software without specific prior written permission. 172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# 182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis"""This file provides a class for parsing/building frames of the WebSocket 322da489cd246702bee5938545b18a6f710ed214bcJamie Gennisprotocol version HyBi 00 and Hixie 75. 332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 342da489cd246702bee5938545b18a6f710ed214bcJamie GennisSpecification: 352da489cd246702bee5938545b18a6f710ed214bcJamie Gennishttp://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis""" 372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 392da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket import common 402da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket._stream_base import BadOperationException 412da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket._stream_base import ConnectionTerminatedException 422da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket._stream_base import InvalidFrameException 432da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket._stream_base import StreamBase 442da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket._stream_base import UnsupportedFrameException 452da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfrom mod_pywebsocket import util 462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 482da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass StreamHixie75(StreamBase): 492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """A class for parsing/building frames of the WebSocket protocol version 502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis HyBi 00 and Hixie 75. 512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def __init__(self, request, enable_closing_handshake=False): 542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Construct an instance. 552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis request: mod_python request. 582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis enable_closing_handshake: to let StreamHixie75 perform closing 592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis handshake as specified in HyBi 00, set 602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis this option to True. 612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis StreamBase.__init__(self, request) 642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger = util.get_class_logger(self) 662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._enable_closing_handshake = enable_closing_handshake 682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._request.client_terminated = False 702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._request.server_terminated = False 712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def send_message(self, message, end=True, binary=False): 732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Send message. 742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Args: 762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis message: unicode string to send. 772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis binary: not used in hixie75. 782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Raises: 802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis BadOperationException: when called on a server-terminated 812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis connection. 822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if not end: 852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise BadOperationException( 862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'StreamHixie75 doesn\'t support send_message with end=False') 872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if binary: 892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise BadOperationException( 902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'StreamHixie75 doesn\'t support send_message with binary=True') 912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if self._request.server_terminated: 932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise BadOperationException( 942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'Requested send_message after sending out a closing handshake') 952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._write(''.join(['\x00', message.encode('utf-8'), '\xff'])) 972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _read_payload_length_hixie75(self): 992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Reads a length header in a Hixie75 version frame with length. 1002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Raises: 1022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis ConnectionTerminatedException: when read returns empty string. 1032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 1042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis length = 0 1062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis while True: 1072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis b_str = self._read(1) 1082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis b = ord(b_str) 1092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis length = length * 128 + (b & 0x7f) 1102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (b & 0x80) == 0: 1112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis break 1122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return length 1132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def receive_message(self): 1152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Receive a WebSocket frame and return its payload an unicode string. 1162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Returns: 1182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis payload unicode string in a WebSocket frame. 1192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Raises: 1212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis ConnectionTerminatedException: when read returns empty 1222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis string. 1232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis BadOperationException: when called on a client-terminated 1242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis connection. 1252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 1262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if self._request.client_terminated: 1282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise BadOperationException( 1292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'Requested receive_message after receiving a closing ' 1302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'handshake') 1312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis while True: 1332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Read 1 byte. 1342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # mp_conn.read will block if no bytes are available. 1352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Timeout is controlled by TimeOut directive of Apache. 1362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis frame_type_str = self.receive_bytes(1) 1372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis frame_type = ord(frame_type_str) 1382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if (frame_type & 0x80) == 0x80: 1392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # The payload length is specified in the frame. 1402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Read and discard. 1412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis length = self._read_payload_length_hixie75() 1422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if length > 0: 1432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis _ = self.receive_bytes(length) 1442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # 5.3 3. 12. if /type/ is 0xFF and /length/ is 0, then set the 1452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # /client terminated/ flag and abort these steps. 1462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if not self._enable_closing_handshake: 1472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis continue 1482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if frame_type == 0xFF and length == 0: 1502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._request.client_terminated = True 1512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if self._request.server_terminated: 1532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger.debug( 1542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'Received ack for server-initiated closing ' 1552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'handshake') 1562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return None 1572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger.debug( 1592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'Received client-initiated closing handshake') 1602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._send_closing_handshake() 1622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger.debug( 1632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'Sent ack for client-initiated closing handshake') 1642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return None 1652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis else: 1662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # The payload is delimited with \xff. 1672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis bytes = self._read_until('\xff') 1682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # The WebSocket protocol section 4.4 specifies that invalid 1692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # characters must be replaced with U+fffd REPLACEMENT 1702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # CHARACTER. 1712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis message = bytes.decode('utf-8', 'replace') 1722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if frame_type == 0x00: 1732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return message 1742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # Discard data of other types. 1752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def _send_closing_handshake(self): 1772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if not self._enable_closing_handshake: 1782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise BadOperationException( 1792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'Closing handshake is not supported in Hixie 75 protocol') 1802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._request.server_terminated = True 1822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # 5.3 the server may decide to terminate the WebSocket connection by 1842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # running through the following steps: 1852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # 1. send a 0xFF byte and a 0x00 byte to the client to indicate the 1862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # start of the closing handshake. 1872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._write('\xff\x00') 1882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def close_connection(self, unused_code='', unused_reason=''): 1902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """Closes a WebSocket connection. 1912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis Raises: 1932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis ConnectionTerminatedException: when closing handshake was 1942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis not successfull. 1952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis """ 1962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 1972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if self._request.server_terminated: 1982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger.debug( 1992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'Requested close_connection but server is already terminated') 2002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return 2012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if not self._enable_closing_handshake: 2032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._request.server_terminated = True 2042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger.debug('Connection closed') 2052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis return 2062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._send_closing_handshake() 2082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis self._logger.debug('Sent server-initiated closing handshake') 2092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # TODO(ukai): 2. wait until the /client terminated/ flag has been set, 2112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # or until a server-defined timeout expires. 2122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # 2132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # For now, we expect receiving closing handshake right after sending 2142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # out closing handshake, and if we couldn't receive non-handshake 2152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # frame, we take it as ConnectionTerminatedException. 2162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis message = self.receive_message() 2172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis if message is not None: 2182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise ConnectionTerminatedException( 2192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'Didn\'t receive valid ack for closing handshake') 2202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # TODO: 3. close the WebSocket connection. 2212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis # note: mod_python Connection (mp_conn) doesn't have close method. 2222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis def send_ping(self, body): 2242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis raise BadOperationException( 2252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 'StreamHixie75 doesn\'t support send_ping') 2262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis 2282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# vi:sts=4 sw=4 et 229