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

     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.
    31 """This file provides a class for parsing/building frames of the WebSocket
    32 protocol version HyBi 00 and Hixie 75.
    34 Specification:
    35 http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
    36 """
    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
    48 class StreamHixie75(StreamBase):
    49     """A class for parsing/building frames of the WebSocket protocol version
    50     HyBi 00 and Hixie 75.
    51     """
    53     def __init__(self, request, enable_closing_handshake=False):
    54         """Construct an instance.
    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         """
    63         StreamBase.__init__(self, request)
    65         self._logger = util.get_class_logger(self)
    67         self._enable_closing_handshake = enable_closing_handshake
    69         self._request.client_terminated = False
    70         self._request.server_terminated = False
    72     def send_message(self, message, end=True, binary=False):
    73         """Send message.
    75         Args:
    76             message: unicode string to send.
    77             binary: not used in hixie75.
    79         Raises:
    80             BadOperationException: when called on a server-terminated
    81                 connection.
    82         """
    84         if not end:
    85             raise BadOperationException(
    86                 'StreamHixie75 doesn\'t support send_message with end=False')
    88         if binary:
    89             raise BadOperationException(
    90                 'StreamHixie75 doesn\'t support send_message with binary=True')
    92         if self._request.server_terminated:
    93             raise BadOperationException(
    94                 'Requested send_message after sending out a closing handshake')
    96         self._write(''.join(['\x00', message.encode('utf-8'), '\xff']))
    98     def _read_payload_length_hixie75(self):
    99         """Reads a length header in a Hixie75 version frame with length.
   101         Raises:
   102             ConnectionTerminatedException: when read returns empty string.
   103         """
   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
   114     def receive_message(self):
   115         """Receive a WebSocket frame and return its payload an unicode string.
   117         Returns:
   118             payload unicode string in a WebSocket frame.
   120         Raises:
   121             ConnectionTerminatedException: when read returns empty
   122                 string.
   123             BadOperationException: when called on a client-terminated
   124                 connection.
   125         """
   127         if self._request.client_terminated:
   128             raise BadOperationException(
   129                 'Requested receive_message after receiving a closing '
   130                 'handshake')
   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
   149                 if frame_type == 0xFF and length == 0:
   150                     self._request.client_terminated = True
   152                     if self._request.server_terminated:
   153                         self._logger.debug(
   154                             'Received ack for server-initiated closing '
   155                             'handshake')
   156                         return None
   158                     self._logger.debug(
   159                         'Received client-initiated closing handshake')
   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.
   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')
   181         self._request.server_terminated = True
   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')
   189     def close_connection(self, unused_code='', unused_reason=''):
   190         """Closes a WebSocket connection.
   192         Raises:
   193             ConnectionTerminatedException: when closing handshake was
   194                 not successfull.
   195         """
   197         if self._request.server_terminated:
   198             self._logger.debug(
   199                 'Requested close_connection but server is already terminated')
   200             return
   202         if not self._enable_closing_handshake:
   203             self._request.server_terminated = True
   204             self._logger.debug('Connection closed')
   205             return
   207         self._send_closing_handshake()
   208         self._logger.debug('Sent server-initiated closing handshake')
   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.
   223     def send_ping(self, body):
   224         raise BadOperationException(
   225             'StreamHixie75 doesn\'t support send_ping')
   228 # vi:sts=4 sw=4 et

mercurial