testing/mochitest/pywebsocket/mod_pywebsocket/_stream_hixie75.py

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 # Copyright 2011, Google Inc.
michael@0 2 # All rights reserved.
michael@0 3 #
michael@0 4 # Redistribution and use in source and binary forms, with or without
michael@0 5 # modification, are permitted provided that the following conditions are
michael@0 6 # met:
michael@0 7 #
michael@0 8 # * Redistributions of source code must retain the above copyright
michael@0 9 # notice, this list of conditions and the following disclaimer.
michael@0 10 # * Redistributions in binary form must reproduce the above
michael@0 11 # copyright notice, this list of conditions and the following disclaimer
michael@0 12 # in the documentation and/or other materials provided with the
michael@0 13 # distribution.
michael@0 14 # * Neither the name of Google Inc. nor the names of its
michael@0 15 # contributors may be used to endorse or promote products derived from
michael@0 16 # this software without specific prior written permission.
michael@0 17 #
michael@0 18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
michael@0 19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
michael@0 20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
michael@0 21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
michael@0 22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
michael@0 23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
michael@0 24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
michael@0 25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
michael@0 26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
michael@0 27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
michael@0 28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
michael@0 29
michael@0 30
michael@0 31 """This file provides a class for parsing/building frames of the WebSocket
michael@0 32 protocol version HyBi 00 and Hixie 75.
michael@0 33
michael@0 34 Specification:
michael@0 35 http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
michael@0 36 """
michael@0 37
michael@0 38
michael@0 39 from mod_pywebsocket import common
michael@0 40 from mod_pywebsocket._stream_base import BadOperationException
michael@0 41 from mod_pywebsocket._stream_base import ConnectionTerminatedException
michael@0 42 from mod_pywebsocket._stream_base import InvalidFrameException
michael@0 43 from mod_pywebsocket._stream_base import StreamBase
michael@0 44 from mod_pywebsocket._stream_base import UnsupportedFrameException
michael@0 45 from mod_pywebsocket import util
michael@0 46
michael@0 47
michael@0 48 class StreamHixie75(StreamBase):
michael@0 49 """A class for parsing/building frames of the WebSocket protocol version
michael@0 50 HyBi 00 and Hixie 75.
michael@0 51 """
michael@0 52
michael@0 53 def __init__(self, request, enable_closing_handshake=False):
michael@0 54 """Construct an instance.
michael@0 55
michael@0 56 Args:
michael@0 57 request: mod_python request.
michael@0 58 enable_closing_handshake: to let StreamHixie75 perform closing
michael@0 59 handshake as specified in HyBi 00, set
michael@0 60 this option to True.
michael@0 61 """
michael@0 62
michael@0 63 StreamBase.__init__(self, request)
michael@0 64
michael@0 65 self._logger = util.get_class_logger(self)
michael@0 66
michael@0 67 self._enable_closing_handshake = enable_closing_handshake
michael@0 68
michael@0 69 self._request.client_terminated = False
michael@0 70 self._request.server_terminated = False
michael@0 71
michael@0 72 def send_message(self, message, end=True, binary=False):
michael@0 73 """Send message.
michael@0 74
michael@0 75 Args:
michael@0 76 message: unicode string to send.
michael@0 77 binary: not used in hixie75.
michael@0 78
michael@0 79 Raises:
michael@0 80 BadOperationException: when called on a server-terminated
michael@0 81 connection.
michael@0 82 """
michael@0 83
michael@0 84 if not end:
michael@0 85 raise BadOperationException(
michael@0 86 'StreamHixie75 doesn\'t support send_message with end=False')
michael@0 87
michael@0 88 if binary:
michael@0 89 raise BadOperationException(
michael@0 90 'StreamHixie75 doesn\'t support send_message with binary=True')
michael@0 91
michael@0 92 if self._request.server_terminated:
michael@0 93 raise BadOperationException(
michael@0 94 'Requested send_message after sending out a closing handshake')
michael@0 95
michael@0 96 self._write(''.join(['\x00', message.encode('utf-8'), '\xff']))
michael@0 97
michael@0 98 def _read_payload_length_hixie75(self):
michael@0 99 """Reads a length header in a Hixie75 version frame with length.
michael@0 100
michael@0 101 Raises:
michael@0 102 ConnectionTerminatedException: when read returns empty string.
michael@0 103 """
michael@0 104
michael@0 105 length = 0
michael@0 106 while True:
michael@0 107 b_str = self._read(1)
michael@0 108 b = ord(b_str)
michael@0 109 length = length * 128 + (b & 0x7f)
michael@0 110 if (b & 0x80) == 0:
michael@0 111 break
michael@0 112 return length
michael@0 113
michael@0 114 def receive_message(self):
michael@0 115 """Receive a WebSocket frame and return its payload an unicode string.
michael@0 116
michael@0 117 Returns:
michael@0 118 payload unicode string in a WebSocket frame.
michael@0 119
michael@0 120 Raises:
michael@0 121 ConnectionTerminatedException: when read returns empty
michael@0 122 string.
michael@0 123 BadOperationException: when called on a client-terminated
michael@0 124 connection.
michael@0 125 """
michael@0 126
michael@0 127 if self._request.client_terminated:
michael@0 128 raise BadOperationException(
michael@0 129 'Requested receive_message after receiving a closing '
michael@0 130 'handshake')
michael@0 131
michael@0 132 while True:
michael@0 133 # Read 1 byte.
michael@0 134 # mp_conn.read will block if no bytes are available.
michael@0 135 # Timeout is controlled by TimeOut directive of Apache.
michael@0 136 frame_type_str = self.receive_bytes(1)
michael@0 137 frame_type = ord(frame_type_str)
michael@0 138 if (frame_type & 0x80) == 0x80:
michael@0 139 # The payload length is specified in the frame.
michael@0 140 # Read and discard.
michael@0 141 length = self._read_payload_length_hixie75()
michael@0 142 if length > 0:
michael@0 143 _ = self.receive_bytes(length)
michael@0 144 # 5.3 3. 12. if /type/ is 0xFF and /length/ is 0, then set the
michael@0 145 # /client terminated/ flag and abort these steps.
michael@0 146 if not self._enable_closing_handshake:
michael@0 147 continue
michael@0 148
michael@0 149 if frame_type == 0xFF and length == 0:
michael@0 150 self._request.client_terminated = True
michael@0 151
michael@0 152 if self._request.server_terminated:
michael@0 153 self._logger.debug(
michael@0 154 'Received ack for server-initiated closing '
michael@0 155 'handshake')
michael@0 156 return None
michael@0 157
michael@0 158 self._logger.debug(
michael@0 159 'Received client-initiated closing handshake')
michael@0 160
michael@0 161 self._send_closing_handshake()
michael@0 162 self._logger.debug(
michael@0 163 'Sent ack for client-initiated closing handshake')
michael@0 164 return None
michael@0 165 else:
michael@0 166 # The payload is delimited with \xff.
michael@0 167 bytes = self._read_until('\xff')
michael@0 168 # The WebSocket protocol section 4.4 specifies that invalid
michael@0 169 # characters must be replaced with U+fffd REPLACEMENT
michael@0 170 # CHARACTER.
michael@0 171 message = bytes.decode('utf-8', 'replace')
michael@0 172 if frame_type == 0x00:
michael@0 173 return message
michael@0 174 # Discard data of other types.
michael@0 175
michael@0 176 def _send_closing_handshake(self):
michael@0 177 if not self._enable_closing_handshake:
michael@0 178 raise BadOperationException(
michael@0 179 'Closing handshake is not supported in Hixie 75 protocol')
michael@0 180
michael@0 181 self._request.server_terminated = True
michael@0 182
michael@0 183 # 5.3 the server may decide to terminate the WebSocket connection by
michael@0 184 # running through the following steps:
michael@0 185 # 1. send a 0xFF byte and a 0x00 byte to the client to indicate the
michael@0 186 # start of the closing handshake.
michael@0 187 self._write('\xff\x00')
michael@0 188
michael@0 189 def close_connection(self, unused_code='', unused_reason=''):
michael@0 190 """Closes a WebSocket connection.
michael@0 191
michael@0 192 Raises:
michael@0 193 ConnectionTerminatedException: when closing handshake was
michael@0 194 not successfull.
michael@0 195 """
michael@0 196
michael@0 197 if self._request.server_terminated:
michael@0 198 self._logger.debug(
michael@0 199 'Requested close_connection but server is already terminated')
michael@0 200 return
michael@0 201
michael@0 202 if not self._enable_closing_handshake:
michael@0 203 self._request.server_terminated = True
michael@0 204 self._logger.debug('Connection closed')
michael@0 205 return
michael@0 206
michael@0 207 self._send_closing_handshake()
michael@0 208 self._logger.debug('Sent server-initiated closing handshake')
michael@0 209
michael@0 210 # TODO(ukai): 2. wait until the /client terminated/ flag has been set,
michael@0 211 # or until a server-defined timeout expires.
michael@0 212 #
michael@0 213 # For now, we expect receiving closing handshake right after sending
michael@0 214 # out closing handshake, and if we couldn't receive non-handshake
michael@0 215 # frame, we take it as ConnectionTerminatedException.
michael@0 216 message = self.receive_message()
michael@0 217 if message is not None:
michael@0 218 raise ConnectionTerminatedException(
michael@0 219 'Didn\'t receive valid ack for closing handshake')
michael@0 220 # TODO: 3. close the WebSocket connection.
michael@0 221 # note: mod_python Connection (mp_conn) doesn't have close method.
michael@0 222
michael@0 223 def send_ping(self, body):
michael@0 224 raise BadOperationException(
michael@0 225 'StreamHixie75 doesn\'t support send_ping')
michael@0 226
michael@0 227
michael@0 228 # vi:sts=4 sw=4 et

mercurial