diff options
| author | 2013-06-09 18:10:22 +0200 | |
|---|---|---|
| committer | 2013-06-09 18:10:23 +0200 | |
| commit | 16af85004c84d0d6c626b4f8424ce9647669a0c1 (patch) | |
| tree | 025d479862d376dbc17e934f4ed20031c8cd97d1 /module/lib/mod_pywebsocket | |
| parent | adapted to jshint config (diff) | |
| download | pyload-16af85004c84d0d6c626b4f8424ce9647669a0c1.tar.xz | |
moved everything from module to pyload
Diffstat (limited to 'module/lib/mod_pywebsocket')
20 files changed, 0 insertions, 7968 deletions
diff --git a/module/lib/mod_pywebsocket/COPYING b/module/lib/mod_pywebsocket/COPYING deleted file mode 100644 index 989d02e4c..000000000 --- a/module/lib/mod_pywebsocket/COPYING +++ /dev/null @@ -1,28 +0,0 @@ -Copyright 2012, Google Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -    * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -    * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. -    * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/module/lib/mod_pywebsocket/__init__.py b/module/lib/mod_pywebsocket/__init__.py deleted file mode 100644 index 454ae0c45..000000000 --- a/module/lib/mod_pywebsocket/__init__.py +++ /dev/null @@ -1,197 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""WebSocket extension for Apache HTTP Server. - -mod_pywebsocket is a WebSocket extension for Apache HTTP Server -intended for testing or experimental purposes. mod_python is required. - - -Installation -============ - -0. Prepare an Apache HTTP Server for which mod_python is enabled. - -1. Specify the following Apache HTTP Server directives to suit your -   configuration. - -   If mod_pywebsocket is not in the Python path, specify the following. -   <websock_lib> is the directory where mod_pywebsocket is installed. - -       PythonPath "sys.path+['<websock_lib>']" - -   Always specify the following. <websock_handlers> is the directory where -   user-written WebSocket handlers are placed. - -       PythonOption mod_pywebsocket.handler_root <websock_handlers> -       PythonHeaderParserHandler mod_pywebsocket.headerparserhandler - -   To limit the search for WebSocket handlers to a directory <scan_dir> -   under <websock_handlers>, configure as follows: - -       PythonOption mod_pywebsocket.handler_scan <scan_dir> - -   <scan_dir> is useful in saving scan time when <websock_handlers> -   contains many non-WebSocket handler files. - -   If you want to allow handlers whose canonical path is not under the root -   directory (i.e. symbolic link is in root directory but its target is not), -   configure as follows: - -       PythonOption mod_pywebsocket.allow_handlers_outside_root_dir On - -   Example snippet of httpd.conf: -   (mod_pywebsocket is in /websock_lib, WebSocket handlers are in -   /websock_handlers, port is 80 for ws, 443 for wss.) - -       <IfModule python_module> -         PythonPath "sys.path+['/websock_lib']" -         PythonOption mod_pywebsocket.handler_root /websock_handlers -         PythonHeaderParserHandler mod_pywebsocket.headerparserhandler -       </IfModule> - -2. Tune Apache parameters for serving WebSocket. We'd like to note that at -   least TimeOut directive from core features and RequestReadTimeout -   directive from mod_reqtimeout should be modified not to kill connections -   in only a few seconds of idle time. - -3. Verify installation. You can use example/console.html to poke the server. - - -Writing WebSocket handlers -========================== - -When a WebSocket request comes in, the resource name -specified in the handshake is considered as if it is a file path under -<websock_handlers> and the handler defined in -<websock_handlers>/<resource_name>_wsh.py is invoked. - -For example, if the resource name is /example/chat, the handler defined in -<websock_handlers>/example/chat_wsh.py is invoked. - -A WebSocket handler is composed of the following three functions: - -    web_socket_do_extra_handshake(request) -    web_socket_transfer_data(request) -    web_socket_passive_closing_handshake(request) - -where: -    request: mod_python request. - -web_socket_do_extra_handshake is called during the handshake after the -headers are successfully parsed and WebSocket properties (ws_location, -ws_origin, and ws_resource) are added to request. A handler -can reject the request by raising an exception. - -A request object has the following properties that you can use during the -extra handshake (web_socket_do_extra_handshake): -- ws_resource -- ws_origin -- ws_version -- ws_location (HyBi 00 only) -- ws_extensions (HyBi 06 and later) -- ws_deflate (HyBi 06 and later) -- ws_protocol -- ws_requested_protocols (HyBi 06 and later) - -The last two are a bit tricky. See the next subsection. - - -Subprotocol Negotiation ------------------------ - -For HyBi 06 and later, ws_protocol is always set to None when -web_socket_do_extra_handshake is called. If ws_requested_protocols is not -None, you must choose one subprotocol from this list and set it to -ws_protocol. - -For HyBi 00, when web_socket_do_extra_handshake is called, -ws_protocol is set to the value given by the client in -Sec-WebSocket-Protocol header or None if -such header was not found in the opening handshake request. Finish extra -handshake with ws_protocol untouched to accept the request subprotocol. -Then, Sec-WebSocket-Protocol header will be sent to -the client in response with the same value as requested. Raise an exception -in web_socket_do_extra_handshake to reject the requested subprotocol. - - -Data Transfer -------------- - -web_socket_transfer_data is called after the handshake completed -successfully. A handler can receive/send messages from/to the client -using request. mod_pywebsocket.msgutil module provides utilities -for data transfer. - -You can receive a message by the following statement. - -    message = request.ws_stream.receive_message() - -This call blocks until any complete text frame arrives, and the payload data -of the incoming frame will be stored into message. When you're using IETF -HyBi 00 or later protocol, receive_message() will return None on receiving -client-initiated closing handshake. When any error occurs, receive_message() -will raise some exception. - -You can send a message by the following statement. - -    request.ws_stream.send_message(message) - - -Closing Connection ------------------- - -Executing the following statement or just return-ing from -web_socket_transfer_data cause connection close. - -    request.ws_stream.close_connection() - -close_connection will wait -for closing handshake acknowledgement coming from the client. When it -couldn't receive a valid acknowledgement, raises an exception. - -web_socket_passive_closing_handshake is called after the server receives -incoming closing frame from the client peer immediately. You can specify -code and reason by return values. They are sent as a outgoing closing frame -from the server. A request object has the following properties that you can -use in web_socket_passive_closing_handshake. -- ws_close_code -- ws_close_reason - - -Threading ---------- - -A WebSocket handler must be thread-safe if the server (Apache or -standalone.py) is configured to use threads. -""" - - -# vi:sts=4 sw=4 et tw=72 diff --git a/module/lib/mod_pywebsocket/_stream_base.py b/module/lib/mod_pywebsocket/_stream_base.py deleted file mode 100644 index 60fb33d2c..000000000 --- a/module/lib/mod_pywebsocket/_stream_base.py +++ /dev/null @@ -1,165 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""Base stream class. -""" - - -# Note: request.connection.write/read are used in this module, even though -# mod_python document says that they should be used only in connection -# handlers. Unfortunately, we have no other options. For example, -# request.write/read are not suitable because they don't allow direct raw bytes -# writing/reading. - - -from mod_pywebsocket import util - - -# Exceptions - - -class ConnectionTerminatedException(Exception): -    """This exception will be raised when a connection is terminated -    unexpectedly. -    """ - -    pass - - -class InvalidFrameException(ConnectionTerminatedException): -    """This exception will be raised when we received an invalid frame we -    cannot parse. -    """ - -    pass - - -class BadOperationException(Exception): -    """This exception will be raised when send_message() is called on -    server-terminated connection or receive_message() is called on -    client-terminated connection. -    """ - -    pass - - -class UnsupportedFrameException(Exception): -    """This exception will be raised when we receive a frame with flag, opcode -    we cannot handle. Handlers can just catch and ignore this exception and -    call receive_message() again to continue processing the next frame. -    """ - -    pass - - -class InvalidUTF8Exception(Exception): -    """This exception will be raised when we receive a text frame which -    contains invalid UTF-8 strings. -    """ - -    pass - - -class StreamBase(object): -    """Base stream class.""" - -    def __init__(self, request): -        """Construct an instance. - -        Args: -            request: mod_python request. -        """ - -        self._logger = util.get_class_logger(self) - -        self._request = request - -    def _read(self, length): -        """Reads length bytes from connection. In case we catch any exception, -        prepends remote address to the exception message and raise again. - -        Raises: -            ConnectionTerminatedException: when read returns empty string. -        """ - -        bytes = self._request.connection.read(length) -        if not bytes: -            raise ConnectionTerminatedException( -                'Receiving %d byte failed. Peer (%r) closed connection' % -                (length, (self._request.connection.remote_addr,))) -        return bytes - -    def _write(self, bytes): -        """Writes given bytes to connection. In case we catch any exception, -        prepends remote address to the exception message and raise again. -        """ - -        try: -            self._request.connection.write(bytes) -        except Exception, e: -            util.prepend_message_to_exception( -                    'Failed to send message to %r: ' % -                            (self._request.connection.remote_addr,), -                    e) -            raise - -    def receive_bytes(self, length): -        """Receives multiple bytes. Retries read when we couldn't receive the -        specified amount. - -        Raises: -            ConnectionTerminatedException: when read returns empty string. -        """ - -        bytes = [] -        while length > 0: -            new_bytes = self._read(length) -            bytes.append(new_bytes) -            length -= len(new_bytes) -        return ''.join(bytes) - -    def _read_until(self, delim_char): -        """Reads bytes until we encounter delim_char. The result will not -        contain delim_char. - -        Raises: -            ConnectionTerminatedException: when read returns empty string. -        """ - -        bytes = [] -        while True: -            ch = self._read(1) -            if ch == delim_char: -                break -            bytes.append(ch) -        return ''.join(bytes) - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/_stream_hixie75.py b/module/lib/mod_pywebsocket/_stream_hixie75.py deleted file mode 100644 index 94cf5b31b..000000000 --- a/module/lib/mod_pywebsocket/_stream_hixie75.py +++ /dev/null @@ -1,229 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""This file provides a class for parsing/building frames of the WebSocket -protocol version HyBi 00 and Hixie 75. - -Specification: -- HyBi 00 http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 -- Hixie 75 http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 -""" - - -from mod_pywebsocket import common -from mod_pywebsocket._stream_base import BadOperationException -from mod_pywebsocket._stream_base import ConnectionTerminatedException -from mod_pywebsocket._stream_base import InvalidFrameException -from mod_pywebsocket._stream_base import StreamBase -from mod_pywebsocket._stream_base import UnsupportedFrameException -from mod_pywebsocket import util - - -class StreamHixie75(StreamBase): -    """A class for parsing/building frames of the WebSocket protocol version -    HyBi 00 and Hixie 75. -    """ - -    def __init__(self, request, enable_closing_handshake=False): -        """Construct an instance. - -        Args: -            request: mod_python request. -            enable_closing_handshake: to let StreamHixie75 perform closing -                                      handshake as specified in HyBi 00, set -                                      this option to True. -        """ - -        StreamBase.__init__(self, request) - -        self._logger = util.get_class_logger(self) - -        self._enable_closing_handshake = enable_closing_handshake - -        self._request.client_terminated = False -        self._request.server_terminated = False - -    def send_message(self, message, end=True, binary=False): -        """Send message. - -        Args: -            message: unicode string to send. -            binary: not used in hixie75. - -        Raises: -            BadOperationException: when called on a server-terminated -                connection. -        """ - -        if not end: -            raise BadOperationException( -                'StreamHixie75 doesn\'t support send_message with end=False') - -        if binary: -            raise BadOperationException( -                'StreamHixie75 doesn\'t support send_message with binary=True') - -        if self._request.server_terminated: -            raise BadOperationException( -                'Requested send_message after sending out a closing handshake') - -        self._write(''.join(['\x00', message.encode('utf-8'), '\xff'])) - -    def _read_payload_length_hixie75(self): -        """Reads a length header in a Hixie75 version frame with length. - -        Raises: -            ConnectionTerminatedException: when read returns empty string. -        """ - -        length = 0 -        while True: -            b_str = self._read(1) -            b = ord(b_str) -            length = length * 128 + (b & 0x7f) -            if (b & 0x80) == 0: -                break -        return length - -    def receive_message(self): -        """Receive a WebSocket frame and return its payload an unicode string. - -        Returns: -            payload unicode string in a WebSocket frame. - -        Raises: -            ConnectionTerminatedException: when read returns empty -                string. -            BadOperationException: when called on a client-terminated -                connection. -        """ - -        if self._request.client_terminated: -            raise BadOperationException( -                'Requested receive_message after receiving a closing ' -                'handshake') - -        while True: -            # Read 1 byte. -            # mp_conn.read will block if no bytes are available. -            # Timeout is controlled by TimeOut directive of Apache. -            frame_type_str = self.receive_bytes(1) -            frame_type = ord(frame_type_str) -            if (frame_type & 0x80) == 0x80: -                # The payload length is specified in the frame. -                # Read and discard. -                length = self._read_payload_length_hixie75() -                if length > 0: -                    _ = self.receive_bytes(length) -                # 5.3 3. 12. if /type/ is 0xFF and /length/ is 0, then set the -                # /client terminated/ flag and abort these steps. -                if not self._enable_closing_handshake: -                    continue - -                if frame_type == 0xFF and length == 0: -                    self._request.client_terminated = True - -                    if self._request.server_terminated: -                        self._logger.debug( -                            'Received ack for server-initiated closing ' -                            'handshake') -                        return None - -                    self._logger.debug( -                        'Received client-initiated closing handshake') - -                    self._send_closing_handshake() -                    self._logger.debug( -                        'Sent ack for client-initiated closing handshake') -                    return None -            else: -                # The payload is delimited with \xff. -                bytes = self._read_until('\xff') -                # The WebSocket protocol section 4.4 specifies that invalid -                # characters must be replaced with U+fffd REPLACEMENT -                # CHARACTER. -                message = bytes.decode('utf-8', 'replace') -                if frame_type == 0x00: -                    return message -                # Discard data of other types. - -    def _send_closing_handshake(self): -        if not self._enable_closing_handshake: -            raise BadOperationException( -                'Closing handshake is not supported in Hixie 75 protocol') - -        self._request.server_terminated = True - -        # 5.3 the server may decide to terminate the WebSocket connection by -        # running through the following steps: -        # 1. send a 0xFF byte and a 0x00 byte to the client to indicate the -        # start of the closing handshake. -        self._write('\xff\x00') - -    def close_connection(self, unused_code='', unused_reason=''): -        """Closes a WebSocket connection. - -        Raises: -            ConnectionTerminatedException: when closing handshake was -                not successfull. -        """ - -        if self._request.server_terminated: -            self._logger.debug( -                'Requested close_connection but server is already terminated') -            return - -        if not self._enable_closing_handshake: -            self._request.server_terminated = True -            self._logger.debug('Connection closed') -            return - -        self._send_closing_handshake() -        self._logger.debug('Sent server-initiated closing handshake') - -        # TODO(ukai): 2. wait until the /client terminated/ flag has been set, -        # or until a server-defined timeout expires. -        # -        # For now, we expect receiving closing handshake right after sending -        # out closing handshake, and if we couldn't receive non-handshake -        # frame, we take it as ConnectionTerminatedException. -        message = self.receive_message() -        if message is not None: -            raise ConnectionTerminatedException( -                'Didn\'t receive valid ack for closing handshake') -        # TODO: 3. close the WebSocket connection. -        # note: mod_python Connection (mp_conn) doesn't have close method. - -    def send_ping(self, body): -        raise BadOperationException( -            'StreamHixie75 doesn\'t support send_ping') - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/_stream_hybi.py b/module/lib/mod_pywebsocket/_stream_hybi.py deleted file mode 100644 index bd158fa6b..000000000 --- a/module/lib/mod_pywebsocket/_stream_hybi.py +++ /dev/null @@ -1,915 +0,0 @@ -# Copyright 2012, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""This file provides classes and helper functions for parsing/building frames -of the WebSocket protocol (RFC 6455). - -Specification: -http://tools.ietf.org/html/rfc6455 -""" - - -from collections import deque -import logging -import os -import struct -import time - -from mod_pywebsocket import common -from mod_pywebsocket import util -from mod_pywebsocket._stream_base import BadOperationException -from mod_pywebsocket._stream_base import ConnectionTerminatedException -from mod_pywebsocket._stream_base import InvalidFrameException -from mod_pywebsocket._stream_base import InvalidUTF8Exception -from mod_pywebsocket._stream_base import StreamBase -from mod_pywebsocket._stream_base import UnsupportedFrameException - - -_NOOP_MASKER = util.NoopMasker() - - -class Frame(object): - -    def __init__(self, fin=1, rsv1=0, rsv2=0, rsv3=0, -                 opcode=None, payload=''): -        self.fin = fin -        self.rsv1 = rsv1 -        self.rsv2 = rsv2 -        self.rsv3 = rsv3 -        self.opcode = opcode -        self.payload = payload - - -# Helper functions made public to be used for writing unittests for WebSocket -# clients. - - -def create_length_header(length, mask): -    """Creates a length header. - -    Args: -        length: Frame length. Must be less than 2^63. -        mask: Mask bit. Must be boolean. - -    Raises: -        ValueError: when bad data is given. -    """ - -    if mask: -        mask_bit = 1 << 7 -    else: -        mask_bit = 0 - -    if length < 0: -        raise ValueError('length must be non negative integer') -    elif length <= 125: -        return chr(mask_bit | length) -    elif length < (1 << 16): -        return chr(mask_bit | 126) + struct.pack('!H', length) -    elif length < (1 << 63): -        return chr(mask_bit | 127) + struct.pack('!Q', length) -    else: -        raise ValueError('Payload is too big for one frame') - - -def create_header(opcode, payload_length, fin, rsv1, rsv2, rsv3, mask): -    """Creates a frame header. - -    Raises: -        Exception: when bad data is given. -    """ - -    if opcode < 0 or 0xf < opcode: -        raise ValueError('Opcode out of range') - -    if payload_length < 0 or (1 << 63) <= payload_length: -        raise ValueError('payload_length out of range') - -    if (fin | rsv1 | rsv2 | rsv3) & ~1: -        raise ValueError('FIN bit and Reserved bit parameter must be 0 or 1') - -    header = '' - -    first_byte = ((fin << 7) -                  | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4) -                  | opcode) -    header += chr(first_byte) -    header += create_length_header(payload_length, mask) - -    return header - - -def _build_frame(header, body, mask): -    if not mask: -        return header + body - -    masking_nonce = os.urandom(4) -    masker = util.RepeatedXorMasker(masking_nonce) - -    return header + masking_nonce + masker.mask(body) - - -def _filter_and_format_frame_object(frame, mask, frame_filters): -    for frame_filter in frame_filters: -        frame_filter.filter(frame) - -    header = create_header( -        frame.opcode, len(frame.payload), frame.fin, -        frame.rsv1, frame.rsv2, frame.rsv3, mask) -    return _build_frame(header, frame.payload, mask) - - -def create_binary_frame( -    message, opcode=common.OPCODE_BINARY, fin=1, mask=False, frame_filters=[]): -    """Creates a simple binary frame with no extension, reserved bit.""" - -    frame = Frame(fin=fin, opcode=opcode, payload=message) -    return _filter_and_format_frame_object(frame, mask, frame_filters) - - -def create_text_frame( -    message, opcode=common.OPCODE_TEXT, fin=1, mask=False, frame_filters=[]): -    """Creates a simple text frame with no extension, reserved bit.""" - -    encoded_message = message.encode('utf-8') -    return create_binary_frame(encoded_message, opcode, fin, mask, -                               frame_filters) - - -def parse_frame(receive_bytes, logger=None, -                ws_version=common.VERSION_HYBI_LATEST, -                unmask_receive=True): -    """Parses a frame. Returns a tuple containing each header field and -    payload. - -    Args: -        receive_bytes: a function that reads frame data from a stream or -            something similar. The function takes length of the bytes to be -            read. The function must raise ConnectionTerminatedException if -            there is not enough data to be read. -        logger: a logging object. -        ws_version: the version of WebSocket protocol. -        unmask_receive: unmask received frames. When received unmasked -            frame, raises InvalidFrameException. - -    Raises: -        ConnectionTerminatedException: when receive_bytes raises it. -        InvalidFrameException: when the frame contains invalid data. -    """ - -    if not logger: -        logger = logging.getLogger() - -    logger.log(common.LOGLEVEL_FINE, 'Receive the first 2 octets of a frame') - -    received = receive_bytes(2) - -    first_byte = ord(received[0]) -    fin = (first_byte >> 7) & 1 -    rsv1 = (first_byte >> 6) & 1 -    rsv2 = (first_byte >> 5) & 1 -    rsv3 = (first_byte >> 4) & 1 -    opcode = first_byte & 0xf - -    second_byte = ord(received[1]) -    mask = (second_byte >> 7) & 1 -    payload_length = second_byte & 0x7f - -    logger.log(common.LOGLEVEL_FINE, -               'FIN=%s, RSV1=%s, RSV2=%s, RSV3=%s, opcode=%s, ' -               'Mask=%s, Payload_length=%s', -               fin, rsv1, rsv2, rsv3, opcode, mask, payload_length) - -    if (mask == 1) != unmask_receive: -        raise InvalidFrameException( -            'Mask bit on the received frame did\'nt match masking ' -            'configuration for received frames') - -    # The HyBi and later specs disallow putting a value in 0x0-0xFFFF -    # into the 8-octet extended payload length field (or 0x0-0xFD in -    # 2-octet field). -    valid_length_encoding = True -    length_encoding_bytes = 1 -    if payload_length == 127: -        logger.log(common.LOGLEVEL_FINE, -                   'Receive 8-octet extended payload length') - -        extended_payload_length = receive_bytes(8) -        payload_length = struct.unpack( -            '!Q', extended_payload_length)[0] -        if payload_length > 0x7FFFFFFFFFFFFFFF: -            raise InvalidFrameException( -                'Extended payload length >= 2^63') -        if ws_version >= 13 and payload_length < 0x10000: -            valid_length_encoding = False -            length_encoding_bytes = 8 - -        logger.log(common.LOGLEVEL_FINE, -                   'Decoded_payload_length=%s', payload_length) -    elif payload_length == 126: -        logger.log(common.LOGLEVEL_FINE, -                   'Receive 2-octet extended payload length') - -        extended_payload_length = receive_bytes(2) -        payload_length = struct.unpack( -            '!H', extended_payload_length)[0] -        if ws_version >= 13 and payload_length < 126: -            valid_length_encoding = False -            length_encoding_bytes = 2 - -        logger.log(common.LOGLEVEL_FINE, -                   'Decoded_payload_length=%s', payload_length) - -    if not valid_length_encoding: -        logger.warning( -            'Payload length is not encoded using the minimal number of ' -            'bytes (%d is encoded using %d bytes)', -            payload_length, -            length_encoding_bytes) - -    if mask == 1: -        logger.log(common.LOGLEVEL_FINE, 'Receive mask') - -        masking_nonce = receive_bytes(4) -        masker = util.RepeatedXorMasker(masking_nonce) - -        logger.log(common.LOGLEVEL_FINE, 'Mask=%r', masking_nonce) -    else: -        masker = _NOOP_MASKER - -    logger.log(common.LOGLEVEL_FINE, 'Receive payload data') -    if logger.isEnabledFor(common.LOGLEVEL_FINE): -        receive_start = time.time() - -    raw_payload_bytes = receive_bytes(payload_length) - -    if logger.isEnabledFor(common.LOGLEVEL_FINE): -        logger.log( -            common.LOGLEVEL_FINE, -            'Done receiving payload data at %s MB/s', -            payload_length / (time.time() - receive_start) / 1000 / 1000) -    logger.log(common.LOGLEVEL_FINE, 'Unmask payload data') - -    if logger.isEnabledFor(common.LOGLEVEL_FINE): -        unmask_start = time.time() - -    bytes = masker.mask(raw_payload_bytes) - -    if logger.isEnabledFor(common.LOGLEVEL_FINE): -        logger.log( -            common.LOGLEVEL_FINE, -            'Done unmasking payload data at %s MB/s', -            payload_length / (time.time() - unmask_start) / 1000 / 1000) - -    return opcode, bytes, fin, rsv1, rsv2, rsv3 - - -class FragmentedFrameBuilder(object): -    """A stateful class to send a message as fragments.""" - -    def __init__(self, mask, frame_filters=[], encode_utf8=True): -        """Constructs an instance.""" - -        self._mask = mask -        self._frame_filters = frame_filters -        # This is for skipping UTF-8 encoding when building text type frames -        # from compressed data. -        self._encode_utf8 = encode_utf8 - -        self._started = False - -        # Hold opcode of the first frame in messages to verify types of other -        # frames in the message are all the same. -        self._opcode = common.OPCODE_TEXT - -    def build(self, payload_data, end, binary): -        if binary: -            frame_type = common.OPCODE_BINARY -        else: -            frame_type = common.OPCODE_TEXT -        if self._started: -            if self._opcode != frame_type: -                raise ValueError('Message types are different in frames for ' -                                 'the same message') -            opcode = common.OPCODE_CONTINUATION -        else: -            opcode = frame_type -            self._opcode = frame_type - -        if end: -            self._started = False -            fin = 1 -        else: -            self._started = True -            fin = 0 - -        if binary or not self._encode_utf8: -            return create_binary_frame( -                payload_data, opcode, fin, self._mask, self._frame_filters) -        else: -            return create_text_frame( -                payload_data, opcode, fin, self._mask, self._frame_filters) - - -def _create_control_frame(opcode, body, mask, frame_filters): -    frame = Frame(opcode=opcode, payload=body) - -    for frame_filter in frame_filters: -        frame_filter.filter(frame) - -    if len(frame.payload) > 125: -        raise BadOperationException( -            'Payload data size of control frames must be 125 bytes or less') - -    header = create_header( -        frame.opcode, len(frame.payload), frame.fin, -        frame.rsv1, frame.rsv2, frame.rsv3, mask) -    return _build_frame(header, frame.payload, mask) - - -def create_ping_frame(body, mask=False, frame_filters=[]): -    return _create_control_frame(common.OPCODE_PING, body, mask, frame_filters) - - -def create_pong_frame(body, mask=False, frame_filters=[]): -    return _create_control_frame(common.OPCODE_PONG, body, mask, frame_filters) - - -def create_close_frame(body, mask=False, frame_filters=[]): -    return _create_control_frame( -        common.OPCODE_CLOSE, body, mask, frame_filters) - - -def create_closing_handshake_body(code, reason): -    body = '' -    if code is not None: -        if (code > common.STATUS_USER_PRIVATE_MAX or -            code < common.STATUS_NORMAL_CLOSURE): -            raise BadOperationException('Status code is out of range') -        if (code == common.STATUS_NO_STATUS_RECEIVED or -            code == common.STATUS_ABNORMAL_CLOSURE or -            code == common.STATUS_TLS_HANDSHAKE): -            raise BadOperationException('Status code is reserved pseudo ' -                'code') -        encoded_reason = reason.encode('utf-8') -        body = struct.pack('!H', code) + encoded_reason -    return body - - -class StreamOptions(object): -    """Holds option values to configure Stream objects.""" - -    def __init__(self): -        """Constructs StreamOptions.""" - -        # Enables deflate-stream extension. -        self.deflate_stream = False - -        # Filters applied to frames. -        self.outgoing_frame_filters = [] -        self.incoming_frame_filters = [] - -        # Filters applied to messages. Control frames are not affected by them. -        self.outgoing_message_filters = [] -        self.incoming_message_filters = [] - -        self.encode_text_message_to_utf8 = True -        self.mask_send = False -        self.unmask_receive = True -        # RFC6455 disallows fragmented control frames, but mux extension -        # relaxes the restriction. -        self.allow_fragmented_control_frame = False - - -class Stream(StreamBase): -    """A class for parsing/building frames of the WebSocket protocol -    (RFC 6455). -    """ - -    def __init__(self, request, options): -        """Constructs an instance. - -        Args: -            request: mod_python request. -        """ - -        StreamBase.__init__(self, request) - -        self._logger = util.get_class_logger(self) - -        self._options = options - -        if self._options.deflate_stream: -            self._logger.debug('Setup filter for deflate-stream') -            self._request = util.DeflateRequest(self._request) - -        self._request.client_terminated = False -        self._request.server_terminated = False - -        # Holds body of received fragments. -        self._received_fragments = [] -        # Holds the opcode of the first fragment. -        self._original_opcode = None - -        self._writer = FragmentedFrameBuilder( -            self._options.mask_send, self._options.outgoing_frame_filters, -            self._options.encode_text_message_to_utf8) - -        self._ping_queue = deque() - -    def _receive_frame(self): -        """Receives a frame and return data in the frame as a tuple containing -        each header field and payload separately. - -        Raises: -            ConnectionTerminatedException: when read returns empty -                string. -            InvalidFrameException: when the frame contains invalid data. -        """ - -        def _receive_bytes(length): -            return self.receive_bytes(length) - -        return parse_frame(receive_bytes=_receive_bytes, -                           logger=self._logger, -                           ws_version=self._request.ws_version, -                           unmask_receive=self._options.unmask_receive) - -    def _receive_frame_as_frame_object(self): -        opcode, bytes, fin, rsv1, rsv2, rsv3 = self._receive_frame() - -        return Frame(fin=fin, rsv1=rsv1, rsv2=rsv2, rsv3=rsv3, -                     opcode=opcode, payload=bytes) - -    def receive_filtered_frame(self): -        """Receives a frame and applies frame filters and message filters. -        The frame to be received must satisfy following conditions: -        - The frame is not fragmented. -        - The opcode of the frame is TEXT or BINARY. - -        DO NOT USE this method except for testing purpose. -        """ - -        frame = self._receive_frame_as_frame_object() -        if not frame.fin: -            raise InvalidFrameException( -                'Segmented frames must not be received via ' -                'receive_filtered_frame()') -        if (frame.opcode != common.OPCODE_TEXT and -            frame.opcode != common.OPCODE_BINARY): -            raise InvalidFrameException( -                'Control frames must not be received via ' -                'receive_filtered_frame()') - -        for frame_filter in self._options.incoming_frame_filters: -            frame_filter.filter(frame) -        for message_filter in self._options.incoming_message_filters: -            frame.payload = message_filter.filter(frame.payload) -        return frame - -    def send_message(self, message, end=True, binary=False): -        """Send message. - -        Args: -            message: text in unicode or binary in str to send. -            binary: send message as binary frame. - -        Raises: -            BadOperationException: when called on a server-terminated -                connection or called with inconsistent message type or -                binary parameter. -        """ - -        if self._request.server_terminated: -            raise BadOperationException( -                'Requested send_message after sending out a closing handshake') - -        if binary and isinstance(message, unicode): -            raise BadOperationException( -                'Message for binary frame must be instance of str') - -        for message_filter in self._options.outgoing_message_filters: -            message = message_filter.filter(message, end, binary) - -        try: -            # Set this to any positive integer to limit maximum size of data in -            # payload data of each frame. -            MAX_PAYLOAD_DATA_SIZE = -1 - -            if MAX_PAYLOAD_DATA_SIZE <= 0: -                self._write(self._writer.build(message, end, binary)) -                return - -            bytes_written = 0 -            while True: -                end_for_this_frame = end -                bytes_to_write = len(message) - bytes_written -                if (MAX_PAYLOAD_DATA_SIZE > 0 and -                    bytes_to_write > MAX_PAYLOAD_DATA_SIZE): -                    end_for_this_frame = False -                    bytes_to_write = MAX_PAYLOAD_DATA_SIZE - -                frame = self._writer.build( -                    message[bytes_written:bytes_written + bytes_to_write], -                    end_for_this_frame, -                    binary) -                self._write(frame) - -                bytes_written += bytes_to_write - -                # This if must be placed here (the end of while block) so that -                # at least one frame is sent. -                if len(message) <= bytes_written: -                    break -        except ValueError, e: -            raise BadOperationException(e) - -    def _get_message_from_frame(self, frame): -        """Gets a message from frame. If the message is composed of fragmented -        frames and the frame is not the last fragmented frame, this method -        returns None. The whole message will be returned when the last -        fragmented frame is passed to this method. - -        Raises: -            InvalidFrameException: when the frame doesn't match defragmentation -                context, or the frame contains invalid data. -        """ - -        if frame.opcode == common.OPCODE_CONTINUATION: -            if not self._received_fragments: -                if frame.fin: -                    raise InvalidFrameException( -                        'Received a termination frame but fragmentation ' -                        'not started') -                else: -                    raise InvalidFrameException( -                        'Received an intermediate frame but ' -                        'fragmentation not started') - -            if frame.fin: -                # End of fragmentation frame -                self._received_fragments.append(frame.payload) -                message = ''.join(self._received_fragments) -                self._received_fragments = [] -                return message -            else: -                # Intermediate frame -                self._received_fragments.append(frame.payload) -                return None -        else: -            if self._received_fragments: -                if frame.fin: -                    raise InvalidFrameException( -                        'Received an unfragmented frame without ' -                        'terminating existing fragmentation') -                else: -                    raise InvalidFrameException( -                        'New fragmentation started without terminating ' -                        'existing fragmentation') - -            if frame.fin: -                # Unfragmented frame - -                self._original_opcode = frame.opcode -                return frame.payload -            else: -                # Start of fragmentation frame - -                if (not self._options.allow_fragmented_control_frame and -                    common.is_control_opcode(frame.opcode)): -                    raise InvalidFrameException( -                        'Control frames must not be fragmented') - -                self._original_opcode = frame.opcode -                self._received_fragments.append(frame.payload) -                return None - -    def _process_close_message(self, message): -        """Processes close message. - -        Args: -            message: close message. - -        Raises: -            InvalidFrameException: when the message is invalid. -        """ - -        self._request.client_terminated = True - -        # Status code is optional. We can have status reason only if we -        # have status code. Status reason can be empty string. So, -        # allowed cases are -        # - no application data: no code no reason -        # - 2 octet of application data: has code but no reason -        # - 3 or more octet of application data: both code and reason -        if len(message) == 0: -            self._logger.debug('Received close frame (empty body)') -            self._request.ws_close_code = ( -                common.STATUS_NO_STATUS_RECEIVED) -        elif len(message) == 1: -            raise InvalidFrameException( -                'If a close frame has status code, the length of ' -                'status code must be 2 octet') -        elif len(message) >= 2: -            self._request.ws_close_code = struct.unpack( -                '!H', message[0:2])[0] -            self._request.ws_close_reason = message[2:].decode( -                'utf-8', 'replace') -            self._logger.debug( -                'Received close frame (code=%d, reason=%r)', -                self._request.ws_close_code, -                self._request.ws_close_reason) - -        # Drain junk data after the close frame if necessary. -        self._drain_received_data() - -        if self._request.server_terminated: -            self._logger.debug( -                'Received ack for server-initiated closing handshake') -            return - -        self._logger.debug( -            'Received client-initiated closing handshake') - -        code = common.STATUS_NORMAL_CLOSURE -        reason = '' -        if hasattr(self._request, '_dispatcher'): -            dispatcher = self._request._dispatcher -            code, reason = dispatcher.passive_closing_handshake( -                self._request) -            if code is None and reason is not None and len(reason) > 0: -                self._logger.warning( -                    'Handler specified reason despite code being None') -                reason = '' -            if reason is None: -                reason = '' -        self._send_closing_handshake(code, reason) -        self._logger.debug( -            'Sent ack for client-initiated closing handshake ' -            '(code=%r, reason=%r)', code, reason) - -    def _process_ping_message(self, message): -        """Processes ping message. - -        Args: -            message: ping message. -        """ - -        try: -            handler = self._request.on_ping_handler -            if handler: -                handler(self._request, message) -                return -        except AttributeError, e: -            pass -        self._send_pong(message) - -    def _process_pong_message(self, message): -        """Processes pong message. - -        Args: -            message: pong message. -        """ - -        # TODO(tyoshino): Add ping timeout handling. - -        inflight_pings = deque() - -        while True: -            try: -                expected_body = self._ping_queue.popleft() -                if expected_body == message: -                    # inflight_pings contains pings ignored by the -                    # other peer. Just forget them. -                    self._logger.debug( -                        'Ping %r is acked (%d pings were ignored)', -                        expected_body, len(inflight_pings)) -                    break -                else: -                    inflight_pings.append(expected_body) -            except IndexError, e: -                # The received pong was unsolicited pong. Keep the -                # ping queue as is. -                self._ping_queue = inflight_pings -                self._logger.debug('Received a unsolicited pong') -                break - -        try: -            handler = self._request.on_pong_handler -            if handler: -                handler(self._request, message) -        except AttributeError, e: -            pass - -    def receive_message(self): -        """Receive a WebSocket frame and return its payload as a text in -        unicode or a binary in str. - -        Returns: -            payload data of the frame -            - as unicode instance if received text frame -            - as str instance if received binary frame -            or None iff received closing handshake. -        Raises: -            BadOperationException: when called on a client-terminated -                connection. -            ConnectionTerminatedException: when read returns empty -                string. -            InvalidFrameException: when the frame contains invalid -                data. -            UnsupportedFrameException: when the received frame has -                flags, opcode we cannot handle. You can ignore this -                exception and continue receiving the next frame. -        """ - -        if self._request.client_terminated: -            raise BadOperationException( -                'Requested receive_message after receiving a closing ' -                'handshake') - -        while True: -            # mp_conn.read will block if no bytes are available. -            # Timeout is controlled by TimeOut directive of Apache. - -            frame = self._receive_frame_as_frame_object() - -            # Check the constraint on the payload size for control frames -            # before extension processes the frame. -            # See also http://tools.ietf.org/html/rfc6455#section-5.5 -            if (common.is_control_opcode(frame.opcode) and -                len(frame.payload) > 125): -                raise InvalidFrameException( -                    'Payload data size of control frames must be 125 bytes or ' -                    'less') - -            for frame_filter in self._options.incoming_frame_filters: -                frame_filter.filter(frame) - -            if frame.rsv1 or frame.rsv2 or frame.rsv3: -                raise UnsupportedFrameException( -                    'Unsupported flag is set (rsv = %d%d%d)' % -                    (frame.rsv1, frame.rsv2, frame.rsv3)) - -            message = self._get_message_from_frame(frame) -            if message is None: -                continue - -            for message_filter in self._options.incoming_message_filters: -                message = message_filter.filter(message) - -            if self._original_opcode == common.OPCODE_TEXT: -                # The WebSocket protocol section 4.4 specifies that invalid -                # characters must be replaced with U+fffd REPLACEMENT -                # CHARACTER. -                try: -                    return message.decode('utf-8') -                except UnicodeDecodeError, e: -                    raise InvalidUTF8Exception(e) -            elif self._original_opcode == common.OPCODE_BINARY: -                return message -            elif self._original_opcode == common.OPCODE_CLOSE: -                self._process_close_message(message) -                return None -            elif self._original_opcode == common.OPCODE_PING: -                self._process_ping_message(message) -            elif self._original_opcode == common.OPCODE_PONG: -                self._process_pong_message(message) -            else: -                raise UnsupportedFrameException( -                    'Opcode %d is not supported' % self._original_opcode) - -    def _send_closing_handshake(self, code, reason): -        body = create_closing_handshake_body(code, reason) -        frame = create_close_frame( -            body, mask=self._options.mask_send, -            frame_filters=self._options.outgoing_frame_filters) - -        self._request.server_terminated = True - -        self._write(frame) - -    def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''): -        """Closes a WebSocket connection. - -        Args: -            code: Status code for close frame. If code is None, a close -                frame with empty body will be sent. -            reason: string representing close reason. -        Raises: -            BadOperationException: when reason is specified with code None -            or reason is not an instance of both str and unicode. -        """ - -        if self._request.server_terminated: -            self._logger.debug( -                'Requested close_connection but server is already terminated') -            return - -        if code is None: -            if reason is not None and len(reason) > 0: -                raise BadOperationException( -                    'close reason must not be specified if code is None') -            reason = '' -        else: -            if not isinstance(reason, str) and not isinstance(reason, unicode): -                raise BadOperationException( -                    'close reason must be an instance of str or unicode') - -        self._send_closing_handshake(code, reason) -        self._logger.debug( -            'Sent server-initiated closing handshake (code=%r, reason=%r)', -            code, reason) - -        if (code == common.STATUS_GOING_AWAY or -            code == common.STATUS_PROTOCOL_ERROR): -            # It doesn't make sense to wait for a close frame if the reason is -            # protocol error or that the server is going away. For some of -            # other reasons, it might not make sense to wait for a close frame, -            # but it's not clear, yet. -            return - -        # TODO(ukai): 2. wait until the /client terminated/ flag has been set, -        # or until a server-defined timeout expires. -        # -        # For now, we expect receiving closing handshake right after sending -        # out closing handshake. -        message = self.receive_message() -        if message is not None: -            raise ConnectionTerminatedException( -                'Didn\'t receive valid ack for closing handshake') -        # TODO: 3. close the WebSocket connection. -        # note: mod_python Connection (mp_conn) doesn't have close method. - -    def send_ping(self, body=''): -        frame = create_ping_frame( -            body, -            self._options.mask_send, -            self._options.outgoing_frame_filters) -        self._write(frame) - -        self._ping_queue.append(body) - -    def _send_pong(self, body): -        frame = create_pong_frame( -            body, -            self._options.mask_send, -            self._options.outgoing_frame_filters) -        self._write(frame) - -    def get_last_received_opcode(self): -        """Returns the opcode of the WebSocket message which the last received -        frame belongs to. The return value is valid iff immediately after -        receive_message call. -        """ - -        return self._original_opcode - -    def _drain_received_data(self): -        """Drains unread data in the receive buffer to avoid sending out TCP -        RST packet. This is because when deflate-stream is enabled, some -        DEFLATE block for flushing data may follow a close frame. If any data -        remains in the receive buffer of a socket when the socket is closed, -        it sends out TCP RST packet to the other peer. - -        Since mod_python's mp_conn object doesn't support non-blocking read, -        we perform this only when pywebsocket is running in standalone mode. -        """ - -        # If self._options.deflate_stream is true, self._request is -        # DeflateRequest, so we can get wrapped request object by -        # self._request._request. -        # -        # Only _StandaloneRequest has _drain_received_data method. -        if (self._options.deflate_stream and -            ('_drain_received_data' in dir(self._request._request))): -            self._request._request._drain_received_data() - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/common.py b/module/lib/mod_pywebsocket/common.py deleted file mode 100644 index 2388379c0..000000000 --- a/module/lib/mod_pywebsocket/common.py +++ /dev/null @@ -1,307 +0,0 @@ -# Copyright 2012, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""This file must not depend on any module specific to the WebSocket protocol. -""" - - -from mod_pywebsocket import http_header_util - - -# Additional log level definitions. -LOGLEVEL_FINE = 9 - -# Constants indicating WebSocket protocol version. -VERSION_HIXIE75 = -1 -VERSION_HYBI00 = 0 -VERSION_HYBI01 = 1 -VERSION_HYBI02 = 2 -VERSION_HYBI03 = 2 -VERSION_HYBI04 = 4 -VERSION_HYBI05 = 5 -VERSION_HYBI06 = 6 -VERSION_HYBI07 = 7 -VERSION_HYBI08 = 8 -VERSION_HYBI09 = 8 -VERSION_HYBI10 = 8 -VERSION_HYBI11 = 8 -VERSION_HYBI12 = 8 -VERSION_HYBI13 = 13 -VERSION_HYBI14 = 13 -VERSION_HYBI15 = 13 -VERSION_HYBI16 = 13 -VERSION_HYBI17 = 13 - -# Constants indicating WebSocket protocol latest version. -VERSION_HYBI_LATEST = VERSION_HYBI13 - -# Port numbers -DEFAULT_WEB_SOCKET_PORT = 80 -DEFAULT_WEB_SOCKET_SECURE_PORT = 443 - -# Schemes -WEB_SOCKET_SCHEME = 'ws' -WEB_SOCKET_SECURE_SCHEME = 'wss' - -# Frame opcodes defined in the spec. -OPCODE_CONTINUATION = 0x0 -OPCODE_TEXT = 0x1 -OPCODE_BINARY = 0x2 -OPCODE_CLOSE = 0x8 -OPCODE_PING = 0x9 -OPCODE_PONG = 0xa - -# UUIDs used by HyBi 04 and later opening handshake and frame masking. -WEBSOCKET_ACCEPT_UUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' - -# Opening handshake header names and expected values. -UPGRADE_HEADER = 'Upgrade' -WEBSOCKET_UPGRADE_TYPE = 'websocket' -WEBSOCKET_UPGRADE_TYPE_HIXIE75 = 'WebSocket' -CONNECTION_HEADER = 'Connection' -UPGRADE_CONNECTION_TYPE = 'Upgrade' -HOST_HEADER = 'Host' -ORIGIN_HEADER = 'Origin' -SEC_WEBSOCKET_ORIGIN_HEADER = 'Sec-WebSocket-Origin' -SEC_WEBSOCKET_KEY_HEADER = 'Sec-WebSocket-Key' -SEC_WEBSOCKET_ACCEPT_HEADER = 'Sec-WebSocket-Accept' -SEC_WEBSOCKET_VERSION_HEADER = 'Sec-WebSocket-Version' -SEC_WEBSOCKET_PROTOCOL_HEADER = 'Sec-WebSocket-Protocol' -SEC_WEBSOCKET_EXTENSIONS_HEADER = 'Sec-WebSocket-Extensions' -SEC_WEBSOCKET_DRAFT_HEADER = 'Sec-WebSocket-Draft' -SEC_WEBSOCKET_KEY1_HEADER = 'Sec-WebSocket-Key1' -SEC_WEBSOCKET_KEY2_HEADER = 'Sec-WebSocket-Key2' -SEC_WEBSOCKET_LOCATION_HEADER = 'Sec-WebSocket-Location' - -# Extensions -DEFLATE_STREAM_EXTENSION = 'deflate-stream' -DEFLATE_FRAME_EXTENSION = 'deflate-frame' -PERFRAME_COMPRESSION_EXTENSION = 'perframe-compress' -PERMESSAGE_COMPRESSION_EXTENSION = 'permessage-compress' -X_WEBKIT_DEFLATE_FRAME_EXTENSION = 'x-webkit-deflate-frame' -X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION = 'x-webkit-permessage-compress' -MUX_EXTENSION = 'mux_DO_NOT_USE' - -# Status codes -# Code STATUS_NO_STATUS_RECEIVED, STATUS_ABNORMAL_CLOSURE, and -# STATUS_TLS_HANDSHAKE are pseudo codes to indicate specific error cases. -# Could not be used for codes in actual closing frames. -# Application level errors must use codes in the range -# STATUS_USER_REGISTERED_BASE to STATUS_USER_PRIVATE_MAX. The codes in the -# range STATUS_USER_REGISTERED_BASE to STATUS_USER_REGISTERED_MAX are managed -# by IANA. Usually application must define user protocol level errors in the -# range STATUS_USER_PRIVATE_BASE to STATUS_USER_PRIVATE_MAX. -STATUS_NORMAL_CLOSURE = 1000 -STATUS_GOING_AWAY = 1001 -STATUS_PROTOCOL_ERROR = 1002 -STATUS_UNSUPPORTED_DATA = 1003 -STATUS_NO_STATUS_RECEIVED = 1005 -STATUS_ABNORMAL_CLOSURE = 1006 -STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007 -STATUS_POLICY_VIOLATION = 1008 -STATUS_MESSAGE_TOO_BIG = 1009 -STATUS_MANDATORY_EXTENSION = 1010 -STATUS_INTERNAL_ENDPOINT_ERROR = 1011 -STATUS_TLS_HANDSHAKE = 1015 -STATUS_USER_REGISTERED_BASE = 3000 -STATUS_USER_REGISTERED_MAX = 3999 -STATUS_USER_PRIVATE_BASE = 4000 -STATUS_USER_PRIVATE_MAX = 4999 -# Following definitions are aliases to keep compatibility. Applications must -# not use these obsoleted definitions anymore. -STATUS_NORMAL = STATUS_NORMAL_CLOSURE -STATUS_UNSUPPORTED = STATUS_UNSUPPORTED_DATA -STATUS_CODE_NOT_AVAILABLE = STATUS_NO_STATUS_RECEIVED -STATUS_ABNORMAL_CLOSE = STATUS_ABNORMAL_CLOSURE -STATUS_INVALID_FRAME_PAYLOAD = STATUS_INVALID_FRAME_PAYLOAD_DATA -STATUS_MANDATORY_EXT = STATUS_MANDATORY_EXTENSION - -# HTTP status codes -HTTP_STATUS_BAD_REQUEST = 400 -HTTP_STATUS_FORBIDDEN = 403 -HTTP_STATUS_NOT_FOUND = 404 - - -def is_control_opcode(opcode): -    return (opcode >> 3) == 1 - - -class ExtensionParameter(object): -    """Holds information about an extension which is exchanged on extension -    negotiation in opening handshake. -    """ - -    def __init__(self, name): -        self._name = name -        # TODO(tyoshino): Change the data structure to more efficient one such -        # as dict when the spec changes to say like -        # - Parameter names must be unique -        # - The order of parameters is not significant -        self._parameters = [] - -    def name(self): -        return self._name - -    def add_parameter(self, name, value): -        self._parameters.append((name, value)) - -    def get_parameters(self): -        return self._parameters - -    def get_parameter_names(self): -        return [name for name, unused_value in self._parameters] - -    def has_parameter(self, name): -        for param_name, param_value in self._parameters: -            if param_name == name: -                return True -        return False - -    def get_parameter_value(self, name): -        for param_name, param_value in self._parameters: -            if param_name == name: -                return param_value - - -class ExtensionParsingException(Exception): -    def __init__(self, name): -        super(ExtensionParsingException, self).__init__(name) - - -def _parse_extension_param(state, definition, allow_quoted_string): -    param_name = http_header_util.consume_token(state) - -    if param_name is None: -        raise ExtensionParsingException('No valid parameter name found') - -    http_header_util.consume_lwses(state) - -    if not http_header_util.consume_string(state, '='): -        definition.add_parameter(param_name, None) -        return - -    http_header_util.consume_lwses(state) - -    if allow_quoted_string: -        # TODO(toyoshim): Add code to validate that parsed param_value is token -        param_value = http_header_util.consume_token_or_quoted_string(state) -    else: -        param_value = http_header_util.consume_token(state) -    if param_value is None: -        raise ExtensionParsingException( -            'No valid parameter value found on the right-hand side of ' -            'parameter %r' % param_name) - -    definition.add_parameter(param_name, param_value) - - -def _parse_extension(state, allow_quoted_string): -    extension_token = http_header_util.consume_token(state) -    if extension_token is None: -        return None - -    extension = ExtensionParameter(extension_token) - -    while True: -        http_header_util.consume_lwses(state) - -        if not http_header_util.consume_string(state, ';'): -            break - -        http_header_util.consume_lwses(state) - -        try: -            _parse_extension_param(state, extension, allow_quoted_string) -        except ExtensionParsingException, e: -            raise ExtensionParsingException( -                'Failed to parse parameter for %r (%r)' % -                (extension_token, e)) - -    return extension - - -def parse_extensions(data, allow_quoted_string=False): -    """Parses Sec-WebSocket-Extensions header value returns a list of -    ExtensionParameter objects. - -    Leading LWSes must be trimmed. -    """ - -    state = http_header_util.ParsingState(data) - -    extension_list = [] -    while True: -        extension = _parse_extension(state, allow_quoted_string) -        if extension is not None: -            extension_list.append(extension) - -        http_header_util.consume_lwses(state) - -        if http_header_util.peek(state) is None: -            break - -        if not http_header_util.consume_string(state, ','): -            raise ExtensionParsingException( -                'Failed to parse Sec-WebSocket-Extensions header: ' -                'Expected a comma but found %r' % -                http_header_util.peek(state)) - -        http_header_util.consume_lwses(state) - -    if len(extension_list) == 0: -        raise ExtensionParsingException( -            'No valid extension entry found') - -    return extension_list - - -def format_extension(extension): -    """Formats an ExtensionParameter object.""" - -    formatted_params = [extension.name()] -    for param_name, param_value in extension.get_parameters(): -        if param_value is None: -            formatted_params.append(param_name) -        else: -            quoted_value = http_header_util.quote_if_necessary(param_value) -            formatted_params.append('%s=%s' % (param_name, quoted_value)) -    return '; '.join(formatted_params) - - -def format_extensions(extension_list): -    """Formats a list of ExtensionParameter objects.""" - -    formatted_extension_list = [] -    for extension in extension_list: -        formatted_extension_list.append(format_extension(extension)) -    return ', '.join(formatted_extension_list) - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/dispatch.py b/module/lib/mod_pywebsocket/dispatch.py deleted file mode 100644 index 25905f180..000000000 --- a/module/lib/mod_pywebsocket/dispatch.py +++ /dev/null @@ -1,387 +0,0 @@ -# Copyright 2012, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""Dispatch WebSocket request. -""" - - -import logging -import os -import re - -from mod_pywebsocket import common -from mod_pywebsocket import handshake -from mod_pywebsocket import msgutil -from mod_pywebsocket import mux -from mod_pywebsocket import stream -from mod_pywebsocket import util - - -_SOURCE_PATH_PATTERN = re.compile(r'(?i)_wsh\.py$') -_SOURCE_SUFFIX = '_wsh.py' -_DO_EXTRA_HANDSHAKE_HANDLER_NAME = 'web_socket_do_extra_handshake' -_TRANSFER_DATA_HANDLER_NAME = 'web_socket_transfer_data' -_PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME = ( -    'web_socket_passive_closing_handshake') - - -class DispatchException(Exception): -    """Exception in dispatching WebSocket request.""" - -    def __init__(self, name, status=common.HTTP_STATUS_NOT_FOUND): -        super(DispatchException, self).__init__(name) -        self.status = status - - -def _default_passive_closing_handshake_handler(request): -    """Default web_socket_passive_closing_handshake handler.""" - -    return common.STATUS_NORMAL_CLOSURE, '' - - -def _normalize_path(path): -    """Normalize path. - -    Args: -        path: the path to normalize. - -    Path is converted to the absolute path. -    The input path can use either '\\' or '/' as the separator. -    The normalized path always uses '/' regardless of the platform. -    """ - -    path = path.replace('\\', os.path.sep) -    path = os.path.realpath(path) -    path = path.replace('\\', '/') -    return path - - -def _create_path_to_resource_converter(base_dir): -    """Returns a function that converts the path of a WebSocket handler source -    file to a resource string by removing the path to the base directory from -    its head, removing _SOURCE_SUFFIX from its tail, and replacing path -    separators in it with '/'. - -    Args: -        base_dir: the path to the base directory. -    """ - -    base_dir = _normalize_path(base_dir) - -    base_len = len(base_dir) -    suffix_len = len(_SOURCE_SUFFIX) - -    def converter(path): -        if not path.endswith(_SOURCE_SUFFIX): -            return None -        # _normalize_path must not be used because resolving symlink breaks -        # following path check. -        path = path.replace('\\', '/') -        if not path.startswith(base_dir): -            return None -        return path[base_len:-suffix_len] - -    return converter - - -def _enumerate_handler_file_paths(directory): -    """Returns a generator that enumerates WebSocket Handler source file names -    in the given directory. -    """ - -    for root, unused_dirs, files in os.walk(directory): -        for base in files: -            path = os.path.join(root, base) -            if _SOURCE_PATH_PATTERN.search(path): -                yield path - - -class _HandlerSuite(object): -    """A handler suite holder class.""" - -    def __init__(self, do_extra_handshake, transfer_data, -                 passive_closing_handshake): -        self.do_extra_handshake = do_extra_handshake -        self.transfer_data = transfer_data -        self.passive_closing_handshake = passive_closing_handshake - - -def _source_handler_file(handler_definition): -    """Source a handler definition string. - -    Args: -        handler_definition: a string containing Python statements that define -                            handler functions. -    """ - -    global_dic = {} -    try: -        exec handler_definition in global_dic -    except Exception: -        raise DispatchException('Error in sourcing handler:' + -                                util.get_stack_trace()) -    passive_closing_handshake_handler = None -    try: -        passive_closing_handshake_handler = _extract_handler( -            global_dic, _PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME) -    except Exception: -        passive_closing_handshake_handler = ( -            _default_passive_closing_handshake_handler) -    return _HandlerSuite( -        _extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME), -        _extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME), -        passive_closing_handshake_handler) - - -def _extract_handler(dic, name): -    """Extracts a callable with the specified name from the given dictionary -    dic. -    """ - -    if name not in dic: -        raise DispatchException('%s is not defined.' % name) -    handler = dic[name] -    if not callable(handler): -        raise DispatchException('%s is not callable.' % name) -    return handler - - -class Dispatcher(object): -    """Dispatches WebSocket requests. - -    This class maintains a map from resource name to handlers. -    """ - -    def __init__( -        self, root_dir, scan_dir=None, -        allow_handlers_outside_root_dir=True): -        """Construct an instance. - -        Args: -            root_dir: The directory where handler definition files are -                      placed. -            scan_dir: The directory where handler definition files are -                      searched. scan_dir must be a directory under root_dir, -                      including root_dir itself.  If scan_dir is None, -                      root_dir is used as scan_dir. scan_dir can be useful -                      in saving scan time when root_dir contains many -                      subdirectories. -            allow_handlers_outside_root_dir: Scans handler files even if their -                      canonical path is not under root_dir. -        """ - -        self._logger = util.get_class_logger(self) - -        self._handler_suite_map = {} -        self._source_warnings = [] -        if scan_dir is None: -            scan_dir = root_dir -        if not os.path.realpath(scan_dir).startswith( -                os.path.realpath(root_dir)): -            raise DispatchException('scan_dir:%s must be a directory under ' -                                    'root_dir:%s.' % (scan_dir, root_dir)) -        self._source_handler_files_in_dir( -            root_dir, scan_dir, allow_handlers_outside_root_dir) - -    def add_resource_path_alias(self, -                                alias_resource_path, existing_resource_path): -        """Add resource path alias. - -        Once added, request to alias_resource_path would be handled by -        handler registered for existing_resource_path. - -        Args: -            alias_resource_path: alias resource path -            existing_resource_path: existing resource path -        """ -        try: -            handler_suite = self._handler_suite_map[existing_resource_path] -            self._handler_suite_map[alias_resource_path] = handler_suite -        except KeyError: -            raise DispatchException('No handler for: %r' % -                                    existing_resource_path) - -    def source_warnings(self): -        """Return warnings in sourcing handlers.""" - -        return self._source_warnings - -    def do_extra_handshake(self, request): -        """Do extra checking in WebSocket handshake. - -        Select a handler based on request.uri and call its -        web_socket_do_extra_handshake function. - -        Args: -            request: mod_python request. - -        Raises: -            DispatchException: when handler was not found -            AbortedByUserException: when user handler abort connection -            HandshakeException: when opening handshake failed -        """ - -        handler_suite = self.get_handler_suite(request.ws_resource) -        if handler_suite is None: -            raise DispatchException('No handler for: %r' % request.ws_resource) -        do_extra_handshake_ = handler_suite.do_extra_handshake -        try: -            do_extra_handshake_(request) -        except handshake.AbortedByUserException, e: -            raise -        except Exception, e: -            util.prepend_message_to_exception( -                    '%s raised exception for %s: ' % ( -                            _DO_EXTRA_HANDSHAKE_HANDLER_NAME, -                            request.ws_resource), -                    e) -            raise handshake.HandshakeException(e, common.HTTP_STATUS_FORBIDDEN) - -    def transfer_data(self, request): -        """Let a handler transfer_data with a WebSocket client. - -        Select a handler based on request.ws_resource and call its -        web_socket_transfer_data function. - -        Args: -            request: mod_python request. - -        Raises: -            DispatchException: when handler was not found -            AbortedByUserException: when user handler abort connection -        """ - -        # TODO(tyoshino): Terminate underlying TCP connection if possible. -        try: -            if mux.use_mux(request): -                mux.start(request, self) -            else: -                handler_suite = self.get_handler_suite(request.ws_resource) -                if handler_suite is None: -                    raise DispatchException('No handler for: %r' % -                                            request.ws_resource) -                transfer_data_ = handler_suite.transfer_data -                transfer_data_(request) - -            if not request.server_terminated: -                request.ws_stream.close_connection() -        # Catch non-critical exceptions the handler didn't handle. -        except handshake.AbortedByUserException, e: -            self._logger.debug('%s', e) -            raise -        except msgutil.BadOperationException, e: -            self._logger.debug('%s', e) -            request.ws_stream.close_connection(common.STATUS_ABNORMAL_CLOSURE) -        except msgutil.InvalidFrameException, e: -            # InvalidFrameException must be caught before -            # ConnectionTerminatedException that catches InvalidFrameException. -            self._logger.debug('%s', e) -            request.ws_stream.close_connection(common.STATUS_PROTOCOL_ERROR) -        except msgutil.UnsupportedFrameException, e: -            self._logger.debug('%s', e) -            request.ws_stream.close_connection(common.STATUS_UNSUPPORTED_DATA) -        except stream.InvalidUTF8Exception, e: -            self._logger.debug('%s', e) -            request.ws_stream.close_connection( -                common.STATUS_INVALID_FRAME_PAYLOAD_DATA) -        except msgutil.ConnectionTerminatedException, e: -            self._logger.debug('%s', e) -        except Exception, e: -            util.prepend_message_to_exception( -                '%s raised exception for %s: ' % ( -                    _TRANSFER_DATA_HANDLER_NAME, request.ws_resource), -                e) -            raise - -    def passive_closing_handshake(self, request): -        """Prepare code and reason for responding client initiated closing -        handshake. -        """ - -        handler_suite = self.get_handler_suite(request.ws_resource) -        if handler_suite is None: -            return _default_passive_closing_handshake_handler(request) -        return handler_suite.passive_closing_handshake(request) - -    def get_handler_suite(self, resource): -        """Retrieves two handlers (one for extra handshake processing, and one -        for data transfer) for the given request as a HandlerSuite object. -        """ - -        fragment = None -        if '#' in resource: -            resource, fragment = resource.split('#', 1) -        if '?' in resource: -            resource = resource.split('?', 1)[0] -        handler_suite = self._handler_suite_map.get(resource) -        if handler_suite and fragment: -            raise DispatchException('Fragment identifiers MUST NOT be used on ' -                                    'WebSocket URIs', -                                    common.HTTP_STATUS_BAD_REQUEST) -        return handler_suite - -    def _source_handler_files_in_dir( -        self, root_dir, scan_dir, allow_handlers_outside_root_dir): -        """Source all the handler source files in the scan_dir directory. - -        The resource path is determined relative to root_dir. -        """ - -        # We build a map from resource to handler code assuming that there's -        # only one path from root_dir to scan_dir and it can be obtained by -        # comparing realpath of them. - -        # Here we cannot use abspath. See -        # https://bugs.webkit.org/show_bug.cgi?id=31603 - -        convert = _create_path_to_resource_converter(root_dir) -        scan_realpath = os.path.realpath(scan_dir) -        root_realpath = os.path.realpath(root_dir) -        for path in _enumerate_handler_file_paths(scan_realpath): -            if (not allow_handlers_outside_root_dir and -                (not os.path.realpath(path).startswith(root_realpath))): -                self._logger.debug( -                    'Canonical path of %s is not under root directory' % -                    path) -                continue -            try: -                handler_suite = _source_handler_file(open(path).read()) -            except DispatchException, e: -                self._source_warnings.append('%s: %s' % (path, e)) -                continue -            resource = convert(path) -            if resource is None: -                self._logger.debug( -                    'Path to resource conversion on %s failed' % path) -            else: -                self._handler_suite_map[convert(path)] = handler_suite - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/extensions.py b/module/lib/mod_pywebsocket/extensions.py deleted file mode 100644 index 03dbf9ee1..000000000 --- a/module/lib/mod_pywebsocket/extensions.py +++ /dev/null @@ -1,727 +0,0 @@ -# Copyright 2012, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -from mod_pywebsocket import common -from mod_pywebsocket import util -from mod_pywebsocket.http_header_util import quote_if_necessary - - -_available_processors = {} - - -class ExtensionProcessorInterface(object): - -    def name(self): -        return None - -    def get_extension_response(self): -        return None - -    def setup_stream_options(self, stream_options): -        pass - - -class DeflateStreamExtensionProcessor(ExtensionProcessorInterface): -    """WebSocket DEFLATE stream extension processor. - -    Specification: -    Section 9.2.1 in -    http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 -    """ - -    def __init__(self, request): -        self._logger = util.get_class_logger(self) - -        self._request = request - -    def name(self): -        return common.DEFLATE_STREAM_EXTENSION - -    def get_extension_response(self): -        if len(self._request.get_parameter_names()) != 0: -            return None - -        self._logger.debug( -            'Enable %s extension', common.DEFLATE_STREAM_EXTENSION) - -        return common.ExtensionParameter(common.DEFLATE_STREAM_EXTENSION) - -    def setup_stream_options(self, stream_options): -        stream_options.deflate_stream = True - - -_available_processors[common.DEFLATE_STREAM_EXTENSION] = ( -    DeflateStreamExtensionProcessor) - - -def _log_compression_ratio(logger, original_bytes, total_original_bytes, -                           filtered_bytes, total_filtered_bytes): -    # Print inf when ratio is not available. -    ratio = float('inf') -    average_ratio = float('inf') -    if original_bytes != 0: -        ratio = float(filtered_bytes) / original_bytes -    if total_original_bytes != 0: -        average_ratio = ( -            float(total_filtered_bytes) / total_original_bytes) -    logger.debug('Outgoing compress ratio: %f (average: %f)' % -        (ratio, average_ratio)) - - -def _log_decompression_ratio(logger, received_bytes, total_received_bytes, -                             filtered_bytes, total_filtered_bytes): -    # Print inf when ratio is not available. -    ratio = float('inf') -    average_ratio = float('inf') -    if received_bytes != 0: -        ratio = float(received_bytes) / filtered_bytes -    if total_filtered_bytes != 0: -        average_ratio = ( -            float(total_received_bytes) / total_filtered_bytes) -    logger.debug('Incoming compress ratio: %f (average: %f)' % -        (ratio, average_ratio)) - - -class DeflateFrameExtensionProcessor(ExtensionProcessorInterface): -    """WebSocket Per-frame DEFLATE extension processor. - -    Specification: -    http://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate -    """ - -    _WINDOW_BITS_PARAM = 'max_window_bits' -    _NO_CONTEXT_TAKEOVER_PARAM = 'no_context_takeover' - -    def __init__(self, request): -        self._logger = util.get_class_logger(self) - -        self._request = request - -        self._response_window_bits = None -        self._response_no_context_takeover = False -        self._bfinal = False - -        # Counters for statistics. - -        # Total number of outgoing bytes supplied to this filter. -        self._total_outgoing_payload_bytes = 0 -        # Total number of bytes sent to the network after applying this filter. -        self._total_filtered_outgoing_payload_bytes = 0 - -        # Total number of bytes received from the network. -        self._total_incoming_payload_bytes = 0 -        # Total number of incoming bytes obtained after applying this filter. -        self._total_filtered_incoming_payload_bytes = 0 - -    def name(self): -        return common.DEFLATE_FRAME_EXTENSION - -    def get_extension_response(self): -        # Any unknown parameter will be just ignored. - -        window_bits = self._request.get_parameter_value( -            self._WINDOW_BITS_PARAM) -        no_context_takeover = self._request.has_parameter( -            self._NO_CONTEXT_TAKEOVER_PARAM) -        if (no_context_takeover and -            self._request.get_parameter_value( -                self._NO_CONTEXT_TAKEOVER_PARAM) is not None): -            return None - -        if window_bits is not None: -            try: -                window_bits = int(window_bits) -            except ValueError, e: -                return None -            if window_bits < 8 or window_bits > 15: -                return None - -        self._deflater = util._RFC1979Deflater( -            window_bits, no_context_takeover) - -        self._inflater = util._RFC1979Inflater() - -        self._compress_outgoing = True - -        response = common.ExtensionParameter(self._request.name()) - -        if self._response_window_bits is not None: -            response.add_parameter( -                self._WINDOW_BITS_PARAM, str(self._response_window_bits)) -        if self._response_no_context_takeover: -            response.add_parameter( -                self._NO_CONTEXT_TAKEOVER_PARAM, None) - -        self._logger.debug( -            'Enable %s extension (' -            'request: window_bits=%s; no_context_takeover=%r, ' -            'response: window_wbits=%s; no_context_takeover=%r)' % -            (self._request.name(), -             window_bits, -             no_context_takeover, -             self._response_window_bits, -             self._response_no_context_takeover)) - -        return response - -    def setup_stream_options(self, stream_options): - -        class _OutgoingFilter(object): - -            def __init__(self, parent): -                self._parent = parent - -            def filter(self, frame): -                self._parent._outgoing_filter(frame) - -        class _IncomingFilter(object): - -            def __init__(self, parent): -                self._parent = parent - -            def filter(self, frame): -                self._parent._incoming_filter(frame) - -        stream_options.outgoing_frame_filters.append( -            _OutgoingFilter(self)) -        stream_options.incoming_frame_filters.insert( -            0, _IncomingFilter(self)) - -    def set_response_window_bits(self, value): -        self._response_window_bits = value - -    def set_response_no_context_takeover(self, value): -        self._response_no_context_takeover = value - -    def set_bfinal(self, value): -        self._bfinal = value - -    def enable_outgoing_compression(self): -        self._compress_outgoing = True - -    def disable_outgoing_compression(self): -        self._compress_outgoing = False - -    def _outgoing_filter(self, frame): -        """Transform outgoing frames. This method is called only by -        an _OutgoingFilter instance. -        """ - -        original_payload_size = len(frame.payload) -        self._total_outgoing_payload_bytes += original_payload_size - -        if (not self._compress_outgoing or -            common.is_control_opcode(frame.opcode)): -            self._total_filtered_outgoing_payload_bytes += ( -                original_payload_size) -            return - -        frame.payload = self._deflater.filter( -            frame.payload, bfinal=self._bfinal) -        frame.rsv1 = 1 - -        filtered_payload_size = len(frame.payload) -        self._total_filtered_outgoing_payload_bytes += filtered_payload_size - -        _log_compression_ratio(self._logger, original_payload_size, -                               self._total_outgoing_payload_bytes, -                               filtered_payload_size, -                               self._total_filtered_outgoing_payload_bytes) - -    def _incoming_filter(self, frame): -        """Transform incoming frames. This method is called only by -        an _IncomingFilter instance. -        """ - -        received_payload_size = len(frame.payload) -        self._total_incoming_payload_bytes += received_payload_size - -        if frame.rsv1 != 1 or common.is_control_opcode(frame.opcode): -            self._total_filtered_incoming_payload_bytes += ( -                received_payload_size) -            return - -        frame.payload = self._inflater.filter(frame.payload) -        frame.rsv1 = 0 - -        filtered_payload_size = len(frame.payload) -        self._total_filtered_incoming_payload_bytes += filtered_payload_size - -        _log_decompression_ratio(self._logger, received_payload_size, -                                 self._total_incoming_payload_bytes, -                                 filtered_payload_size, -                                 self._total_filtered_incoming_payload_bytes) - - -_available_processors[common.DEFLATE_FRAME_EXTENSION] = ( -    DeflateFrameExtensionProcessor) - - -# Adding vendor-prefixed deflate-frame extension. -# TODO(bashi): Remove this after WebKit stops using vendor prefix. -_available_processors[common.X_WEBKIT_DEFLATE_FRAME_EXTENSION] = ( -    DeflateFrameExtensionProcessor) - - -def _parse_compression_method(data): -    """Parses the value of "method" extension parameter.""" - -    return common.parse_extensions(data, allow_quoted_string=True) - - -def _create_accepted_method_desc(method_name, method_params): -    """Creates accepted-method-desc from given method name and parameters""" - -    extension = common.ExtensionParameter(method_name) -    for name, value in method_params: -        extension.add_parameter(name, value) -    return common.format_extension(extension) - - -class CompressionExtensionProcessorBase(ExtensionProcessorInterface): -    """Base class for Per-frame and Per-message compression extension.""" - -    _METHOD_PARAM = 'method' - -    def __init__(self, request): -        self._logger = util.get_class_logger(self) -        self._request = request -        self._compression_method_name = None -        self._compression_processor = None -        self._compression_processor_hook = None - -    def name(self): -        return '' - -    def _lookup_compression_processor(self, method_desc): -        return None - -    def _get_compression_processor_response(self): -        """Looks up the compression processor based on the self._request and -           returns the compression processor's response. -        """ - -        method_list = self._request.get_parameter_value(self._METHOD_PARAM) -        if method_list is None: -            return None -        methods = _parse_compression_method(method_list) -        if methods is None: -            return None -        comression_processor = None -        # The current implementation tries only the first method that matches -        # supported algorithm. Following methods aren't tried even if the -        # first one is rejected. -        # TODO(bashi): Need to clarify this behavior. -        for method_desc in methods: -            compression_processor = self._lookup_compression_processor( -                method_desc) -            if compression_processor is not None: -                self._compression_method_name = method_desc.name() -                break -        if compression_processor is None: -            return None - -        if self._compression_processor_hook: -            self._compression_processor_hook(compression_processor) - -        processor_response = compression_processor.get_extension_response() -        if processor_response is None: -            return None -        self._compression_processor = compression_processor -        return processor_response - -    def get_extension_response(self): -        processor_response = self._get_compression_processor_response() -        if processor_response is None: -            return None - -        response = common.ExtensionParameter(self._request.name()) -        accepted_method_desc = _create_accepted_method_desc( -                                   self._compression_method_name, -                                   processor_response.get_parameters()) -        response.add_parameter(self._METHOD_PARAM, accepted_method_desc) -        self._logger.debug( -            'Enable %s extension (method: %s)' % -            (self._request.name(), self._compression_method_name)) -        return response - -    def setup_stream_options(self, stream_options): -        if self._compression_processor is None: -            return -        self._compression_processor.setup_stream_options(stream_options) - -    def set_compression_processor_hook(self, hook): -        self._compression_processor_hook = hook - -    def get_compression_processor(self): -        return self._compression_processor - - -class PerFrameCompressionExtensionProcessor(CompressionExtensionProcessorBase): -    """WebSocket Per-frame compression extension processor. - -    Specification: -    http://tools.ietf.org/html/draft-ietf-hybi-websocket-perframe-compression -    """ - -    _DEFLATE_METHOD = 'deflate' - -    def __init__(self, request): -        CompressionExtensionProcessorBase.__init__(self, request) - -    def name(self): -        return common.PERFRAME_COMPRESSION_EXTENSION - -    def _lookup_compression_processor(self, method_desc): -        if method_desc.name() == self._DEFLATE_METHOD: -            return DeflateFrameExtensionProcessor(method_desc) -        return None - - -_available_processors[common.PERFRAME_COMPRESSION_EXTENSION] = ( -    PerFrameCompressionExtensionProcessor) - - -class DeflateMessageProcessor(ExtensionProcessorInterface): -    """Per-message deflate processor.""" - -    _S2C_MAX_WINDOW_BITS_PARAM = 's2c_max_window_bits' -    _S2C_NO_CONTEXT_TAKEOVER_PARAM = 's2c_no_context_takeover' -    _C2S_MAX_WINDOW_BITS_PARAM = 'c2s_max_window_bits' -    _C2S_NO_CONTEXT_TAKEOVER_PARAM = 'c2s_no_context_takeover' - -    def __init__(self, request): -        self._request = request -        self._logger = util.get_class_logger(self) - -        self._c2s_max_window_bits = None -        self._c2s_no_context_takeover = False -        self._bfinal = False - -        self._compress_outgoing_enabled = False - -        # True if a message is fragmented and compression is ongoing. -        self._compress_ongoing = False - -        # Counters for statistics. - -        # Total number of outgoing bytes supplied to this filter. -        self._total_outgoing_payload_bytes = 0 -        # Total number of bytes sent to the network after applying this filter. -        self._total_filtered_outgoing_payload_bytes = 0 - -        # Total number of bytes received from the network. -        self._total_incoming_payload_bytes = 0 -        # Total number of incoming bytes obtained after applying this filter. -        self._total_filtered_incoming_payload_bytes = 0 - -    def name(self): -        return 'deflate' - -    def get_extension_response(self): -        # Any unknown parameter will be just ignored. - -        s2c_max_window_bits = self._request.get_parameter_value( -            self._S2C_MAX_WINDOW_BITS_PARAM) -        if s2c_max_window_bits is not None: -            try: -                s2c_max_window_bits = int(s2c_max_window_bits) -            except ValueError, e: -                return None -            if s2c_max_window_bits < 8 or s2c_max_window_bits > 15: -                return None - -        s2c_no_context_takeover = self._request.has_parameter( -            self._S2C_NO_CONTEXT_TAKEOVER_PARAM) -        if (s2c_no_context_takeover and -            self._request.get_parameter_value( -                self._S2C_NO_CONTEXT_TAKEOVER_PARAM) is not None): -            return None - -        self._deflater = util._RFC1979Deflater( -            s2c_max_window_bits, s2c_no_context_takeover) - -        self._inflater = util._RFC1979Inflater() - -        self._compress_outgoing_enabled = True - -        response = common.ExtensionParameter(self._request.name()) - -        if s2c_max_window_bits is not None: -            response.add_parameter( -                self._S2C_MAX_WINDOW_BITS_PARAM, str(s2c_max_window_bits)) - -        if s2c_no_context_takeover: -            response.add_parameter( -                self._S2C_NO_CONTEXT_TAKEOVER_PARAM, None) - -        if self._c2s_max_window_bits is not None: -            response.add_parameter( -                self._C2S_MAX_WINDOW_BITS_PARAM, -                str(self._c2s_max_window_bits)) -        if self._c2s_no_context_takeover: -            response.add_parameter( -                self._C2S_NO_CONTEXT_TAKEOVER_PARAM, None) - -        self._logger.debug( -            'Enable %s extension (' -            'request: s2c_max_window_bits=%s; s2c_no_context_takeover=%r, ' -            'response: c2s_max_window_bits=%s; c2s_no_context_takeover=%r)' % -            (self._request.name(), -             s2c_max_window_bits, -             s2c_no_context_takeover, -             self._c2s_max_window_bits, -             self._c2s_no_context_takeover)) - -        return response - -    def setup_stream_options(self, stream_options): -        class _OutgoingMessageFilter(object): - -            def __init__(self, parent): -                self._parent = parent - -            def filter(self, message, end=True, binary=False): -                return self._parent._process_outgoing_message( -                    message, end, binary) - -        class _IncomingMessageFilter(object): - -            def __init__(self, parent): -                self._parent = parent -                self._decompress_next_message = False - -            def decompress_next_message(self): -                self._decompress_next_message = True - -            def filter(self, message): -                message = self._parent._process_incoming_message( -                    message, self._decompress_next_message) -                self._decompress_next_message = False -                return message - -        self._outgoing_message_filter = _OutgoingMessageFilter(self) -        self._incoming_message_filter = _IncomingMessageFilter(self) -        stream_options.outgoing_message_filters.append( -            self._outgoing_message_filter) -        stream_options.incoming_message_filters.append( -            self._incoming_message_filter) - -        class _OutgoingFrameFilter(object): - -            def __init__(self, parent): -                self._parent = parent -                self._set_compression_bit = False - -            def set_compression_bit(self): -                self._set_compression_bit = True - -            def filter(self, frame): -                self._parent._process_outgoing_frame( -                    frame, self._set_compression_bit) -                self._set_compression_bit = False - -        class _IncomingFrameFilter(object): - -            def __init__(self, parent): -                self._parent = parent - -            def filter(self, frame): -                self._parent._process_incoming_frame(frame) - -        self._outgoing_frame_filter = _OutgoingFrameFilter(self) -        self._incoming_frame_filter = _IncomingFrameFilter(self) -        stream_options.outgoing_frame_filters.append( -            self._outgoing_frame_filter) -        stream_options.incoming_frame_filters.append( -            self._incoming_frame_filter) - -        stream_options.encode_text_message_to_utf8 = False - -    def set_c2s_max_window_bits(self, value): -        self._c2s_max_window_bits = value - -    def set_c2s_no_context_takeover(self, value): -        self._c2s_no_context_takeover = value - -    def set_bfinal(self, value): -        self._bfinal = value - -    def enable_outgoing_compression(self): -        self._compress_outgoing_enabled = True - -    def disable_outgoing_compression(self): -        self._compress_outgoing_enabled = False - -    def _process_incoming_message(self, message, decompress): -        if not decompress: -            return message - -        received_payload_size = len(message) -        self._total_incoming_payload_bytes += received_payload_size - -        message = self._inflater.filter(message) - -        filtered_payload_size = len(message) -        self._total_filtered_incoming_payload_bytes += filtered_payload_size - -        _log_decompression_ratio(self._logger, received_payload_size, -                                 self._total_incoming_payload_bytes, -                                 filtered_payload_size, -                                 self._total_filtered_incoming_payload_bytes) - -        return message - -    def _process_outgoing_message(self, message, end, binary): -        if not binary: -            message = message.encode('utf-8') - -        if not self._compress_outgoing_enabled: -            return message - -        original_payload_size = len(message) -        self._total_outgoing_payload_bytes += original_payload_size - -        message = self._deflater.filter( -            message, flush=end, bfinal=self._bfinal) - -        filtered_payload_size = len(message) -        self._total_filtered_outgoing_payload_bytes += filtered_payload_size - -        _log_compression_ratio(self._logger, original_payload_size, -                               self._total_outgoing_payload_bytes, -                               filtered_payload_size, -                               self._total_filtered_outgoing_payload_bytes) - -        if not self._compress_ongoing: -            self._outgoing_frame_filter.set_compression_bit() -        self._compress_ongoing = not end -        return message - -    def _process_incoming_frame(self, frame): -        if frame.rsv1 == 1 and not common.is_control_opcode(frame.opcode): -            self._incoming_message_filter.decompress_next_message() -            frame.rsv1 = 0 - -    def _process_outgoing_frame(self, frame, compression_bit): -        if (not compression_bit or -            common.is_control_opcode(frame.opcode)): -            return - -        frame.rsv1 = 1 - - -class PerMessageCompressionExtensionProcessor( -    CompressionExtensionProcessorBase): -    """WebSocket Per-message compression extension processor. - -    Specification: -    http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression -    """ - -    _DEFLATE_METHOD = 'deflate' - -    def __init__(self, request): -        CompressionExtensionProcessorBase.__init__(self, request) - -    def name(self): -        return common.PERMESSAGE_COMPRESSION_EXTENSION - -    def _lookup_compression_processor(self, method_desc): -        if method_desc.name() == self._DEFLATE_METHOD: -            return DeflateMessageProcessor(method_desc) -        return None - - -_available_processors[common.PERMESSAGE_COMPRESSION_EXTENSION] = ( -    PerMessageCompressionExtensionProcessor) - - -# Adding vendor-prefixed permessage-compress extension. -# TODO(bashi): Remove this after WebKit stops using vendor prefix. -_available_processors[common.X_WEBKIT_PERMESSAGE_COMPRESSION_EXTENSION] = ( -    PerMessageCompressionExtensionProcessor) - - -class MuxExtensionProcessor(ExtensionProcessorInterface): -    """WebSocket multiplexing extension processor.""" - -    _QUOTA_PARAM = 'quota' - -    def __init__(self, request): -        self._request = request - -    def name(self): -        return common.MUX_EXTENSION - -    def get_extension_response(self, ws_request, -                               logical_channel_extensions): -        # Mux extension cannot be used after extensions that depend on -        # frame boundary, extension data field, or any reserved bits -        # which are attributed to each frame. -        for extension in logical_channel_extensions: -            name = extension.name() -            if (name == common.PERFRAME_COMPRESSION_EXTENSION or -                name == common.DEFLATE_FRAME_EXTENSION or -                name == common.X_WEBKIT_DEFLATE_FRAME_EXTENSION): -                return None - -        quota = self._request.get_parameter_value(self._QUOTA_PARAM) -        if quota is None: -            ws_request.mux_quota = 0 -        else: -            try: -                quota = int(quota) -            except ValueError, e: -                return None -            if quota < 0 or quota >= 2 ** 32: -                return None -            ws_request.mux_quota = quota - -        ws_request.mux = True -        ws_request.mux_extensions = logical_channel_extensions -        return common.ExtensionParameter(common.MUX_EXTENSION) - -    def setup_stream_options(self, stream_options): -        pass - - -_available_processors[common.MUX_EXTENSION] = MuxExtensionProcessor - - -def get_extension_processor(extension_request): -    global _available_processors -    processor_class = _available_processors.get(extension_request.name()) -    if processor_class is None: -        return None -    return processor_class(extension_request) - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/handshake/__init__.py b/module/lib/mod_pywebsocket/handshake/__init__.py deleted file mode 100644 index 194f6b395..000000000 --- a/module/lib/mod_pywebsocket/handshake/__init__.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""WebSocket opening handshake processor. This class try to apply available -opening handshake processors for each protocol version until a connection is -successfully established. -""" - - -import logging - -from mod_pywebsocket import common -from mod_pywebsocket.handshake import hybi00 -from mod_pywebsocket.handshake import hybi -# Export AbortedByUserException, HandshakeException, and VersionException -# symbol from this module. -from mod_pywebsocket.handshake._base import AbortedByUserException -from mod_pywebsocket.handshake._base import HandshakeException -from mod_pywebsocket.handshake._base import VersionException - - -_LOGGER = logging.getLogger(__name__) - - -def do_handshake(request, dispatcher, allowDraft75=False, strict=False): -    """Performs WebSocket handshake. - -    Args: -        request: mod_python request. -        dispatcher: Dispatcher (dispatch.Dispatcher). -        allowDraft75: obsolete argument. ignored. -        strict: obsolete argument. ignored. - -    Handshaker will add attributes such as ws_resource in performing -    handshake. -    """ - -    _LOGGER.debug('Client\'s opening handshake resource: %r', request.uri) -    # To print mimetools.Message as escaped one-line string, we converts -    # headers_in to dict object. Without conversion, if we use %r, it just -    # prints the type and address, and if we use %s, it prints the original -    # header string as multiple lines. -    # -    # Both mimetools.Message and MpTable_Type of mod_python can be -    # converted to dict. -    # -    # mimetools.Message.__str__ returns the original header string. -    # dict(mimetools.Message object) returns the map from header names to -    # header values. While MpTable_Type doesn't have such __str__ but just -    # __repr__ which formats itself as well as dictionary object. -    _LOGGER.debug( -        'Client\'s opening handshake headers: %r', dict(request.headers_in)) - -    handshakers = [] -    handshakers.append( -        ('RFC 6455', hybi.Handshaker(request, dispatcher))) -    handshakers.append( -        ('HyBi 00', hybi00.Handshaker(request, dispatcher))) - -    for name, handshaker in handshakers: -        _LOGGER.debug('Trying protocol version %s', name) -        try: -            handshaker.do_handshake() -            _LOGGER.info('Established (%s protocol)', name) -            return -        except HandshakeException, e: -            _LOGGER.debug( -                'Failed to complete opening handshake as %s protocol: %r', -                name, e) -            if e.status: -                raise e -        except AbortedByUserException, e: -            raise -        except VersionException, e: -            raise - -    # TODO(toyoshim): Add a test to cover the case all handshakers fail. -    raise HandshakeException( -        'Failed to complete opening handshake for all available protocols', -        status=common.HTTP_STATUS_BAD_REQUEST) - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/handshake/_base.py b/module/lib/mod_pywebsocket/handshake/_base.py deleted file mode 100644 index e5c94ca90..000000000 --- a/module/lib/mod_pywebsocket/handshake/_base.py +++ /dev/null @@ -1,226 +0,0 @@ -# Copyright 2012, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""Common functions and exceptions used by WebSocket opening handshake -processors. -""" - - -from mod_pywebsocket import common -from mod_pywebsocket import http_header_util - - -class AbortedByUserException(Exception): -    """Exception for aborting a connection intentionally. - -    If this exception is raised in do_extra_handshake handler, the connection -    will be abandoned. No other WebSocket or HTTP(S) handler will be invoked. - -    If this exception is raised in transfer_data_handler, the connection will -    be closed without closing handshake. No other WebSocket or HTTP(S) handler -    will be invoked. -    """ - -    pass - - -class HandshakeException(Exception): -    """This exception will be raised when an error occurred while processing -    WebSocket initial handshake. -    """ - -    def __init__(self, name, status=None): -        super(HandshakeException, self).__init__(name) -        self.status = status - - -class VersionException(Exception): -    """This exception will be raised when a version of client request does not -    match with version the server supports. -    """ - -    def __init__(self, name, supported_versions=''): -        """Construct an instance. - -        Args: -            supported_version: a str object to show supported hybi versions. -                               (e.g. '8, 13') -        """ -        super(VersionException, self).__init__(name) -        self.supported_versions = supported_versions - - -def get_default_port(is_secure): -    if is_secure: -        return common.DEFAULT_WEB_SOCKET_SECURE_PORT -    else: -        return common.DEFAULT_WEB_SOCKET_PORT - - -def validate_subprotocol(subprotocol, hixie): -    """Validate a value in the Sec-WebSocket-Protocol field. - -    See -    - RFC 6455: Section 4.1., 4.2.2., and 4.3. -    - HyBi 00: Section 4.1. Opening handshake - -    Args: -         hixie: if True, checks if characters in subprotocol are in range -                between U+0020 and U+007E. It's required by HyBi 00 but not by -                RFC 6455. -    """ - -    if not subprotocol: -        raise HandshakeException('Invalid subprotocol name: empty') -    if hixie: -        # Parameter should be in the range U+0020 to U+007E. -        for c in subprotocol: -            if not 0x20 <= ord(c) <= 0x7e: -                raise HandshakeException( -                    'Illegal character in subprotocol name: %r' % c) -    else: -        # Parameter should be encoded HTTP token. -        state = http_header_util.ParsingState(subprotocol) -        token = http_header_util.consume_token(state) -        rest = http_header_util.peek(state) -        # If |rest| is not None, |subprotocol| is not one token or invalid. If -        # |rest| is None, |token| must not be None because |subprotocol| is -        # concatenation of |token| and |rest| and is not None. -        if rest is not None: -            raise HandshakeException('Invalid non-token string in subprotocol ' -                                     'name: %r' % rest) - - -def parse_host_header(request): -    fields = request.headers_in['Host'].split(':', 1) -    if len(fields) == 1: -        return fields[0], get_default_port(request.is_https()) -    try: -        return fields[0], int(fields[1]) -    except ValueError, e: -        raise HandshakeException('Invalid port number format: %r' % e) - - -def format_header(name, value): -    return '%s: %s\r\n' % (name, value) - - -def build_location(request): -    """Build WebSocket location for request.""" -    location_parts = [] -    if request.is_https(): -        location_parts.append(common.WEB_SOCKET_SECURE_SCHEME) -    else: -        location_parts.append(common.WEB_SOCKET_SCHEME) -    location_parts.append('://') -    host, port = parse_host_header(request) -    connection_port = request.connection.local_addr[1] -    if port != connection_port: -        raise HandshakeException('Header/connection port mismatch: %d/%d' % -                                 (port, connection_port)) -    location_parts.append(host) -    if (port != get_default_port(request.is_https())): -        location_parts.append(':') -        location_parts.append(str(port)) -    location_parts.append(request.uri) -    return ''.join(location_parts) - - -def get_mandatory_header(request, key): -    value = request.headers_in.get(key) -    if value is None: -        raise HandshakeException('Header %s is not defined' % key) -    return value - - -def validate_mandatory_header(request, key, expected_value, fail_status=None): -    value = get_mandatory_header(request, key) - -    if value.lower() != expected_value.lower(): -        raise HandshakeException( -            'Expected %r for header %s but found %r (case-insensitive)' % -            (expected_value, key, value), status=fail_status) - - -def check_request_line(request): -    # 5.1 1. The three character UTF-8 string "GET". -    # 5.1 2. A UTF-8-encoded U+0020 SPACE character (0x20 byte). -    if request.method != 'GET': -        raise HandshakeException('Method is not GET: %r' % request.method) - -    if request.protocol != 'HTTP/1.1': -        raise HandshakeException('Version is not HTTP/1.1: %r' % -                                 request.protocol) - - -def check_header_lines(request, mandatory_headers): -    check_request_line(request) - -    # The expected field names, and the meaning of their corresponding -    # values, are as follows. -    #  |Upgrade| and |Connection| -    for key, expected_value in mandatory_headers: -        validate_mandatory_header(request, key, expected_value) - - -def parse_token_list(data): -    """Parses a header value which follows 1#token and returns parsed elements -    as a list of strings. - -    Leading LWSes must be trimmed. -    """ - -    state = http_header_util.ParsingState(data) - -    token_list = [] - -    while True: -        token = http_header_util.consume_token(state) -        if token is not None: -            token_list.append(token) - -        http_header_util.consume_lwses(state) - -        if http_header_util.peek(state) is None: -            break - -        if not http_header_util.consume_string(state, ','): -            raise HandshakeException( -                'Expected a comma but found %r' % http_header_util.peek(state)) - -        http_header_util.consume_lwses(state) - -    if len(token_list) == 0: -        raise HandshakeException('No valid token found') - -    return token_list - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/handshake/hybi.py b/module/lib/mod_pywebsocket/handshake/hybi.py deleted file mode 100644 index fc0e2a096..000000000 --- a/module/lib/mod_pywebsocket/handshake/hybi.py +++ /dev/null @@ -1,404 +0,0 @@ -# Copyright 2012, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""This file provides the opening handshake processor for the WebSocket -protocol (RFC 6455). - -Specification: -http://tools.ietf.org/html/rfc6455 -""" - - -# Note: request.connection.write is used in this module, even though mod_python -# document says that it should be used only in connection handlers. -# Unfortunately, we have no other options. For example, request.write is not -# suitable because it doesn't allow direct raw bytes writing. - - -import base64 -import logging -import os -import re - -from mod_pywebsocket import common -from mod_pywebsocket.extensions import get_extension_processor -from mod_pywebsocket.handshake._base import check_request_line -from mod_pywebsocket.handshake._base import format_header -from mod_pywebsocket.handshake._base import get_mandatory_header -from mod_pywebsocket.handshake._base import HandshakeException -from mod_pywebsocket.handshake._base import parse_token_list -from mod_pywebsocket.handshake._base import validate_mandatory_header -from mod_pywebsocket.handshake._base import validate_subprotocol -from mod_pywebsocket.handshake._base import VersionException -from mod_pywebsocket.stream import Stream -from mod_pywebsocket.stream import StreamOptions -from mod_pywebsocket import util - - -# Used to validate the value in the Sec-WebSocket-Key header strictly. RFC 4648 -# disallows non-zero padding, so the character right before == must be any of -# A, Q, g and w. -_SEC_WEBSOCKET_KEY_REGEX = re.compile('^[+/0-9A-Za-z]{21}[AQgw]==$') - -# Defining aliases for values used frequently. -_VERSION_HYBI08 = common.VERSION_HYBI08 -_VERSION_HYBI08_STRING = str(_VERSION_HYBI08) -_VERSION_LATEST = common.VERSION_HYBI_LATEST -_VERSION_LATEST_STRING = str(_VERSION_LATEST) -_SUPPORTED_VERSIONS = [ -    _VERSION_LATEST, -    _VERSION_HYBI08, -] - - -def compute_accept(key): -    """Computes value for the Sec-WebSocket-Accept header from value of the -    Sec-WebSocket-Key header. -    """ - -    accept_binary = util.sha1_hash( -        key + common.WEBSOCKET_ACCEPT_UUID).digest() -    accept = base64.b64encode(accept_binary) - -    return (accept, accept_binary) - - -class Handshaker(object): -    """Opening handshake processor for the WebSocket protocol (RFC 6455).""" - -    def __init__(self, request, dispatcher): -        """Construct an instance. - -        Args: -            request: mod_python request. -            dispatcher: Dispatcher (dispatch.Dispatcher). - -        Handshaker will add attributes such as ws_resource during handshake. -        """ - -        self._logger = util.get_class_logger(self) - -        self._request = request -        self._dispatcher = dispatcher - -    def _validate_connection_header(self): -        connection = get_mandatory_header( -            self._request, common.CONNECTION_HEADER) - -        try: -            connection_tokens = parse_token_list(connection) -        except HandshakeException, e: -            raise HandshakeException( -                'Failed to parse %s: %s' % (common.CONNECTION_HEADER, e)) - -        connection_is_valid = False -        for token in connection_tokens: -            if token.lower() == common.UPGRADE_CONNECTION_TYPE.lower(): -                connection_is_valid = True -                break -        if not connection_is_valid: -            raise HandshakeException( -                '%s header doesn\'t contain "%s"' % -                (common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE)) - -    def do_handshake(self): -        self._request.ws_close_code = None -        self._request.ws_close_reason = None - -        # Parsing. - -        check_request_line(self._request) - -        validate_mandatory_header( -            self._request, -            common.UPGRADE_HEADER, -            common.WEBSOCKET_UPGRADE_TYPE) - -        self._validate_connection_header() - -        self._request.ws_resource = self._request.uri - -        unused_host = get_mandatory_header(self._request, common.HOST_HEADER) - -        self._request.ws_version = self._check_version() - -        # This handshake must be based on latest hybi. We are responsible to -        # fallback to HTTP on handshake failure as latest hybi handshake -        # specifies. -        try: -            self._get_origin() -            self._set_protocol() -            self._parse_extensions() - -            # Key validation, response generation. - -            key = self._get_key() -            (accept, accept_binary) = compute_accept(key) -            self._logger.debug( -                '%s: %r (%s)', -                common.SEC_WEBSOCKET_ACCEPT_HEADER, -                accept, -                util.hexify(accept_binary)) - -            self._logger.debug('Protocol version is RFC 6455') - -            # Setup extension processors. - -            processors = [] -            if self._request.ws_requested_extensions is not None: -                for extension_request in self._request.ws_requested_extensions: -                    processor = get_extension_processor(extension_request) -                    # Unknown extension requests are just ignored. -                    if processor is not None: -                        processors.append(processor) -            self._request.ws_extension_processors = processors - -            # Extra handshake handler may modify/remove processors. -            self._dispatcher.do_extra_handshake(self._request) -            processors = filter(lambda processor: processor is not None, -                                self._request.ws_extension_processors) - -            accepted_extensions = [] - -            # We need to take care of mux extension here. Extensions that -            # are placed before mux should be applied to logical channels. -            mux_index = -1 -            for i, processor in enumerate(processors): -                if processor.name() == common.MUX_EXTENSION: -                    mux_index = i -                    break -            if mux_index >= 0: -                mux_processor = processors[mux_index] -                logical_channel_processors = processors[:mux_index] -                processors = processors[mux_index+1:] - -                for processor in logical_channel_processors: -                    extension_response = processor.get_extension_response() -                    if extension_response is None: -                        # Rejected. -                        continue -                    accepted_extensions.append(extension_response) -                # Pass a shallow copy of accepted_extensions as extensions for -                # logical channels. -                mux_response = mux_processor.get_extension_response( -                    self._request, accepted_extensions[:]) -                if mux_response is not None: -                    accepted_extensions.append(mux_response) - -            stream_options = StreamOptions() - -            # When there is mux extension, here, |processors| contain only -            # prosessors for extensions placed after mux. -            for processor in processors: - -                extension_response = processor.get_extension_response() -                if extension_response is None: -                    # Rejected. -                    continue - -                accepted_extensions.append(extension_response) - -                processor.setup_stream_options(stream_options) - -            if len(accepted_extensions) > 0: -                self._request.ws_extensions = accepted_extensions -                self._logger.debug( -                    'Extensions accepted: %r', -                    map(common.ExtensionParameter.name, accepted_extensions)) -            else: -                self._request.ws_extensions = None - -            self._request.ws_stream = self._create_stream(stream_options) - -            if self._request.ws_requested_protocols is not None: -                if self._request.ws_protocol is None: -                    raise HandshakeException( -                        'do_extra_handshake must choose one subprotocol from ' -                        'ws_requested_protocols and set it to ws_protocol') -                validate_subprotocol(self._request.ws_protocol, hixie=False) - -                self._logger.debug( -                    'Subprotocol accepted: %r', -                    self._request.ws_protocol) -            else: -                if self._request.ws_protocol is not None: -                    raise HandshakeException( -                        'ws_protocol must be None when the client didn\'t ' -                        'request any subprotocol') - -            self._send_handshake(accept) -        except HandshakeException, e: -            if not e.status: -                # Fallback to 400 bad request by default. -                e.status = common.HTTP_STATUS_BAD_REQUEST -            raise e - -    def _get_origin(self): -        if self._request.ws_version is _VERSION_HYBI08: -            origin_header = common.SEC_WEBSOCKET_ORIGIN_HEADER -        else: -            origin_header = common.ORIGIN_HEADER -        origin = self._request.headers_in.get(origin_header) -        if origin is None: -            self._logger.debug('Client request does not have origin header') -        self._request.ws_origin = origin - -    def _check_version(self): -        version = get_mandatory_header(self._request, -                                       common.SEC_WEBSOCKET_VERSION_HEADER) -        if version == _VERSION_HYBI08_STRING: -            return _VERSION_HYBI08 -        if version == _VERSION_LATEST_STRING: -            return _VERSION_LATEST - -        if version.find(',') >= 0: -            raise HandshakeException( -                'Multiple versions (%r) are not allowed for header %s' % -                (version, common.SEC_WEBSOCKET_VERSION_HEADER), -                status=common.HTTP_STATUS_BAD_REQUEST) -        raise VersionException( -            'Unsupported version %r for header %s' % -            (version, common.SEC_WEBSOCKET_VERSION_HEADER), -            supported_versions=', '.join(map(str, _SUPPORTED_VERSIONS))) - -    def _set_protocol(self): -        self._request.ws_protocol = None - -        protocol_header = self._request.headers_in.get( -            common.SEC_WEBSOCKET_PROTOCOL_HEADER) - -        if protocol_header is None: -            self._request.ws_requested_protocols = None -            return - -        self._request.ws_requested_protocols = parse_token_list( -            protocol_header) -        self._logger.debug('Subprotocols requested: %r', -                           self._request.ws_requested_protocols) - -    def _parse_extensions(self): -        extensions_header = self._request.headers_in.get( -            common.SEC_WEBSOCKET_EXTENSIONS_HEADER) -        if not extensions_header: -            self._request.ws_requested_extensions = None -            return - -        if self._request.ws_version is common.VERSION_HYBI08: -            allow_quoted_string=False -        else: -            allow_quoted_string=True -        try: -            self._request.ws_requested_extensions = common.parse_extensions( -                extensions_header, allow_quoted_string=allow_quoted_string) -        except common.ExtensionParsingException, e: -            raise HandshakeException( -                'Failed to parse Sec-WebSocket-Extensions header: %r' % e) - -        self._logger.debug( -            'Extensions requested: %r', -            map(common.ExtensionParameter.name, -                self._request.ws_requested_extensions)) - -    def _validate_key(self, key): -        if key.find(',') >= 0: -            raise HandshakeException('Request has multiple %s header lines or ' -                                     'contains illegal character \',\': %r' % -                                     (common.SEC_WEBSOCKET_KEY_HEADER, key)) - -        # Validate -        key_is_valid = False -        try: -            # Validate key by quick regex match before parsing by base64 -            # module. Because base64 module skips invalid characters, we have -            # to do this in advance to make this server strictly reject illegal -            # keys. -            if _SEC_WEBSOCKET_KEY_REGEX.match(key): -                decoded_key = base64.b64decode(key) -                if len(decoded_key) == 16: -                    key_is_valid = True -        except TypeError, e: -            pass - -        if not key_is_valid: -            raise HandshakeException( -                'Illegal value for header %s: %r' % -                (common.SEC_WEBSOCKET_KEY_HEADER, key)) - -        return decoded_key - -    def _get_key(self): -        key = get_mandatory_header( -            self._request, common.SEC_WEBSOCKET_KEY_HEADER) - -        decoded_key = self._validate_key(key) - -        self._logger.debug( -            '%s: %r (%s)', -            common.SEC_WEBSOCKET_KEY_HEADER, -            key, -            util.hexify(decoded_key)) - -        return key - -    def _create_stream(self, stream_options): -        return Stream(self._request, stream_options) - -    def _create_handshake_response(self, accept): -        response = [] - -        response.append('HTTP/1.1 101 Switching Protocols\r\n') - -        response.append(format_header( -            common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE)) -        response.append(format_header( -            common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE)) -        response.append(format_header( -            common.SEC_WEBSOCKET_ACCEPT_HEADER, accept)) -        if self._request.ws_protocol is not None: -            response.append(format_header( -                common.SEC_WEBSOCKET_PROTOCOL_HEADER, -                self._request.ws_protocol)) -        if (self._request.ws_extensions is not None and -            len(self._request.ws_extensions) != 0): -            response.append(format_header( -                common.SEC_WEBSOCKET_EXTENSIONS_HEADER, -                common.format_extensions(self._request.ws_extensions))) -        response.append('\r\n') - -        return ''.join(response) - -    def _send_handshake(self, accept): -        raw_response = self._create_handshake_response(accept) -        self._request.connection.write(raw_response) -        self._logger.debug('Sent server\'s opening handshake: %r', -                           raw_response) - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/handshake/hybi00.py b/module/lib/mod_pywebsocket/handshake/hybi00.py deleted file mode 100644 index cc6f8dc43..000000000 --- a/module/lib/mod_pywebsocket/handshake/hybi00.py +++ /dev/null @@ -1,242 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""This file provides the opening handshake processor for the WebSocket -protocol version HyBi 00. - -Specification: -http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 -""" - - -# Note: request.connection.write/read are used in this module, even though -# mod_python document says that they should be used only in connection -# handlers. Unfortunately, we have no other options. For example, -# request.write/read are not suitable because they don't allow direct raw bytes -# writing/reading. - - -import logging -import re -import struct - -from mod_pywebsocket import common -from mod_pywebsocket.stream import StreamHixie75 -from mod_pywebsocket import util -from mod_pywebsocket.handshake._base import HandshakeException -from mod_pywebsocket.handshake._base import build_location -from mod_pywebsocket.handshake._base import check_header_lines -from mod_pywebsocket.handshake._base import format_header -from mod_pywebsocket.handshake._base import get_mandatory_header -from mod_pywebsocket.handshake._base import validate_subprotocol - - -_MANDATORY_HEADERS = [ -    # key, expected value or None -    [common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75], -    [common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE], -] - - -class Handshaker(object): -    """Opening handshake processor for the WebSocket protocol version HyBi 00. -    """ - -    def __init__(self, request, dispatcher): -        """Construct an instance. - -        Args: -            request: mod_python request. -            dispatcher: Dispatcher (dispatch.Dispatcher). - -        Handshaker will add attributes such as ws_resource in performing -        handshake. -        """ - -        self._logger = util.get_class_logger(self) - -        self._request = request -        self._dispatcher = dispatcher - -    def do_handshake(self): -        """Perform WebSocket Handshake. - -        On _request, we set -            ws_resource, ws_protocol, ws_location, ws_origin, ws_challenge, -            ws_challenge_md5: WebSocket handshake information. -            ws_stream: Frame generation/parsing class. -            ws_version: Protocol version. - -        Raises: -            HandshakeException: when any error happened in parsing the opening -                                handshake request. -        """ - -        # 5.1 Reading the client's opening handshake. -        # dispatcher sets it in self._request. -        check_header_lines(self._request, _MANDATORY_HEADERS) -        self._set_resource() -        self._set_subprotocol() -        self._set_location() -        self._set_origin() -        self._set_challenge_response() -        self._set_protocol_version() - -        self._dispatcher.do_extra_handshake(self._request) - -        self._send_handshake() - -    def _set_resource(self): -        self._request.ws_resource = self._request.uri - -    def _set_subprotocol(self): -        # |Sec-WebSocket-Protocol| -        subprotocol = self._request.headers_in.get( -            common.SEC_WEBSOCKET_PROTOCOL_HEADER) -        if subprotocol is not None: -            validate_subprotocol(subprotocol, hixie=True) -        self._request.ws_protocol = subprotocol - -    def _set_location(self): -        # |Host| -        host = self._request.headers_in.get(common.HOST_HEADER) -        if host is not None: -            self._request.ws_location = build_location(self._request) -        # TODO(ukai): check host is this host. - -    def _set_origin(self): -        # |Origin| -        origin = self._request.headers_in.get(common.ORIGIN_HEADER) -        if origin is not None: -            self._request.ws_origin = origin - -    def _set_protocol_version(self): -        # |Sec-WebSocket-Draft| -        draft = self._request.headers_in.get(common.SEC_WEBSOCKET_DRAFT_HEADER) -        if draft is not None and draft != '0': -            raise HandshakeException('Illegal value for %s: %s' % -                                     (common.SEC_WEBSOCKET_DRAFT_HEADER, -                                      draft)) - -        self._logger.debug('Protocol version is HyBi 00') -        self._request.ws_version = common.VERSION_HYBI00 -        self._request.ws_stream = StreamHixie75(self._request, True) - -    def _set_challenge_response(self): -        # 5.2 4-8. -        self._request.ws_challenge = self._get_challenge() -        # 5.2 9. let /response/ be the MD5 finterprint of /challenge/ -        self._request.ws_challenge_md5 = util.md5_hash( -            self._request.ws_challenge).digest() -        self._logger.debug( -            'Challenge: %r (%s)', -            self._request.ws_challenge, -            util.hexify(self._request.ws_challenge)) -        self._logger.debug( -            'Challenge response: %r (%s)', -            self._request.ws_challenge_md5, -            util.hexify(self._request.ws_challenge_md5)) - -    def _get_key_value(self, key_field): -        key_value = get_mandatory_header(self._request, key_field) - -        self._logger.debug('%s: %r', key_field, key_value) - -        # 5.2 4. let /key-number_n/ be the digits (characters in the range -        # U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)) in /key_n/, -        # interpreted as a base ten integer, ignoring all other characters -        # in /key_n/. -        try: -            key_number = int(re.sub("\\D", "", key_value)) -        except: -            raise HandshakeException('%s field contains no digit' % key_field) -        # 5.2 5. let /spaces_n/ be the number of U+0020 SPACE characters -        # in /key_n/. -        spaces = re.subn(" ", "", key_value)[1] -        if spaces == 0: -            raise HandshakeException('%s field contains no space' % key_field) - -        self._logger.debug( -            '%s: Key-number is %d and number of spaces is %d', -            key_field, key_number, spaces) - -        # 5.2 6. if /key-number_n/ is not an integral multiple of /spaces_n/ -        # then abort the WebSocket connection. -        if key_number % spaces != 0: -            raise HandshakeException( -                '%s: Key-number (%d) is not an integral multiple of spaces ' -                '(%d)' % (key_field, key_number, spaces)) -        # 5.2 7. let /part_n/ be /key-number_n/ divided by /spaces_n/. -        part = key_number / spaces -        self._logger.debug('%s: Part is %d', key_field, part) -        return part - -    def _get_challenge(self): -        # 5.2 4-7. -        key1 = self._get_key_value(common.SEC_WEBSOCKET_KEY1_HEADER) -        key2 = self._get_key_value(common.SEC_WEBSOCKET_KEY2_HEADER) -        # 5.2 8. let /challenge/ be the concatenation of /part_1/, -        challenge = '' -        challenge += struct.pack('!I', key1)  # network byteorder int -        challenge += struct.pack('!I', key2)  # network byteorder int -        challenge += self._request.connection.read(8) -        return challenge - -    def _send_handshake(self): -        response = [] - -        # 5.2 10. send the following line. -        response.append('HTTP/1.1 101 WebSocket Protocol Handshake\r\n') - -        # 5.2 11. send the following fields to the client. -        response.append(format_header( -            common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75)) -        response.append(format_header( -            common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE)) -        response.append(format_header( -            common.SEC_WEBSOCKET_LOCATION_HEADER, self._request.ws_location)) -        response.append(format_header( -            common.SEC_WEBSOCKET_ORIGIN_HEADER, self._request.ws_origin)) -        if self._request.ws_protocol: -            response.append(format_header( -                common.SEC_WEBSOCKET_PROTOCOL_HEADER, -                self._request.ws_protocol)) -        # 5.2 12. send two bytes 0x0D 0x0A. -        response.append('\r\n') -        # 5.2 13. send /response/ -        response.append(self._request.ws_challenge_md5) - -        raw_response = ''.join(response) -        self._request.connection.write(raw_response) -        self._logger.debug('Sent server\'s opening handshake: %r', -                           raw_response) - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/headerparserhandler.py b/module/lib/mod_pywebsocket/headerparserhandler.py deleted file mode 100644 index 2cc62de04..000000000 --- a/module/lib/mod_pywebsocket/headerparserhandler.py +++ /dev/null @@ -1,244 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""PythonHeaderParserHandler for mod_pywebsocket. - -Apache HTTP Server and mod_python must be configured such that this -function is called to handle WebSocket request. -""" - - -import logging - -from mod_python import apache - -from mod_pywebsocket import common -from mod_pywebsocket import dispatch -from mod_pywebsocket import handshake -from mod_pywebsocket import util - - -# PythonOption to specify the handler root directory. -_PYOPT_HANDLER_ROOT = 'mod_pywebsocket.handler_root' - -# PythonOption to specify the handler scan directory. -# This must be a directory under the root directory. -# The default is the root directory. -_PYOPT_HANDLER_SCAN = 'mod_pywebsocket.handler_scan' - -# PythonOption to allow handlers whose canonical path is -# not under the root directory. It's disallowed by default. -# Set this option with value of 'yes' to allow. -_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT = ( -    'mod_pywebsocket.allow_handlers_outside_root_dir') -# Map from values to their meanings. 'Yes' and 'No' are allowed just for -# compatibility. -_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION = { -    'off': False, 'no': False, 'on': True, 'yes': True} - -# (Obsolete option. Ignored.) -# PythonOption to specify to allow handshake defined in Hixie 75 version -# protocol. The default is None (Off) -_PYOPT_ALLOW_DRAFT75 = 'mod_pywebsocket.allow_draft75' -# Map from values to their meanings. -_PYOPT_ALLOW_DRAFT75_DEFINITION = {'off': False, 'on': True} - - -class ApacheLogHandler(logging.Handler): -    """Wrapper logging.Handler to emit log message to apache's error.log.""" - -    _LEVELS = { -        logging.DEBUG: apache.APLOG_DEBUG, -        logging.INFO: apache.APLOG_INFO, -        logging.WARNING: apache.APLOG_WARNING, -        logging.ERROR: apache.APLOG_ERR, -        logging.CRITICAL: apache.APLOG_CRIT, -        } - -    def __init__(self, request=None): -        logging.Handler.__init__(self) -        self._log_error = apache.log_error -        if request is not None: -            self._log_error = request.log_error - -        # Time and level will be printed by Apache. -        self._formatter = logging.Formatter('%(name)s: %(message)s') - -    def emit(self, record): -        apache_level = apache.APLOG_DEBUG -        if record.levelno in ApacheLogHandler._LEVELS: -            apache_level = ApacheLogHandler._LEVELS[record.levelno] - -        msg = self._formatter.format(record) - -        # "server" parameter must be passed to have "level" parameter work. -        # If only "level" parameter is passed, nothing shows up on Apache's -        # log. However, at this point, we cannot get the server object of the -        # virtual host which will process WebSocket requests. The only server -        # object we can get here is apache.main_server. But Wherever (server -        # configuration context or virtual host context) we put -        # PythonHeaderParserHandler directive, apache.main_server just points -        # the main server instance (not any of virtual server instance). Then, -        # Apache follows LogLevel directive in the server configuration context -        # to filter logs. So, we need to specify LogLevel in the server -        # configuration context. Even if we specify "LogLevel debug" in the -        # virtual host context which actually handles WebSocket connections, -        # DEBUG level logs never show up unless "LogLevel debug" is specified -        # in the server configuration context. -        # -        # TODO(tyoshino): Provide logging methods on request object. When -        # request is mp_request object (when used together with Apache), the -        # methods call request.log_error indirectly. When request is -        # _StandaloneRequest, the methods call Python's logging facility which -        # we create in standalone.py. -        self._log_error(msg, apache_level, apache.main_server) - - -def _configure_logging(): -    logger = logging.getLogger() -    # Logs are filtered by Apache based on LogLevel directive in Apache -    # configuration file. We must just pass logs for all levels to -    # ApacheLogHandler. -    logger.setLevel(logging.DEBUG) -    logger.addHandler(ApacheLogHandler()) - - -_configure_logging() - -_LOGGER = logging.getLogger(__name__) - - -def _parse_option(name, value, definition): -    if value is None: -        return False - -    meaning = definition.get(value.lower()) -    if meaning is None: -        raise Exception('Invalid value for PythonOption %s: %r' % -                        (name, value)) -    return meaning - - -def _create_dispatcher(): -    _LOGGER.info('Initializing Dispatcher') - -    options = apache.main_server.get_options() - -    handler_root = options.get(_PYOPT_HANDLER_ROOT, None) -    if not handler_root: -        raise Exception('PythonOption %s is not defined' % _PYOPT_HANDLER_ROOT, -                        apache.APLOG_ERR) - -    handler_scan = options.get(_PYOPT_HANDLER_SCAN, handler_root) - -    allow_handlers_outside_root = _parse_option( -        _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT, -        options.get(_PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT), -        _PYOPT_ALLOW_HANDLERS_OUTSIDE_ROOT_DEFINITION) - -    dispatcher = dispatch.Dispatcher( -        handler_root, handler_scan, allow_handlers_outside_root) - -    for warning in dispatcher.source_warnings(): -        apache.log_error('mod_pywebsocket: %s' % warning, apache.APLOG_WARNING) - -    return dispatcher - - -# Initialize -_dispatcher = _create_dispatcher() - - -def headerparserhandler(request): -    """Handle request. - -    Args: -        request: mod_python request. - -    This function is named headerparserhandler because it is the default -    name for a PythonHeaderParserHandler. -    """ - -    handshake_is_done = False -    try: -        # Fallback to default http handler for request paths for which -        # we don't have request handlers. -        if not _dispatcher.get_handler_suite(request.uri): -            request.log_error('No handler for resource: %r' % request.uri, -                              apache.APLOG_INFO) -            request.log_error('Fallback to Apache', apache.APLOG_INFO) -            return apache.DECLINED -    except dispatch.DispatchException, e: -        request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) -        if not handshake_is_done: -            return e.status - -    try: -        allow_draft75 = _parse_option( -            _PYOPT_ALLOW_DRAFT75, -            apache.main_server.get_options().get(_PYOPT_ALLOW_DRAFT75), -            _PYOPT_ALLOW_DRAFT75_DEFINITION) - -        try: -            handshake.do_handshake( -                request, _dispatcher, allowDraft75=allow_draft75) -        except handshake.VersionException, e: -            request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) -            request.err_headers_out.add(common.SEC_WEBSOCKET_VERSION_HEADER, -                                        e.supported_versions) -            return apache.HTTP_BAD_REQUEST -        except handshake.HandshakeException, e: -            # Handshake for ws/wss failed. -            # Send http response with error status. -            request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) -            return e.status - -        handshake_is_done = True -        request._dispatcher = _dispatcher -        _dispatcher.transfer_data(request) -    except handshake.AbortedByUserException, e: -        request.log_error('mod_pywebsocket: %s' % e, apache.APLOG_INFO) -    except Exception, e: -        # DispatchException can also be thrown if something is wrong in -        # pywebsocket code. It's caught here, then. - -        request.log_error('mod_pywebsocket: %s\n%s' % -                          (e, util.get_stack_trace()), -                          apache.APLOG_ERR) -        # Unknown exceptions before handshake mean Apache must handle its -        # request with another handler. -        if not handshake_is_done: -            return apache.DECLINED -    # Set assbackwards to suppress response header generation by Apache. -    request.assbackwards = 1 -    return apache.DONE  # Return DONE such that no other handlers are invoked. - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/http_header_util.py b/module/lib/mod_pywebsocket/http_header_util.py deleted file mode 100644 index b77465393..000000000 --- a/module/lib/mod_pywebsocket/http_header_util.py +++ /dev/null @@ -1,263 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""Utilities for parsing and formatting headers that follow the grammar defined -in HTTP RFC http://www.ietf.org/rfc/rfc2616.txt. -""" - - -import urlparse - - -_SEPARATORS = '()<>@,;:\\"/[]?={} \t' - - -def _is_char(c): -    """Returns true iff c is in CHAR as specified in HTTP RFC.""" - -    return ord(c) <= 127 - - -def _is_ctl(c): -    """Returns true iff c is in CTL as specified in HTTP RFC.""" - -    return ord(c) <= 31 or ord(c) == 127 - - -class ParsingState(object): - -    def __init__(self, data): -        self.data = data -        self.head = 0 - - -def peek(state, pos=0): -    """Peeks the character at pos from the head of data.""" - -    if state.head + pos >= len(state.data): -        return None - -    return state.data[state.head + pos] - - -def consume(state, amount=1): -    """Consumes specified amount of bytes from the head and returns the -    consumed bytes. If there's not enough bytes to consume, returns None. -    """ - -    if state.head + amount > len(state.data): -        return None - -    result = state.data[state.head:state.head + amount] -    state.head = state.head + amount -    return result - - -def consume_string(state, expected): -    """Given a parsing state and a expected string, consumes the string from -    the head. Returns True if consumed successfully. Otherwise, returns -    False. -    """ - -    pos = 0 - -    for c in expected: -        if c != peek(state, pos): -            return False -        pos += 1 - -    consume(state, pos) -    return True - - -def consume_lws(state): -    """Consumes a LWS from the head. Returns True if any LWS is consumed. -    Otherwise, returns False. - -    LWS = [CRLF] 1*( SP | HT ) -    """ - -    original_head = state.head - -    consume_string(state, '\r\n') - -    pos = 0 - -    while True: -        c = peek(state, pos) -        if c == ' ' or c == '\t': -            pos += 1 -        else: -            if pos == 0: -                state.head = original_head -                return False -            else: -                consume(state, pos) -                return True - - -def consume_lwses(state): -    """Consumes *LWS from the head.""" - -    while consume_lws(state): -        pass - - -def consume_token(state): -    """Consumes a token from the head. Returns the token or None if no token -    was found. -    """ - -    pos = 0 - -    while True: -        c = peek(state, pos) -        if c is None or c in _SEPARATORS or _is_ctl(c) or not _is_char(c): -            if pos == 0: -                return None - -            return consume(state, pos) -        else: -            pos += 1 - - -def consume_token_or_quoted_string(state): -    """Consumes a token or a quoted-string, and returns the token or unquoted -    string. If no token or quoted-string was found, returns None. -    """ - -    original_head = state.head - -    if not consume_string(state, '"'): -        return consume_token(state) - -    result = [] - -    expect_quoted_pair = False - -    while True: -        if not expect_quoted_pair and consume_lws(state): -            result.append(' ') -            continue - -        c = consume(state) -        if c is None: -            # quoted-string is not enclosed with double quotation -            state.head = original_head -            return None -        elif expect_quoted_pair: -            expect_quoted_pair = False -            if _is_char(c): -                result.append(c) -            else: -                # Non CHAR character found in quoted-pair -                state.head = original_head -                return None -        elif c == '\\': -            expect_quoted_pair = True -        elif c == '"': -            return ''.join(result) -        elif _is_ctl(c): -            # Invalid character %r found in qdtext -            state.head = original_head -            return None -        else: -            result.append(c) - - -def quote_if_necessary(s): -    """Quotes arbitrary string into quoted-string.""" - -    quote = False -    if s == '': -        return '""' - -    result = [] -    for c in s: -        if c == '"' or c in _SEPARATORS or _is_ctl(c) or not _is_char(c): -            quote = True - -        if c == '"' or _is_ctl(c): -            result.append('\\' + c) -        else: -            result.append(c) - -    if quote: -        return '"' + ''.join(result) + '"' -    else: -        return ''.join(result) - - -def parse_uri(uri): -    """Parse absolute URI then return host, port and resource.""" - -    parsed = urlparse.urlsplit(uri) -    if parsed.scheme != 'wss' and parsed.scheme != 'ws': -        # |uri| must be a relative URI. -        # TODO(toyoshim): Should validate |uri|. -        return None, None, uri - -    if parsed.hostname is None: -        return None, None, None - -    port = None -    try: -        port = parsed.port -    except ValueError, e: -        # port property cause ValueError on invalid null port description like -        # 'ws://host:/path'. -        return None, None, None - -    if port is None: -        if parsed.scheme == 'ws': -            port = 80 -        else: -            port = 443 - -    path = parsed.path -    if not path: -        path += '/' -    if parsed.query: -        path += '?' + parsed.query -    if parsed.fragment: -        path += '#' + parsed.fragment - -    return parsed.hostname, port, path - - -try: -    urlparse.uses_netloc.index('ws') -except ValueError, e: -    # urlparse in Python2.5.1 doesn't have 'ws' and 'wss' entries. -    urlparse.uses_netloc.append('ws') -    urlparse.uses_netloc.append('wss') - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/memorizingfile.py b/module/lib/mod_pywebsocket/memorizingfile.py deleted file mode 100644 index 4d4cd9585..000000000 --- a/module/lib/mod_pywebsocket/memorizingfile.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""Memorizing file. - -A memorizing file wraps a file and memorizes lines read by readline. -""" - - -import sys - - -class MemorizingFile(object): -    """MemorizingFile wraps a file and memorizes lines read by readline. - -    Note that data read by other methods are not memorized. This behavior -    is good enough for memorizing lines SimpleHTTPServer reads before -    the control reaches WebSocketRequestHandler. -    """ - -    def __init__(self, file_, max_memorized_lines=sys.maxint): -        """Construct an instance. - -        Args: -            file_: the file object to wrap. -            max_memorized_lines: the maximum number of lines to memorize. -                Only the first max_memorized_lines are memorized. -                Default: sys.maxint. -        """ - -        self._file = file_ -        self._memorized_lines = [] -        self._max_memorized_lines = max_memorized_lines -        self._buffered = False -        self._buffered_line = None - -    def __getattribute__(self, name): -        if name in ('_file', '_memorized_lines', '_max_memorized_lines', -                    '_buffered', '_buffered_line', 'readline', -                    'get_memorized_lines'): -            return object.__getattribute__(self, name) -        return self._file.__getattribute__(name) - -    def readline(self, size=-1): -        """Override file.readline and memorize the line read. - -        Note that even if size is specified and smaller than actual size, -        the whole line will be read out from underlying file object by -        subsequent readline calls. -        """ - -        if self._buffered: -            line = self._buffered_line -            self._buffered = False -        else: -            line = self._file.readline() -            if line and len(self._memorized_lines) < self._max_memorized_lines: -                self._memorized_lines.append(line) -        if size >= 0 and size < len(line): -            self._buffered = True -            self._buffered_line = line[size:] -            return line[:size] -        return line - -    def get_memorized_lines(self): -        """Get lines memorized so far.""" -        return self._memorized_lines - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/msgutil.py b/module/lib/mod_pywebsocket/msgutil.py deleted file mode 100644 index 4c1a0114b..000000000 --- a/module/lib/mod_pywebsocket/msgutil.py +++ /dev/null @@ -1,219 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""Message related utilities. - -Note: request.connection.write/read are used in this module, even though -mod_python document says that they should be used only in connection -handlers. Unfortunately, we have no other options. For example, -request.write/read are not suitable because they don't allow direct raw -bytes writing/reading. -""" - - -import Queue -import threading - - -# Export Exception symbols from msgutil for backward compatibility -from mod_pywebsocket._stream_base import ConnectionTerminatedException -from mod_pywebsocket._stream_base import InvalidFrameException -from mod_pywebsocket._stream_base import BadOperationException -from mod_pywebsocket._stream_base import UnsupportedFrameException - - -# An API for handler to send/receive WebSocket messages. -def close_connection(request): -    """Close connection. - -    Args: -        request: mod_python request. -    """ -    request.ws_stream.close_connection() - - -def send_message(request, payload_data, end=True, binary=False): -    """Send a message (or part of a message). - -    Args: -        request: mod_python request. -        payload_data: unicode text or str binary to send. -        end: True to terminate a message. -             False to send payload_data as part of a message that is to be -             terminated by next or later send_message call with end=True. -        binary: send payload_data as binary frame(s). -    Raises: -        BadOperationException: when server already terminated. -    """ -    request.ws_stream.send_message(payload_data, end, binary) - - -def receive_message(request): -    """Receive a WebSocket frame and return its payload as a text in -    unicode or a binary in str. - -    Args: -        request: mod_python request. -    Raises: -        InvalidFrameException:     when client send invalid frame. -        UnsupportedFrameException: when client send unsupported frame e.g. some -                                   of reserved bit is set but no extension can -                                   recognize it. -        InvalidUTF8Exception:      when client send a text frame containing any -                                   invalid UTF-8 string. -        ConnectionTerminatedException: when the connection is closed -                                   unexpectedly. -        BadOperationException:     when client already terminated. -    """ -    return request.ws_stream.receive_message() - - -def send_ping(request, body=''): -    request.ws_stream.send_ping(body) - - -class MessageReceiver(threading.Thread): -    """This class receives messages from the client. - -    This class provides three ways to receive messages: blocking, -    non-blocking, and via callback. Callback has the highest precedence. - -    Note: This class should not be used with the standalone server for wss -    because pyOpenSSL used by the server raises a fatal error if the socket -    is accessed from multiple threads. -    """ - -    def __init__(self, request, onmessage=None): -        """Construct an instance. - -        Args: -            request: mod_python request. -            onmessage: a function to be called when a message is received. -                       May be None. If not None, the function is called on -                       another thread. In that case, MessageReceiver.receive -                       and MessageReceiver.receive_nowait are useless -                       because they will never return any messages. -        """ - -        threading.Thread.__init__(self) -        self._request = request -        self._queue = Queue.Queue() -        self._onmessage = onmessage -        self._stop_requested = False -        self.setDaemon(True) -        self.start() - -    def run(self): -        try: -            while not self._stop_requested: -                message = receive_message(self._request) -                if self._onmessage: -                    self._onmessage(message) -                else: -                    self._queue.put(message) -        finally: -            close_connection(self._request) - -    def receive(self): -        """ Receive a message from the channel, blocking. - -        Returns: -            message as a unicode string. -        """ -        return self._queue.get() - -    def receive_nowait(self): -        """ Receive a message from the channel, non-blocking. - -        Returns: -            message as a unicode string if available. None otherwise. -        """ -        try: -            message = self._queue.get_nowait() -        except Queue.Empty: -            message = None -        return message - -    def stop(self): -        """Request to stop this instance. - -        The instance will be stopped after receiving the next message. -        This method may not be very useful, but there is no clean way -        in Python to forcefully stop a running thread. -        """ -        self._stop_requested = True - - -class MessageSender(threading.Thread): -    """This class sends messages to the client. - -    This class provides both synchronous and asynchronous ways to send -    messages. - -    Note: This class should not be used with the standalone server for wss -    because pyOpenSSL used by the server raises a fatal error if the socket -    is accessed from multiple threads. -    """ - -    def __init__(self, request): -        """Construct an instance. - -        Args: -            request: mod_python request. -        """ -        threading.Thread.__init__(self) -        self._request = request -        self._queue = Queue.Queue() -        self.setDaemon(True) -        self.start() - -    def run(self): -        while True: -            message, condition = self._queue.get() -            condition.acquire() -            send_message(self._request, message) -            condition.notify() -            condition.release() - -    def send(self, message): -        """Send a message, blocking.""" - -        condition = threading.Condition() -        condition.acquire() -        self._queue.put((message, condition)) -        condition.wait() - -    def send_nowait(self, message): -        """Send a message, non-blocking.""" - -        self._queue.put((message, threading.Condition())) - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/mux.py b/module/lib/mod_pywebsocket/mux.py deleted file mode 100644 index f0bdd2461..000000000 --- a/module/lib/mod_pywebsocket/mux.py +++ /dev/null @@ -1,1636 +0,0 @@ -# Copyright 2012, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""This file provides classes and helper functions for multiplexing extension. - -Specification: -http://tools.ietf.org/html/draft-ietf-hybi-websocket-multiplexing-06 -""" - - -import collections -import copy -import email -import email.parser -import logging -import math -import struct -import threading -import traceback - -from mod_pywebsocket import common -from mod_pywebsocket import handshake -from mod_pywebsocket import util -from mod_pywebsocket._stream_base import BadOperationException -from mod_pywebsocket._stream_base import ConnectionTerminatedException -from mod_pywebsocket._stream_hybi import Frame -from mod_pywebsocket._stream_hybi import Stream -from mod_pywebsocket._stream_hybi import StreamOptions -from mod_pywebsocket._stream_hybi import create_binary_frame -from mod_pywebsocket._stream_hybi import create_closing_handshake_body -from mod_pywebsocket._stream_hybi import create_header -from mod_pywebsocket._stream_hybi import create_length_header -from mod_pywebsocket._stream_hybi import parse_frame -from mod_pywebsocket.handshake import hybi - - -_CONTROL_CHANNEL_ID = 0 -_DEFAULT_CHANNEL_ID = 1 - -_MUX_OPCODE_ADD_CHANNEL_REQUEST = 0 -_MUX_OPCODE_ADD_CHANNEL_RESPONSE = 1 -_MUX_OPCODE_FLOW_CONTROL = 2 -_MUX_OPCODE_DROP_CHANNEL = 3 -_MUX_OPCODE_NEW_CHANNEL_SLOT = 4 - -_MAX_CHANNEL_ID = 2 ** 29 - 1 - -_INITIAL_NUMBER_OF_CHANNEL_SLOTS = 64 -_INITIAL_QUOTA_FOR_CLIENT = 8 * 1024 - -_HANDSHAKE_ENCODING_IDENTITY = 0 -_HANDSHAKE_ENCODING_DELTA = 1 - -# We need only these status code for now. -_HTTP_BAD_RESPONSE_MESSAGES = { -    common.HTTP_STATUS_BAD_REQUEST: 'Bad Request', -} - -# DropChannel reason code -# TODO(bashi): Define all reason code defined in -05 draft. -_DROP_CODE_NORMAL_CLOSURE = 1000 - -_DROP_CODE_INVALID_ENCAPSULATING_MESSAGE = 2001 -_DROP_CODE_CHANNEL_ID_TRUNCATED = 2002 -_DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED = 2003 -_DROP_CODE_UNKNOWN_MUX_OPCODE = 2004 -_DROP_CODE_INVALID_MUX_CONTROL_BLOCK = 2005 -_DROP_CODE_CHANNEL_ALREADY_EXISTS = 2006 -_DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION = 2007 - -_DROP_CODE_UNKNOWN_REQUEST_ENCODING = 3002 -_DROP_CODE_SEND_QUOTA_VIOLATION = 3005 -_DROP_CODE_ACKNOWLEDGED = 3008 - - -class MuxUnexpectedException(Exception): -    """Exception in handling multiplexing extension.""" -    pass - - -# Temporary -class MuxNotImplementedException(Exception): -    """Raised when a flow enters unimplemented code path.""" -    pass - - -class LogicalConnectionClosedException(Exception): -    """Raised when logical connection is gracefully closed.""" -    pass - - -class PhysicalConnectionError(Exception): -    """Raised when there is a physical connection error.""" -    def __init__(self, drop_code, message=''): -        super(PhysicalConnectionError, self).__init__( -            'code=%d, message=%r' % (drop_code, message)) -        self.drop_code = drop_code -        self.message = message - - -class LogicalChannelError(Exception): -    """Raised when there is a logical channel error.""" -    def __init__(self, channel_id, drop_code, message=''): -        super(LogicalChannelError, self).__init__( -            'channel_id=%d, code=%d, message=%r' % ( -                channel_id, drop_code, message)) -        self.channel_id = channel_id -        self.drop_code = drop_code -        self.message = message - - -def _encode_channel_id(channel_id): -    if channel_id < 0: -        raise ValueError('Channel id %d must not be negative' % channel_id) - -    if channel_id < 2 ** 7: -        return chr(channel_id) -    if channel_id < 2 ** 14: -        return struct.pack('!H', 0x8000 + channel_id) -    if channel_id < 2 ** 21: -        first = chr(0xc0 + (channel_id >> 16)) -        return first + struct.pack('!H', channel_id & 0xffff) -    if channel_id < 2 ** 29: -        return struct.pack('!L', 0xe0000000 + channel_id) - -    raise ValueError('Channel id %d is too large' % channel_id) - - -def _encode_number(number): -    return create_length_header(number, False) - - -def _create_add_channel_response(channel_id, encoded_handshake, -                                 encoding=0, rejected=False, -                                 outer_frame_mask=False): -    if encoding != 0 and encoding != 1: -        raise ValueError('Invalid encoding %d' % encoding) - -    first_byte = ((_MUX_OPCODE_ADD_CHANNEL_RESPONSE << 5) | -                  (rejected << 4) | encoding) -    block = (chr(first_byte) + -             _encode_channel_id(channel_id) + -             _encode_number(len(encoded_handshake)) + -             encoded_handshake) -    payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block -    return create_binary_frame(payload, mask=outer_frame_mask) - - -def _create_drop_channel(channel_id, code=None, message='', -                         outer_frame_mask=False): -    if len(message) > 0 and code is None: -        raise ValueError('Code must be specified if message is specified') - -    first_byte = _MUX_OPCODE_DROP_CHANNEL << 5 -    block = chr(first_byte) + _encode_channel_id(channel_id) -    if code is None: -        block += _encode_number(0) # Reason size -    else: -        reason = struct.pack('!H', code) + message -        reason_size = _encode_number(len(reason)) -        block += reason_size + reason - -    payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block -    return create_binary_frame(payload, mask=outer_frame_mask) - - -def _create_flow_control(channel_id, replenished_quota, -                         outer_frame_mask=False): -    first_byte = _MUX_OPCODE_FLOW_CONTROL << 5 -    block = (chr(first_byte) + -             _encode_channel_id(channel_id) + -             _encode_number(replenished_quota)) -    payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block -    return create_binary_frame(payload, mask=outer_frame_mask) - - -def _create_new_channel_slot(slots, send_quota, outer_frame_mask=False): -    if slots < 0 or send_quota < 0: -        raise ValueError('slots and send_quota must be non-negative.') -    first_byte = _MUX_OPCODE_NEW_CHANNEL_SLOT << 5 -    block = (chr(first_byte) + -             _encode_number(slots) + -             _encode_number(send_quota)) -    payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block -    return create_binary_frame(payload, mask=outer_frame_mask) - - -def _create_fallback_new_channel_slot(outer_frame_mask=False): -    first_byte = (_MUX_OPCODE_NEW_CHANNEL_SLOT << 5) | 1 # Set the F flag -    block = (chr(first_byte) + _encode_number(0) + _encode_number(0)) -    payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + block -    return create_binary_frame(payload, mask=outer_frame_mask) - - -def _parse_request_text(request_text): -    request_line, header_lines = request_text.split('\r\n', 1) - -    words = request_line.split(' ') -    if len(words) != 3: -        raise ValueError('Bad Request-Line syntax %r' % request_line) -    [command, path, version] = words -    if version != 'HTTP/1.1': -        raise ValueError('Bad request version %r' % version) - -    # email.parser.Parser() parses RFC 2822 (RFC 822) style headers. -    # RFC 6455 refers RFC 2616 for handshake parsing, and RFC 2616 refers -    # RFC 822. -    headers = email.parser.Parser().parsestr(header_lines) -    return command, path, version, headers - - -class _ControlBlock(object): -    """A structure that holds parsing result of multiplexing control block. -    Control block specific attributes will be added by _MuxFramePayloadParser. -    (e.g. encoded_handshake will be added for AddChannelRequest and -    AddChannelResponse) -    """ - -    def __init__(self, opcode): -        self.opcode = opcode - - -class _MuxFramePayloadParser(object): -    """A class that parses multiplexed frame payload.""" - -    def __init__(self, payload): -        self._data = payload -        self._read_position = 0 -        self._logger = util.get_class_logger(self) - -    def read_channel_id(self): -        """Reads channel id. - -        Raises: -            ValueError: when the payload doesn't contain -                valid channel id. -        """ - -        remaining_length = len(self._data) - self._read_position -        pos = self._read_position -        if remaining_length == 0: -            raise ValueError('Invalid channel id format') - -        channel_id = ord(self._data[pos]) -        channel_id_length = 1 -        if channel_id & 0xe0 == 0xe0: -            if remaining_length < 4: -                raise ValueError('Invalid channel id format') -            channel_id = struct.unpack('!L', -                                       self._data[pos:pos+4])[0] & 0x1fffffff -            channel_id_length = 4 -        elif channel_id & 0xc0 == 0xc0: -            if remaining_length < 3: -                raise ValueError('Invalid channel id format') -            channel_id = (((channel_id & 0x1f) << 16) + -                          struct.unpack('!H', self._data[pos+1:pos+3])[0]) -            channel_id_length = 3 -        elif channel_id & 0x80 == 0x80: -            if remaining_length < 2: -                raise ValueError('Invalid channel id format') -            channel_id = struct.unpack('!H', -                                       self._data[pos:pos+2])[0] & 0x3fff -            channel_id_length = 2 -        self._read_position += channel_id_length - -        return channel_id - -    def read_inner_frame(self): -        """Reads an inner frame. - -        Raises: -            PhysicalConnectionError: when the inner frame is invalid. -        """ - -        if len(self._data) == self._read_position: -            raise PhysicalConnectionError( -                _DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED) - -        bits = ord(self._data[self._read_position]) -        self._read_position += 1 -        fin = (bits & 0x80) == 0x80 -        rsv1 = (bits & 0x40) == 0x40 -        rsv2 = (bits & 0x20) == 0x20 -        rsv3 = (bits & 0x10) == 0x10 -        opcode = bits & 0xf -        payload = self.remaining_data() -        # Consume rest of the message which is payload data of the original -        # frame. -        self._read_position = len(self._data) -        return fin, rsv1, rsv2, rsv3, opcode, payload - -    def _read_number(self): -        if self._read_position + 1 > len(self._data): -            raise PhysicalConnectionError( -                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                'Cannot read the first byte of number field') - -        number = ord(self._data[self._read_position]) -        if number & 0x80 == 0x80: -            raise PhysicalConnectionError( -                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                'The most significant bit of the first byte of number should ' -                'be unset') -        self._read_position += 1 -        pos = self._read_position -        if number == 127: -            if pos + 8 > len(self._data): -                raise PhysicalConnectionError( -                    _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                    'Invalid number field') -            self._read_position += 8 -            number = struct.unpack('!Q', self._data[pos:pos+8])[0] -            if number > 0x7FFFFFFFFFFFFFFF: -                raise PhysicalConnectionError( -                    _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                    'Encoded number >= 2^63') -            if number <= 0xFFFF: -                raise PhysicalConnectionError( -                    _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                    '%d should not be encoded by 9 bytes encoding' % number) -            return number -        if number == 126: -            if pos + 2 > len(self._data): -                raise PhysicalConnectionError( -                    _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                    'Invalid number field') -            self._read_position += 2 -            number = struct.unpack('!H', self._data[pos:pos+2])[0] -            if number <= 125: -                raise PhysicalConnectionError( -                    _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                    '%d should not be encoded by 3 bytes encoding' % number) -        return number - -    def _read_size_and_contents(self): -        """Reads data that consists of followings: -            - the size of the contents encoded the same way as payload length -              of the WebSocket Protocol with 1 bit padding at the head. -            - the contents. -        """ - -        size = self._read_number() -        pos = self._read_position -        if pos + size > len(self._data): -            raise PhysicalConnectionError( -                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                'Cannot read %d bytes data' % size) - -        self._read_position += size -        return self._data[pos:pos+size] - -    def _read_add_channel_request(self, first_byte, control_block): -        reserved = (first_byte >> 2) & 0x7 -        if reserved != 0: -            raise PhysicalConnectionError( -                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                'Reserved bits must be unset') - -        # Invalid encoding will be handled by MuxHandler. -        encoding = first_byte & 0x3 -        try: -            control_block.channel_id = self.read_channel_id() -        except ValueError, e: -            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK) -        control_block.encoding = encoding -        encoded_handshake = self._read_size_and_contents() -        control_block.encoded_handshake = encoded_handshake -        return control_block - -    def _read_add_channel_response(self, first_byte, control_block): -        reserved = (first_byte >> 2) & 0x3 -        if reserved != 0: -            raise PhysicalConnectionError( -                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                'Reserved bits must be unset') - -        control_block.accepted = (first_byte >> 4) & 1 -        control_block.encoding = first_byte & 0x3 -        try: -            control_block.channel_id = self.read_channel_id() -        except ValueError, e: -            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK) -        control_block.encoded_handshake = self._read_size_and_contents() -        return control_block - -    def _read_flow_control(self, first_byte, control_block): -        reserved = first_byte & 0x1f -        if reserved != 0: -            raise PhysicalConnectionError( -                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                'Reserved bits must be unset') - -        try: -            control_block.channel_id = self.read_channel_id() -        except ValueError, e: -            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK) -        control_block.send_quota = self._read_number() -        return control_block - -    def _read_drop_channel(self, first_byte, control_block): -        reserved = first_byte & 0x1f -        if reserved != 0: -            raise PhysicalConnectionError( -                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                'Reserved bits must be unset') - -        try: -            control_block.channel_id = self.read_channel_id() -        except ValueError, e: -            raise PhysicalConnectionError(_DROP_CODE_INVALID_MUX_CONTROL_BLOCK) -        reason = self._read_size_and_contents() -        if len(reason) == 0: -            control_block.drop_code = None -            control_block.drop_message = '' -        elif len(reason) >= 2: -            control_block.drop_code = struct.unpack('!H', reason[:2])[0] -            control_block.drop_message = reason[2:] -        else: -            raise PhysicalConnectionError( -                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                'Received DropChannel that conains only 1-byte reason') -        return control_block - -    def _read_new_channel_slot(self, first_byte, control_block): -        reserved = first_byte & 0x1e -        if reserved != 0: -            raise PhysicalConnectionError( -                _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                'Reserved bits must be unset') -        control_block.fallback = first_byte & 1 -        control_block.slots = self._read_number() -        control_block.send_quota = self._read_number() -        return control_block - -    def read_control_blocks(self): -        """Reads control block(s). - -        Raises: -           PhysicalConnectionError: when the payload contains invalid control -               block(s). -           StopIteration: when no control blocks left. -        """ - -        while self._read_position < len(self._data): -            first_byte = ord(self._data[self._read_position]) -            self._read_position += 1 -            opcode = (first_byte >> 5) & 0x7 -            control_block = _ControlBlock(opcode=opcode) -            if opcode == _MUX_OPCODE_ADD_CHANNEL_REQUEST: -                yield self._read_add_channel_request(first_byte, control_block) -            elif opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE: -                yield self._read_add_channel_response( -                    first_byte, control_block) -            elif opcode == _MUX_OPCODE_FLOW_CONTROL: -                yield self._read_flow_control(first_byte, control_block) -            elif opcode == _MUX_OPCODE_DROP_CHANNEL: -                yield self._read_drop_channel(first_byte, control_block) -            elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT: -                yield self._read_new_channel_slot(first_byte, control_block) -            else: -                raise PhysicalConnectionError( -                    _DROP_CODE_UNKNOWN_MUX_OPCODE, -                    'Invalid opcode %d' % opcode) - -        assert self._read_position == len(self._data) -        raise StopIteration - -    def remaining_data(self): -        """Returns remaining data.""" - -        return self._data[self._read_position:] - - -class _LogicalRequest(object): -    """Mimics mod_python request.""" - -    def __init__(self, channel_id, command, path, protocol, headers, -                 connection): -        """Constructs an instance. - -        Args: -            channel_id: the channel id of the logical channel. -            command: HTTP request command. -            path: HTTP request path. -            headers: HTTP headers. -            connection: _LogicalConnection instance. -        """ - -        self.channel_id = channel_id -        self.method = command -        self.uri = path -        self.protocol = protocol -        self.headers_in = headers -        self.connection = connection -        self.server_terminated = False -        self.client_terminated = False - -    def is_https(self): -        """Mimics request.is_https(). Returns False because this method is -        used only by old protocols (hixie and hybi00). -        """ - -        return False - - -class _LogicalConnection(object): -    """Mimics mod_python mp_conn.""" - -    # For details, see the comment of set_read_state(). -    STATE_ACTIVE = 1 -    STATE_GRACEFULLY_CLOSED = 2 -    STATE_TERMINATED = 3 - -    def __init__(self, mux_handler, channel_id): -        """Constructs an instance. - -        Args: -            mux_handler: _MuxHandler instance. -            channel_id: channel id of this connection. -        """ - -        self._mux_handler = mux_handler -        self._channel_id = channel_id -        self._incoming_data = '' -        self._write_condition = threading.Condition() -        self._waiting_write_completion = False -        self._read_condition = threading.Condition() -        self._read_state = self.STATE_ACTIVE - -    def get_local_addr(self): -        """Getter to mimic mp_conn.local_addr.""" - -        return self._mux_handler.physical_connection.get_local_addr() -    local_addr = property(get_local_addr) - -    def get_remote_addr(self): -        """Getter to mimic mp_conn.remote_addr.""" - -        return self._mux_handler.physical_connection.get_remote_addr() -    remote_addr = property(get_remote_addr) - -    def get_memorized_lines(self): -        """Gets memorized lines. Not supported.""" - -        raise MuxUnexpectedException('_LogicalConnection does not support ' -                                     'get_memorized_lines') - -    def write(self, data): -        """Writes data. mux_handler sends data asynchronously. The caller will -        be suspended until write done. - -        Args: -            data: data to be written. - -        Raises: -            MuxUnexpectedException: when called before finishing the previous -                write. -        """ - -        try: -            self._write_condition.acquire() -            if self._waiting_write_completion: -                raise MuxUnexpectedException( -                    'Logical connection %d is already waiting the completion ' -                    'of write' % self._channel_id) - -            self._waiting_write_completion = True -            self._mux_handler.send_data(self._channel_id, data) -            self._write_condition.wait() -        finally: -            self._write_condition.release() - -    def write_control_data(self, data): -        """Writes data via the control channel. Don't wait finishing write -        because this method can be called by mux dispatcher. - -        Args: -            data: data to be written. -        """ - -        self._mux_handler.send_control_data(data) - -    def notify_write_done(self): -        """Called when sending data is completed.""" - -        try: -            self._write_condition.acquire() -            if not self._waiting_write_completion: -                raise MuxUnexpectedException( -                    'Invalid call of notify_write_done for logical connection' -                    ' %d' % self._channel_id) -            self._waiting_write_completion = False -            self._write_condition.notify() -        finally: -            self._write_condition.release() - -    def append_frame_data(self, frame_data): -        """Appends incoming frame data. Called when mux_handler dispatches -        frame data to the corresponding application. - -        Args: -            frame_data: incoming frame data. -        """ - -        self._read_condition.acquire() -        self._incoming_data += frame_data -        self._read_condition.notify() -        self._read_condition.release() - -    def read(self, length): -        """Reads data. Blocks until enough data has arrived via physical -        connection. - -        Args: -            length: length of data to be read. -        Raises: -            LogicalConnectionClosedException: when closing handshake for this -                logical channel has been received. -            ConnectionTerminatedException: when the physical connection has -                closed, or an error is caused on the reader thread. -        """ - -        self._read_condition.acquire() -        while (self._read_state == self.STATE_ACTIVE and -               len(self._incoming_data) < length): -            self._read_condition.wait() - -        try: -            if self._read_state == self.STATE_GRACEFULLY_CLOSED: -                raise LogicalConnectionClosedException( -                    'Logical channel %d has closed.' % self._channel_id) -            elif self._read_state == self.STATE_TERMINATED: -                raise ConnectionTerminatedException( -                    'Receiving %d byte failed. Logical channel (%d) closed' % -                    (length, self._channel_id)) - -            value = self._incoming_data[:length] -            self._incoming_data = self._incoming_data[length:] -        finally: -            self._read_condition.release() - -        return value - -    def set_read_state(self, new_state): -        """Sets the state of this connection. Called when an event for this -        connection has occurred. - -        Args: -            new_state: state to be set. new_state must be one of followings: -            - STATE_GRACEFULLY_CLOSED: when closing handshake for this -                connection has been received. -            - STATE_TERMINATED: when the physical connection has closed or -                DropChannel of this connection has received. -        """ - -        self._read_condition.acquire() -        self._read_state = new_state -        self._read_condition.notify() -        self._read_condition.release() - - -class _LogicalStream(Stream): -    """Mimics the Stream class. This class interprets multiplexed WebSocket -    frames. -    """ - -    def __init__(self, request, send_quota, receive_quota): -        """Constructs an instance. - -        Args: -            request: _LogicalRequest instance. -            send_quota: Initial send quota. -            receive_quota: Initial receive quota. -        """ - -        # TODO(bashi): Support frame filters. -        stream_options = StreamOptions() -        # Physical stream is responsible for masking. -        stream_options.unmask_receive = False -        # Control frames can be fragmented on logical channel. -        stream_options.allow_fragmented_control_frame = True -        Stream.__init__(self, request, stream_options) -        self._send_quota = send_quota -        self._send_quota_condition = threading.Condition() -        self._receive_quota = receive_quota -        self._write_inner_frame_semaphore = threading.Semaphore() - -    def _create_inner_frame(self, opcode, payload, end=True): -        # TODO(bashi): Support extensions that use reserved bits. -        first_byte = (end << 7) | opcode -        return (_encode_channel_id(self._request.channel_id) + -                chr(first_byte) + payload) - -    def _write_inner_frame(self, opcode, payload, end=True): -        payload_length = len(payload) -        write_position = 0 - -        try: -            # An inner frame will be fragmented if there is no enough send -            # quota. This semaphore ensures that fragmented inner frames are -            # sent in order on the logical channel. -            # Note that frames that come from other logical channels or -            # multiplexing control blocks can be inserted between fragmented -            # inner frames on the physical channel. -            self._write_inner_frame_semaphore.acquire() -            while write_position < payload_length: -                try: -                    self._send_quota_condition.acquire() -                    while self._send_quota == 0: -                        self._logger.debug( -                            'No quota. Waiting FlowControl message for %d.' % -                            self._request.channel_id) -                        self._send_quota_condition.wait() - -                    remaining = payload_length - write_position -                    write_length = min(self._send_quota, remaining) -                    inner_frame_end = ( -                        end and -                        (write_position + write_length == payload_length)) - -                    inner_frame = self._create_inner_frame( -                        opcode, -                        payload[write_position:write_position+write_length], -                        inner_frame_end) -                    frame_data = self._writer.build( -                        inner_frame, end=True, binary=True) -                    self._send_quota -= write_length -                    self._logger.debug('Consumed quota=%d, remaining=%d' % -                                       (write_length, self._send_quota)) -                finally: -                    self._send_quota_condition.release() - -                # Writing data will block the worker so we need to release -                # _send_quota_condition before writing. -                self._logger.debug('Sending inner frame: %r' % frame_data) -                self._request.connection.write(frame_data) -                write_position += write_length - -                opcode = common.OPCODE_CONTINUATION - -        except ValueError, e: -            raise BadOperationException(e) -        finally: -            self._write_inner_frame_semaphore.release() - -    def replenish_send_quota(self, send_quota): -        """Replenish send quota.""" - -        self._send_quota_condition.acquire() -        self._send_quota += send_quota -        self._logger.debug('Replenished send quota for channel id %d: %d' % -                           (self._request.channel_id, self._send_quota)) -        self._send_quota_condition.notify() -        self._send_quota_condition.release() - -    def consume_receive_quota(self, amount): -        """Consumes receive quota. Returns False on failure.""" - -        if self._receive_quota < amount: -            self._logger.debug('Violate quota on channel id %d: %d < %d' % -                               (self._request.channel_id, -                                self._receive_quota, amount)) -            return False -        self._receive_quota -= amount -        return True - -    def send_message(self, message, end=True, binary=False): -        """Override Stream.send_message.""" - -        if self._request.server_terminated: -            raise BadOperationException( -                'Requested send_message after sending out a closing handshake') - -        if binary and isinstance(message, unicode): -            raise BadOperationException( -                'Message for binary frame must be instance of str') - -        if binary: -            opcode = common.OPCODE_BINARY -        else: -            opcode = common.OPCODE_TEXT -            message = message.encode('utf-8') - -        self._write_inner_frame(opcode, message, end) - -    def _receive_frame(self): -        """Overrides Stream._receive_frame. - -        In addition to call Stream._receive_frame, this method adds the amount -        of payload to receiving quota and sends FlowControl to the client. -        We need to do it here because Stream.receive_message() handles -        control frames internally. -        """ - -        opcode, payload, fin, rsv1, rsv2, rsv3 = Stream._receive_frame(self) -        amount = len(payload) -        self._receive_quota += amount -        frame_data = _create_flow_control(self._request.channel_id, -                                          amount) -        self._logger.debug('Sending flow control for %d, replenished=%d' % -                           (self._request.channel_id, amount)) -        self._request.connection.write_control_data(frame_data) -        return opcode, payload, fin, rsv1, rsv2, rsv3 - -    def receive_message(self): -        """Overrides Stream.receive_message.""" - -        # Just call Stream.receive_message(), but catch -        # LogicalConnectionClosedException, which is raised when the logical -        # connection has closed gracefully. -        try: -            return Stream.receive_message(self) -        except LogicalConnectionClosedException, e: -            self._logger.debug('%s', e) -            return None - -    def _send_closing_handshake(self, code, reason): -        """Overrides Stream._send_closing_handshake.""" - -        body = create_closing_handshake_body(code, reason) -        self._logger.debug('Sending closing handshake for %d: (%r, %r)' % -                           (self._request.channel_id, code, reason)) -        self._write_inner_frame(common.OPCODE_CLOSE, body, end=True) - -        self._request.server_terminated = True - -    def send_ping(self, body=''): -        """Overrides Stream.send_ping""" - -        self._logger.debug('Sending ping on logical channel %d: %r' % -                           (self._request.channel_id, body)) -        self._write_inner_frame(common.OPCODE_PING, body, end=True) - -        self._ping_queue.append(body) - -    def _send_pong(self, body): -        """Overrides Stream._send_pong""" - -        self._logger.debug('Sending pong on logical channel %d: %r' % -                           (self._request.channel_id, body)) -        self._write_inner_frame(common.OPCODE_PONG, body, end=True) - -    def close_connection(self, code=common.STATUS_NORMAL_CLOSURE, reason=''): -        """Overrides Stream.close_connection.""" - -        # TODO(bashi): Implement -        self._logger.debug('Closing logical connection %d' % -                           self._request.channel_id) -        self._request.server_terminated = True - -    def _drain_received_data(self): -        """Overrides Stream._drain_received_data. Nothing need to be done for -        logical channel. -        """ - -        pass - - -class _OutgoingData(object): -    """A structure that holds data to be sent via physical connection and -    origin of the data. -    """ - -    def __init__(self, channel_id, data): -        self.channel_id = channel_id -        self.data = data - - -class _PhysicalConnectionWriter(threading.Thread): -    """A thread that is responsible for writing data to physical connection. - -    TODO(bashi): Make sure there is no thread-safety problem when the reader -    thread reads data from the same socket at a time. -    """ - -    def __init__(self, mux_handler): -        """Constructs an instance. - -        Args: -            mux_handler: _MuxHandler instance. -        """ - -        threading.Thread.__init__(self) -        self._logger = util.get_class_logger(self) -        self._mux_handler = mux_handler -        self.setDaemon(True) -        self._stop_requested = False -        self._deque = collections.deque() -        self._deque_condition = threading.Condition() - -    def put_outgoing_data(self, data): -        """Puts outgoing data. - -        Args: -            data: _OutgoingData instance. - -        Raises: -            BadOperationException: when the thread has been requested to -                terminate. -        """ - -        try: -            self._deque_condition.acquire() -            if self._stop_requested: -                raise BadOperationException('Cannot write data anymore') - -            self._deque.append(data) -            self._deque_condition.notify() -        finally: -            self._deque_condition.release() - -    def _write_data(self, outgoing_data): -        try: -            self._mux_handler.physical_connection.write(outgoing_data.data) -        except Exception, e: -            util.prepend_message_to_exception( -                'Failed to send message to %r: ' % -                (self._mux_handler.physical_connection.remote_addr,), e) -            raise - -        # TODO(bashi): It would be better to block the thread that sends -        # control data as well. -        if outgoing_data.channel_id != _CONTROL_CHANNEL_ID: -            self._mux_handler.notify_write_done(outgoing_data.channel_id) - -    def run(self): -        self._deque_condition.acquire() -        while not self._stop_requested: -            if len(self._deque) == 0: -                self._deque_condition.wait() -                continue - -            outgoing_data = self._deque.popleft() -            self._deque_condition.release() -            self._write_data(outgoing_data) -            self._deque_condition.acquire() - -        # Flush deque -        try: -            while len(self._deque) > 0: -                outgoing_data = self._deque.popleft() -                self._write_data(outgoing_data) -        finally: -            self._deque_condition.release() - -    def stop(self): -        """Stops the writer thread.""" - -        self._deque_condition.acquire() -        self._stop_requested = True -        self._deque_condition.notify() -        self._deque_condition.release() - - -class _PhysicalConnectionReader(threading.Thread): -    """A thread that is responsible for reading data from physical connection. -    """ - -    def __init__(self, mux_handler): -        """Constructs an instance. - -        Args: -            mux_handler: _MuxHandler instance. -        """ - -        threading.Thread.__init__(self) -        self._logger = util.get_class_logger(self) -        self._mux_handler = mux_handler -        self.setDaemon(True) - -    def run(self): -        while True: -            try: -                physical_stream = self._mux_handler.physical_stream -                message = physical_stream.receive_message() -                if message is None: -                    break -                # Below happens only when a data message is received. -                opcode = physical_stream.get_last_received_opcode() -                if opcode != common.OPCODE_BINARY: -                    self._mux_handler.fail_physical_connection( -                        _DROP_CODE_INVALID_ENCAPSULATING_MESSAGE, -                        'Received a text message on physical connection') -                    break - -            except ConnectionTerminatedException, e: -                self._logger.debug('%s', e) -                break - -            try: -                self._mux_handler.dispatch_message(message) -            except PhysicalConnectionError, e: -                self._mux_handler.fail_physical_connection( -                    e.drop_code, e.message) -                break -            except LogicalChannelError, e: -                self._mux_handler.fail_logical_channel( -                    e.channel_id, e.drop_code, e.message) -            except Exception, e: -                self._logger.debug(traceback.format_exc()) -                break - -        self._mux_handler.notify_reader_done() - - -class _Worker(threading.Thread): -    """A thread that is responsible for running the corresponding application -    handler. -    """ - -    def __init__(self, mux_handler, request): -        """Constructs an instance. - -        Args: -            mux_handler: _MuxHandler instance. -            request: _LogicalRequest instance. -        """ - -        threading.Thread.__init__(self) -        self._logger = util.get_class_logger(self) -        self._mux_handler = mux_handler -        self._request = request -        self.setDaemon(True) - -    def run(self): -        self._logger.debug('Logical channel worker started. (id=%d)' % -                           self._request.channel_id) -        try: -            # Non-critical exceptions will be handled by dispatcher. -            self._mux_handler.dispatcher.transfer_data(self._request) -        finally: -            self._mux_handler.notify_worker_done(self._request.channel_id) - - -class _MuxHandshaker(hybi.Handshaker): -    """Opening handshake processor for multiplexing.""" - -    _DUMMY_WEBSOCKET_KEY = 'dGhlIHNhbXBsZSBub25jZQ==' - -    def __init__(self, request, dispatcher, send_quota, receive_quota): -        """Constructs an instance. -        Args: -            request: _LogicalRequest instance. -            dispatcher: Dispatcher instance (dispatch.Dispatcher). -            send_quota: Initial send quota. -            receive_quota: Initial receive quota. -        """ - -        hybi.Handshaker.__init__(self, request, dispatcher) -        self._send_quota = send_quota -        self._receive_quota = receive_quota - -        # Append headers which should not be included in handshake field of -        # AddChannelRequest. -        # TODO(bashi): Make sure whether we should raise exception when -        #     these headers are included already. -        request.headers_in[common.UPGRADE_HEADER] = ( -            common.WEBSOCKET_UPGRADE_TYPE) -        request.headers_in[common.CONNECTION_HEADER] = ( -            common.UPGRADE_CONNECTION_TYPE) -        request.headers_in[common.SEC_WEBSOCKET_VERSION_HEADER] = ( -            str(common.VERSION_HYBI_LATEST)) -        request.headers_in[common.SEC_WEBSOCKET_KEY_HEADER] = ( -            self._DUMMY_WEBSOCKET_KEY) - -    def _create_stream(self, stream_options): -        """Override hybi.Handshaker._create_stream.""" - -        self._logger.debug('Creating logical stream for %d' % -                           self._request.channel_id) -        return _LogicalStream(self._request, self._send_quota, -                              self._receive_quota) - -    def _create_handshake_response(self, accept): -        """Override hybi._create_handshake_response.""" - -        response = [] - -        response.append('HTTP/1.1 101 Switching Protocols\r\n') - -        # Upgrade, Connection and Sec-WebSocket-Accept should be excluded. -        if self._request.ws_protocol is not None: -            response.append('%s: %s\r\n' % ( -                common.SEC_WEBSOCKET_PROTOCOL_HEADER, -                self._request.ws_protocol)) -        if (self._request.ws_extensions is not None and -            len(self._request.ws_extensions) != 0): -            response.append('%s: %s\r\n' % ( -                common.SEC_WEBSOCKET_EXTENSIONS_HEADER, -                common.format_extensions(self._request.ws_extensions))) -        response.append('\r\n') - -        return ''.join(response) - -    def _send_handshake(self, accept): -        """Override hybi.Handshaker._send_handshake.""" - -        # Don't send handshake response for the default channel -        if self._request.channel_id == _DEFAULT_CHANNEL_ID: -            return - -        handshake_response = self._create_handshake_response(accept) -        frame_data = _create_add_channel_response( -                         self._request.channel_id, -                         handshake_response) -        self._logger.debug('Sending handshake response for %d: %r' % -                           (self._request.channel_id, frame_data)) -        self._request.connection.write_control_data(frame_data) - - -class _LogicalChannelData(object): -    """A structure that holds information about logical channel. -    """ - -    def __init__(self, request, worker): -        self.request = request -        self.worker = worker -        self.drop_code = _DROP_CODE_NORMAL_CLOSURE -        self.drop_message = '' - - -class _HandshakeDeltaBase(object): -    """A class that holds information for delta-encoded handshake.""" - -    def __init__(self, headers): -        self._headers = headers - -    def create_headers(self, delta=None): -        """Creates request headers for an AddChannelRequest that has -        delta-encoded handshake. - -        Args: -            delta: headers should be overridden. -        """ - -        headers = copy.copy(self._headers) -        if delta: -            for key, value in delta.items(): -                # The spec requires that a header with an empty value is -                # removed from the delta base. -                if len(value) == 0 and headers.has_key(key): -                    del headers[key] -                else: -                    headers[key] = value -        # TODO(bashi): Support extensions -        headers['Sec-WebSocket-Extensions'] = '' -        return headers - - -class _MuxHandler(object): -    """Multiplexing handler. When a handler starts, it launches three -    threads; the reader thread, the writer thread, and a worker thread. - -    The reader thread reads data from the physical stream, i.e., the -    ws_stream object of the underlying websocket connection. The reader -    thread interprets multiplexed frames and dispatches them to logical -    channels. Methods of this class are mostly called by the reader thread. - -    The writer thread sends multiplexed frames which are created by -    logical channels via the physical connection. - -    The worker thread launched at the starting point handles the -    "Implicitly Opened Connection". If multiplexing handler receives -    an AddChannelRequest and accepts it, the handler will launch a new worker -    thread and dispatch the request to it. -    """ - -    def __init__(self, request, dispatcher): -        """Constructs an instance. - -        Args: -            request: mod_python request of the physical connection. -            dispatcher: Dispatcher instance (dispatch.Dispatcher). -        """ - -        self.original_request = request -        self.dispatcher = dispatcher -        self.physical_connection = request.connection -        self.physical_stream = request.ws_stream -        self._logger = util.get_class_logger(self) -        self._logical_channels = {} -        self._logical_channels_condition = threading.Condition() -        # Holds client's initial quota -        self._channel_slots = collections.deque() -        self._handshake_base = None -        self._worker_done_notify_received = False -        self._reader = None -        self._writer = None - -    def start(self): -        """Starts the handler. - -        Raises: -            MuxUnexpectedException: when the handler already started, or when -                opening handshake of the default channel fails. -        """ - -        if self._reader or self._writer: -            raise MuxUnexpectedException('MuxHandler already started') - -        self._reader = _PhysicalConnectionReader(self) -        self._writer = _PhysicalConnectionWriter(self) -        self._reader.start() -        self._writer.start() - -        # Create "Implicitly Opened Connection". -        logical_connection = _LogicalConnection(self, _DEFAULT_CHANNEL_ID) -        self._handshake_base = _HandshakeDeltaBase( -            self.original_request.headers_in) -        logical_request = _LogicalRequest( -            _DEFAULT_CHANNEL_ID, -            self.original_request.method, -            self.original_request.uri, -            self.original_request.protocol, -            self._handshake_base.create_headers(), -            logical_connection) -        # Client's send quota for the implicitly opened connection is zero, -        # but we will send FlowControl later so set the initial quota to -        # _INITIAL_QUOTA_FOR_CLIENT. -        self._channel_slots.append(_INITIAL_QUOTA_FOR_CLIENT) -        if not self._do_handshake_for_logical_request( -            logical_request, send_quota=self.original_request.mux_quota): -            raise MuxUnexpectedException( -                'Failed handshake on the default channel id') -        self._add_logical_channel(logical_request) - -        # Send FlowControl for the implicitly opened connection. -        frame_data = _create_flow_control(_DEFAULT_CHANNEL_ID, -                                          _INITIAL_QUOTA_FOR_CLIENT) -        logical_request.connection.write_control_data(frame_data) - -    def add_channel_slots(self, slots, send_quota): -        """Adds channel slots. - -        Args: -            slots: number of slots to be added. -            send_quota: initial send quota for slots. -        """ - -        self._channel_slots.extend([send_quota] * slots) -        # Send NewChannelSlot to client. -        frame_data = _create_new_channel_slot(slots, send_quota) -        self.send_control_data(frame_data) - -    def wait_until_done(self, timeout=None): -        """Waits until all workers are done. Returns False when timeout has -        occurred. Returns True on success. - -        Args: -            timeout: timeout in sec. -        """ - -        self._logical_channels_condition.acquire() -        try: -            while len(self._logical_channels) > 0: -                self._logger.debug('Waiting workers(%d)...' % -                                   len(self._logical_channels)) -                self._worker_done_notify_received = False -                self._logical_channels_condition.wait(timeout) -                if not self._worker_done_notify_received: -                    self._logger.debug('Waiting worker(s) timed out') -                    return False - -        finally: -            self._logical_channels_condition.release() - -        # Flush pending outgoing data -        self._writer.stop() -        self._writer.join() - -        return True - -    def notify_write_done(self, channel_id): -        """Called by the writer thread when a write operation has done. - -        Args: -            channel_id: objective channel id. -        """ - -        try: -            self._logical_channels_condition.acquire() -            if channel_id in self._logical_channels: -                channel_data = self._logical_channels[channel_id] -                channel_data.request.connection.notify_write_done() -            else: -                self._logger.debug('Seems that logical channel for %d has gone' -                                   % channel_id) -        finally: -            self._logical_channels_condition.release() - -    def send_control_data(self, data): -        """Sends data via the control channel. - -        Args: -            data: data to be sent. -        """ - -        self._writer.put_outgoing_data(_OutgoingData( -                channel_id=_CONTROL_CHANNEL_ID, data=data)) - -    def send_data(self, channel_id, data): -        """Sends data via given logical channel. This method is called by -        worker threads. - -        Args: -            channel_id: objective channel id. -            data: data to be sent. -        """ - -        self._writer.put_outgoing_data(_OutgoingData( -                channel_id=channel_id, data=data)) - -    def _send_drop_channel(self, channel_id, code=None, message=''): -        frame_data = _create_drop_channel(channel_id, code, message) -        self._logger.debug( -            'Sending drop channel for channel id %d' % channel_id) -        self.send_control_data(frame_data) - -    def _send_error_add_channel_response(self, channel_id, status=None): -        if status is None: -            status = common.HTTP_STATUS_BAD_REQUEST - -        if status in _HTTP_BAD_RESPONSE_MESSAGES: -            message = _HTTP_BAD_RESPONSE_MESSAGES[status] -        else: -            self._logger.debug('Response message for %d is not found' % status) -            message = '???' - -        response = 'HTTP/1.1 %d %s\r\n\r\n' % (status, message) -        frame_data = _create_add_channel_response(channel_id, -                                                  encoded_handshake=response, -                                                  encoding=0, rejected=True) -        self.send_control_data(frame_data) - -    def _create_logical_request(self, block): -        if block.channel_id == _CONTROL_CHANNEL_ID: -            # TODO(bashi): Raise PhysicalConnectionError with code 2006 -            # instead of MuxUnexpectedException. -            raise MuxUnexpectedException( -                'Received the control channel id (0) as objective channel ' -                'id for AddChannel') - -        if block.encoding > _HANDSHAKE_ENCODING_DELTA: -            raise PhysicalConnectionError( -                _DROP_CODE_UNKNOWN_REQUEST_ENCODING) - -        method, path, version, headers = _parse_request_text( -            block.encoded_handshake) -        if block.encoding == _HANDSHAKE_ENCODING_DELTA: -            headers = self._handshake_base.create_headers(headers) - -        connection = _LogicalConnection(self, block.channel_id) -        request = _LogicalRequest(block.channel_id, method, path, version, -                                  headers, connection) -        return request - -    def _do_handshake_for_logical_request(self, request, send_quota=0): -        try: -            receive_quota = self._channel_slots.popleft() -        except IndexError: -            raise LogicalChannelError( -                request.channel_id, _DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION) - -        handshaker = _MuxHandshaker(request, self.dispatcher, -                                    send_quota, receive_quota) -        try: -            handshaker.do_handshake() -        except handshake.VersionException, e: -            self._logger.info('%s', e) -            self._send_error_add_channel_response( -                request.channel_id, status=common.HTTP_STATUS_BAD_REQUEST) -            return False -        except handshake.HandshakeException, e: -            # TODO(bashi): Should we _Fail the Logical Channel_ with 3001 -            # instead? -            self._logger.info('%s', e) -            self._send_error_add_channel_response(request.channel_id, -                                                  status=e.status) -            return False -        except handshake.AbortedByUserException, e: -            self._logger.info('%s', e) -            self._send_error_add_channel_response(request.channel_id) -            return False - -        return True - -    def _add_logical_channel(self, logical_request): -        try: -            self._logical_channels_condition.acquire() -            if logical_request.channel_id in self._logical_channels: -                self._logger.debug('Channel id %d already exists' % -                                   logical_request.channel_id) -                raise PhysicalConnectionError( -                    _DROP_CODE_CHANNEL_ALREADY_EXISTS, -                    'Channel id %d already exists' % -                    logical_request.channel_id) -            worker = _Worker(self, logical_request) -            channel_data = _LogicalChannelData(logical_request, worker) -            self._logical_channels[logical_request.channel_id] = channel_data -            worker.start() -        finally: -            self._logical_channels_condition.release() - -    def _process_add_channel_request(self, block): -        try: -            logical_request = self._create_logical_request(block) -        except ValueError, e: -            self._logger.debug('Failed to create logical request: %r' % e) -            self._send_error_add_channel_response( -                block.channel_id, status=common.HTTP_STATUS_BAD_REQUEST) -            return -        if self._do_handshake_for_logical_request(logical_request): -            if block.encoding == _HANDSHAKE_ENCODING_IDENTITY: -                # Update handshake base. -                # TODO(bashi): Make sure this is the right place to update -                # handshake base. -                self._handshake_base = _HandshakeDeltaBase( -                    logical_request.headers_in) -            self._add_logical_channel(logical_request) -        else: -            self._send_error_add_channel_response( -                block.channel_id, status=common.HTTP_STATUS_BAD_REQUEST) - -    def _process_flow_control(self, block): -        try: -            self._logical_channels_condition.acquire() -            if not block.channel_id in self._logical_channels: -                return -            channel_data = self._logical_channels[block.channel_id] -            channel_data.request.ws_stream.replenish_send_quota( -                block.send_quota) -        finally: -            self._logical_channels_condition.release() - -    def _process_drop_channel(self, block): -        self._logger.debug( -            'DropChannel received for %d: code=%r, reason=%r' % -            (block.channel_id, block.drop_code, block.drop_message)) -        try: -            self._logical_channels_condition.acquire() -            if not block.channel_id in self._logical_channels: -                return -            channel_data = self._logical_channels[block.channel_id] -            channel_data.drop_code = _DROP_CODE_ACKNOWLEDGED -            # Close the logical channel -            channel_data.request.connection.set_read_state( -                _LogicalConnection.STATE_TERMINATED) -        finally: -            self._logical_channels_condition.release() - -    def _process_control_blocks(self, parser): -        for control_block in parser.read_control_blocks(): -            opcode = control_block.opcode -            self._logger.debug('control block received, opcode: %d' % opcode) -            if opcode == _MUX_OPCODE_ADD_CHANNEL_REQUEST: -                self._process_add_channel_request(control_block) -            elif opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE: -                raise PhysicalConnectionError( -                    _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                    'Received AddChannelResponse') -            elif opcode == _MUX_OPCODE_FLOW_CONTROL: -                self._process_flow_control(control_block) -            elif opcode == _MUX_OPCODE_DROP_CHANNEL: -                self._process_drop_channel(control_block) -            elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT: -                raise PhysicalConnectionError( -                    _DROP_CODE_INVALID_MUX_CONTROL_BLOCK, -                    'Received NewChannelSlot') -            else: -                raise MuxUnexpectedException( -                    'Unexpected opcode %r' % opcode) - -    def _process_logical_frame(self, channel_id, parser): -        self._logger.debug('Received a frame. channel id=%d' % channel_id) -        try: -            self._logical_channels_condition.acquire() -            if not channel_id in self._logical_channels: -                # We must ignore the message for an inactive channel. -                return -            channel_data = self._logical_channels[channel_id] -            fin, rsv1, rsv2, rsv3, opcode, payload = parser.read_inner_frame() -            if not channel_data.request.ws_stream.consume_receive_quota( -                len(payload)): -                # The client violates quota. Close logical channel. -                raise LogicalChannelError( -                    channel_id, _DROP_CODE_SEND_QUOTA_VIOLATION) -            header = create_header(opcode, len(payload), fin, rsv1, rsv2, rsv3, -                                   mask=False) -            frame_data = header + payload -            channel_data.request.connection.append_frame_data(frame_data) -        finally: -            self._logical_channels_condition.release() - -    def dispatch_message(self, message): -        """Dispatches message. The reader thread calls this method. - -        Args: -            message: a message that contains encapsulated frame. -        Raises: -            PhysicalConnectionError: if the message contains physical -                connection level errors. -            LogicalChannelError: if the message contains logical channel -                level errors. -        """ - -        parser = _MuxFramePayloadParser(message) -        try: -            channel_id = parser.read_channel_id() -        except ValueError, e: -            raise PhysicalConnectionError(_DROP_CODE_CHANNEL_ID_TRUNCATED) -        if channel_id == _CONTROL_CHANNEL_ID: -            self._process_control_blocks(parser) -        else: -            self._process_logical_frame(channel_id, parser) - -    def notify_worker_done(self, channel_id): -        """Called when a worker has finished. - -        Args: -            channel_id: channel id corresponded with the worker. -        """ - -        self._logger.debug('Worker for channel id %d terminated' % channel_id) -        try: -            self._logical_channels_condition.acquire() -            if not channel_id in self._logical_channels: -                raise MuxUnexpectedException( -                    'Channel id %d not found' % channel_id) -            channel_data = self._logical_channels.pop(channel_id) -        finally: -            self._worker_done_notify_received = True -            self._logical_channels_condition.notify() -            self._logical_channels_condition.release() - -        if not channel_data.request.server_terminated: -            self._send_drop_channel( -                channel_id, code=channel_data.drop_code, -                message=channel_data.drop_message) - -    def notify_reader_done(self): -        """This method is called by the reader thread when the reader has -        finished. -        """ - -        # Terminate all logical connections -        self._logger.debug('termiating all logical connections...') -        self._logical_channels_condition.acquire() -        for channel_data in self._logical_channels.values(): -            try: -                channel_data.request.connection.set_read_state( -                    _LogicalConnection.STATE_TERMINATED) -            except Exception: -                pass -        self._logical_channels_condition.release() - -    def fail_physical_connection(self, code, message): -        """Fail the physical connection. - -        Args: -            code: drop reason code. -            message: drop message. -        """ - -        self._logger.debug('Failing the physical connection...') -        self._send_drop_channel(_CONTROL_CHANNEL_ID, code, message) -        self.physical_stream.close_connection( -            common.STATUS_INTERNAL_ENDPOINT_ERROR) - -    def fail_logical_channel(self, channel_id, code, message): -        """Fail a logical channel. - -        Args: -            channel_id: channel id. -            code: drop reason code. -            message: drop message. -        """ - -        self._logger.debug('Failing logical channel %d...' % channel_id) -        try: -            self._logical_channels_condition.acquire() -            if channel_id in self._logical_channels: -                channel_data = self._logical_channels[channel_id] -                # Close the logical channel. notify_worker_done() will be -                # called later and it will send DropChannel. -                channel_data.drop_code = code -                channel_data.drop_message = message -                channel_data.request.connection.set_read_state( -                    _LogicalConnection.STATE_TERMINATED) -            else: -                self._send_drop_channel(channel_id, code, message) -        finally: -            self._logical_channels_condition.release() - - -def use_mux(request): -    return hasattr(request, 'mux') and request.mux - - -def start(request, dispatcher): -    mux_handler = _MuxHandler(request, dispatcher) -    mux_handler.start() - -    mux_handler.add_channel_slots(_INITIAL_NUMBER_OF_CHANNEL_SLOTS, -                                  _INITIAL_QUOTA_FOR_CLIENT) - -    mux_handler.wait_until_done() - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/standalone.py b/module/lib/mod_pywebsocket/standalone.py deleted file mode 100755 index 07a33d9c9..000000000 --- a/module/lib/mod_pywebsocket/standalone.py +++ /dev/null @@ -1,998 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2012, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""Standalone WebSocket server. - -Use this file to launch pywebsocket without Apache HTTP Server. - - -BASIC USAGE - -Go to the src directory and run - -  $ python mod_pywebsocket/standalone.py [-p <ws_port>] -                                         [-w <websock_handlers>] -                                         [-d <document_root>] - -<ws_port> is the port number to use for ws:// connection. - -<document_root> is the path to the root directory of HTML files. - -<websock_handlers> is the path to the root directory of WebSocket handlers. -If not specified, <document_root> will be used. See __init__.py (or -run $ pydoc mod_pywebsocket) for how to write WebSocket handlers. - -For more detail and other options, run - -  $ python mod_pywebsocket/standalone.py --help - -or see _build_option_parser method below. - -For trouble shooting, adding "--log_level debug" might help you. - - -TRY DEMO - -Go to the src directory and run - -  $ python standalone.py -d example - -to launch pywebsocket with the sample handler and html on port 80. Open -http://localhost/console.html, click the connect button, type something into -the text box next to the send button and click the send button. If everything -is working, you'll see the message you typed echoed by the server. - - -SUPPORTING TLS - -To support TLS, run standalone.py with -t, -k, and -c options. - - -SUPPORTING CLIENT AUTHENTICATION - -To support client authentication with TLS, run standalone.py with -t, -k, -c, -and --tls-client-auth, and --tls-client-ca options. - -E.g., $./standalone.py -d ../example -p 10443 -t -c ../test/cert/cert.pem -k -../test/cert/key.pem --tls-client-auth --tls-client-ca=../test/cert/cacert.pem - - -CONFIGURATION FILE - -You can also write a configuration file and use it by specifying the path to -the configuration file by --config option. Please write a configuration file -following the documentation of the Python ConfigParser library. Name of each -entry must be the long version argument name. E.g. to set log level to debug, -add the following line: - -log_level=debug - -For options which doesn't take value, please add some fake value. E.g. for ---tls option, add the following line: - -tls=True - -Note that tls will be enabled even if you write tls=False as the value part is -fake. - -When both a command line argument and a configuration file entry are set for -the same configuration item, the command line value will override one in the -configuration file. - - -THREADING - -This server is derived from SocketServer.ThreadingMixIn. Hence a thread is -used for each request. - - -SECURITY WARNING - -This uses CGIHTTPServer and CGIHTTPServer is not secure. -It may execute arbitrary Python code or external programs. It should not be -used outside a firewall. -""" - -import BaseHTTPServer -import CGIHTTPServer -import SimpleHTTPServer -import SocketServer -import ConfigParser -import base64 -import httplib -import logging -import logging.handlers -import optparse -import os -import re -import select -import socket -import sys -import threading -import time - -_HAS_SSL = False -_HAS_OPEN_SSL = False -try: -    import ssl -    _HAS_SSL = True -except ImportError: -    try: -        import OpenSSL.SSL -        _HAS_OPEN_SSL = True -    except ImportError: -        pass - -from mod_pywebsocket import common -from mod_pywebsocket import dispatch -from mod_pywebsocket import handshake -from mod_pywebsocket import http_header_util -from mod_pywebsocket import memorizingfile -from mod_pywebsocket import util - - -_DEFAULT_LOG_MAX_BYTES = 1024 * 256 -_DEFAULT_LOG_BACKUP_COUNT = 5 - -_DEFAULT_REQUEST_QUEUE_SIZE = 128 - -# 1024 is practically large enough to contain WebSocket handshake lines. -_MAX_MEMORIZED_LINES = 1024 - - -class _StandaloneConnection(object): -    """Mimic mod_python mp_conn.""" - -    def __init__(self, request_handler): -        """Construct an instance. - -        Args: -            request_handler: A WebSocketRequestHandler instance. -        """ - -        self._request_handler = request_handler - -    def get_local_addr(self): -        """Getter to mimic mp_conn.local_addr.""" - -        return (self._request_handler.server.server_name, -                self._request_handler.server.server_port) -    local_addr = property(get_local_addr) - -    def get_remote_addr(self): -        """Getter to mimic mp_conn.remote_addr. - -        Setting the property in __init__ won't work because the request -        handler is not initialized yet there.""" - -        return self._request_handler.client_address -    remote_addr = property(get_remote_addr) - -    def write(self, data): -        """Mimic mp_conn.write().""" - -        return self._request_handler.wfile.write(data) - -    def read(self, length): -        """Mimic mp_conn.read().""" - -        return self._request_handler.rfile.read(length) - -    def get_memorized_lines(self): -        """Get memorized lines.""" - -        return self._request_handler.rfile.get_memorized_lines() - - -class _StandaloneRequest(object): -    """Mimic mod_python request.""" - -    def __init__(self, request_handler, use_tls): -        """Construct an instance. - -        Args: -            request_handler: A WebSocketRequestHandler instance. -        """ - -        self._logger = util.get_class_logger(self) - -        self._request_handler = request_handler -        self.connection = _StandaloneConnection(request_handler) -        self._use_tls = use_tls -        self.headers_in = request_handler.headers - -    def get_uri(self): -        """Getter to mimic request.uri.""" - -        return self._request_handler.path -    uri = property(get_uri) - -    def get_method(self): -        """Getter to mimic request.method.""" - -        return self._request_handler.command -    method = property(get_method) - -    def get_protocol(self): -        """Getter to mimic request.protocol.""" - -        return self._request_handler.request_version -    protocol = property(get_protocol) - -    def is_https(self): -        """Mimic request.is_https().""" - -        return self._use_tls - -    def _drain_received_data(self): -        """Don't use this method from WebSocket handler. Drains unread data -        in the receive buffer. -        """ - -        raw_socket = self._request_handler.connection -        drained_data = util.drain_received_data(raw_socket) - -        if drained_data: -            self._logger.debug( -                'Drained data following close frame: %r', drained_data) - - -class _StandaloneSSLConnection(object): -    """A wrapper class for OpenSSL.SSL.Connection to provide makefile method -    which is not supported by the class. -    """ - -    def __init__(self, connection): -        self._connection = connection - -    def __getattribute__(self, name): -        if name in ('_connection', 'makefile'): -            return object.__getattribute__(self, name) -        return self._connection.__getattribute__(name) - -    def __setattr__(self, name, value): -        if name in ('_connection', 'makefile'): -            return object.__setattr__(self, name, value) -        return self._connection.__setattr__(name, value) - -    def makefile(self, mode='r', bufsize=-1): -        return socket._fileobject(self._connection, mode, bufsize) - - -def _alias_handlers(dispatcher, websock_handlers_map_file): -    """Set aliases specified in websock_handler_map_file in dispatcher. - -    Args: -        dispatcher: dispatch.Dispatcher instance -        websock_handler_map_file: alias map file -    """ - -    fp = open(websock_handlers_map_file) -    try: -        for line in fp: -            if line[0] == '#' or line.isspace(): -                continue -            m = re.match('(\S+)\s+(\S+)', line) -            if not m: -                logging.warning('Wrong format in map file:' + line) -                continue -            try: -                dispatcher.add_resource_path_alias( -                    m.group(1), m.group(2)) -            except dispatch.DispatchException, e: -                logging.error(str(e)) -    finally: -        fp.close() - - -class WebSocketServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): -    """HTTPServer specialized for WebSocket.""" - -    # Overrides SocketServer.ThreadingMixIn.daemon_threads -    daemon_threads = True -    # Overrides BaseHTTPServer.HTTPServer.allow_reuse_address -    allow_reuse_address = True - -    def __init__(self, options): -        """Override SocketServer.TCPServer.__init__ to set SSL enabled -        socket object to self.socket before server_bind and server_activate, -        if necessary. -        """ - -        # Share a Dispatcher among request handlers to save time for -        # instantiation.  Dispatcher can be shared because it is thread-safe. -        options.dispatcher = dispatch.Dispatcher( -            options.websock_handlers, -            options.scan_dir, -            options.allow_handlers_outside_root_dir) -        if options.websock_handlers_map_file: -            _alias_handlers(options.dispatcher, -                            options.websock_handlers_map_file) -        warnings = options.dispatcher.source_warnings() -        if warnings: -            for warning in warnings: -                logging.warning('mod_pywebsocket: %s' % warning) - -        self._logger = util.get_class_logger(self) - -        self.request_queue_size = options.request_queue_size -        self.__ws_is_shut_down = threading.Event() -        self.__ws_serving = False - -        SocketServer.BaseServer.__init__( -            self, (options.server_host, options.port), WebSocketRequestHandler) - -        # Expose the options object to allow handler objects access it. We name -        # it with websocket_ prefix to avoid conflict. -        self.websocket_server_options = options - -        self._create_sockets() -        self.server_bind() -        self.server_activate() - -    def _create_sockets(self): -        self.server_name, self.server_port = self.server_address -        self._sockets = [] -        if not self.server_name: -            # On platforms that doesn't support IPv6, the first bind fails. -            # On platforms that supports IPv6 -            # - If it binds both IPv4 and IPv6 on call with AF_INET6, the -            #   first bind succeeds and the second fails (we'll see 'Address -            #   already in use' error). -            # - If it binds only IPv6 on call with AF_INET6, both call are -            #   expected to succeed to listen both protocol. -            addrinfo_array = [ -                (socket.AF_INET6, socket.SOCK_STREAM, '', '', ''), -                (socket.AF_INET, socket.SOCK_STREAM, '', '', '')] -        else: -            addrinfo_array = socket.getaddrinfo(self.server_name, -                                                self.server_port, -                                                socket.AF_UNSPEC, -                                                socket.SOCK_STREAM, -                                                socket.IPPROTO_TCP) -        for addrinfo in addrinfo_array: -            self._logger.info('Create socket on: %r', addrinfo) -            family, socktype, proto, canonname, sockaddr = addrinfo -            try: -                socket_ = socket.socket(family, socktype) -            except Exception, e: -                self._logger.info('Skip by failure: %r', e) -                continue -            if self.websocket_server_options.use_tls: -                if _HAS_SSL: -                    if self.websocket_server_options.tls_client_auth: -                        client_cert_ = ssl.CERT_REQUIRED -                    else: -                        client_cert_ = ssl.CERT_NONE -                    socket_ = ssl.wrap_socket(socket_, -                        keyfile=self.websocket_server_options.private_key, -                        certfile=self.websocket_server_options.certificate, -                        ssl_version=ssl.PROTOCOL_SSLv23, -                        ca_certs=self.websocket_server_options.tls_client_ca, -                        cert_reqs=client_cert_) -                if _HAS_OPEN_SSL: -                    ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) -                    ctx.use_privatekey_file( -                        self.websocket_server_options.private_key) -                    ctx.use_certificate_file( -                        self.websocket_server_options.certificate) -                    socket_ = OpenSSL.SSL.Connection(ctx, socket_) -            self._sockets.append((socket_, addrinfo)) - -    def server_bind(self): -        """Override SocketServer.TCPServer.server_bind to enable multiple -        sockets bind. -        """ - -        failed_sockets = [] - -        for socketinfo in self._sockets: -            socket_, addrinfo = socketinfo -            self._logger.info('Bind on: %r', addrinfo) -            if self.allow_reuse_address: -                socket_.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -            try: -                socket_.bind(self.server_address) -            except Exception, e: -                self._logger.info('Skip by failure: %r', e) -                socket_.close() -                failed_sockets.append(socketinfo) -            if self.server_address[1] == 0: -                # The operating system assigns the actual port number for port -                # number 0. This case, the second and later sockets should use -                # the same port number. Also self.server_port is rewritten -                # because it is exported, and will be used by external code. -                self.server_address = ( -                    self.server_name, socket_.getsockname()[1]) -                self.server_port = self.server_address[1] -                self._logger.info('Port %r is assigned', self.server_port) - -        for socketinfo in failed_sockets: -            self._sockets.remove(socketinfo) - -    def server_activate(self): -        """Override SocketServer.TCPServer.server_activate to enable multiple -        sockets listen. -        """ - -        failed_sockets = [] - -        for socketinfo in self._sockets: -            socket_, addrinfo = socketinfo -            self._logger.info('Listen on: %r', addrinfo) -            try: -                socket_.listen(self.request_queue_size) -            except Exception, e: -                self._logger.info('Skip by failure: %r', e) -                socket_.close() -                failed_sockets.append(socketinfo) - -        for socketinfo in failed_sockets: -            self._sockets.remove(socketinfo) - -        if len(self._sockets) == 0: -            self._logger.critical( -                'No sockets activated. Use info log level to see the reason.') - -    def server_close(self): -        """Override SocketServer.TCPServer.server_close to enable multiple -        sockets close. -        """ - -        for socketinfo in self._sockets: -            socket_, addrinfo = socketinfo -            self._logger.info('Close on: %r', addrinfo) -            socket_.close() - -    def fileno(self): -        """Override SocketServer.TCPServer.fileno.""" - -        self._logger.critical('Not supported: fileno') -        return self._sockets[0][0].fileno() - -    def handle_error(self, rquest, client_address): -        """Override SocketServer.handle_error.""" - -        self._logger.error( -            'Exception in processing request from: %r\n%s', -            client_address, -            util.get_stack_trace()) -        # Note: client_address is a tuple. - -    def get_request(self): -        """Override TCPServer.get_request to wrap OpenSSL.SSL.Connection -        object with _StandaloneSSLConnection to provide makefile method. We -        cannot substitute OpenSSL.SSL.Connection.makefile since it's readonly -        attribute. -        """ - -        accepted_socket, client_address = self.socket.accept() -        if self.websocket_server_options.use_tls and _HAS_OPEN_SSL: -            accepted_socket = _StandaloneSSLConnection(accepted_socket) -        return accepted_socket, client_address - -    def serve_forever(self, poll_interval=0.5): -        """Override SocketServer.BaseServer.serve_forever.""" - -        self.__ws_serving = True -        self.__ws_is_shut_down.clear() -        handle_request = self.handle_request -        if hasattr(self, '_handle_request_noblock'): -            handle_request = self._handle_request_noblock -        else: -            self._logger.warning('Fallback to blocking request handler') -        try: -            while self.__ws_serving: -                r, w, e = select.select( -                    [socket_[0] for socket_ in self._sockets], -                    [], [], poll_interval) -                for socket_ in r: -                    self.socket = socket_ -                    handle_request() -                self.socket = None -        finally: -            self.__ws_is_shut_down.set() - -    def shutdown(self): -        """Override SocketServer.BaseServer.shutdown.""" - -        self.__ws_serving = False -        self.__ws_is_shut_down.wait() - - -class WebSocketRequestHandler(CGIHTTPServer.CGIHTTPRequestHandler): -    """CGIHTTPRequestHandler specialized for WebSocket.""" - -    # Use httplib.HTTPMessage instead of mimetools.Message. -    MessageClass = httplib.HTTPMessage - -    def setup(self): -        """Override SocketServer.StreamRequestHandler.setup to wrap rfile -        with MemorizingFile. - -        This method will be called by BaseRequestHandler's constructor -        before calling BaseHTTPRequestHandler.handle. -        BaseHTTPRequestHandler.handle will call -        BaseHTTPRequestHandler.handle_one_request and it will call -        WebSocketRequestHandler.parse_request. -        """ - -        # Call superclass's setup to prepare rfile, wfile, etc. See setup -        # definition on the root class SocketServer.StreamRequestHandler to -        # understand what this does. -        CGIHTTPServer.CGIHTTPRequestHandler.setup(self) - -        self.rfile = memorizingfile.MemorizingFile( -            self.rfile, -            max_memorized_lines=_MAX_MEMORIZED_LINES) - -    def __init__(self, request, client_address, server): -        self._logger = util.get_class_logger(self) - -        self._options = server.websocket_server_options - -        # Overrides CGIHTTPServerRequestHandler.cgi_directories. -        self.cgi_directories = self._options.cgi_directories -        # Replace CGIHTTPRequestHandler.is_executable method. -        if self._options.is_executable_method is not None: -            self.is_executable = self._options.is_executable_method - -        # This actually calls BaseRequestHandler.__init__. -        CGIHTTPServer.CGIHTTPRequestHandler.__init__( -            self, request, client_address, server) - -    def parse_request(self): -        """Override BaseHTTPServer.BaseHTTPRequestHandler.parse_request. - -        Return True to continue processing for HTTP(S), False otherwise. - -        See BaseHTTPRequestHandler.handle_one_request method which calls -        this method to understand how the return value will be handled. -        """ - -        # We hook parse_request method, but also call the original -        # CGIHTTPRequestHandler.parse_request since when we return False, -        # CGIHTTPRequestHandler.handle_one_request continues processing and -        # it needs variables set by CGIHTTPRequestHandler.parse_request. -        # -        # Variables set by this method will be also used by WebSocket request -        # handling (self.path, self.command, self.requestline, etc. See also -        # how _StandaloneRequest's members are implemented using these -        # attributes). -        if not CGIHTTPServer.CGIHTTPRequestHandler.parse_request(self): -            return False - -        if self._options.use_basic_auth: -            auth = self.headers.getheader('Authorization') -            if auth != self._options.basic_auth_credential: -                self.send_response(401) -                self.send_header('WWW-Authenticate', -                                 'Basic realm="Pywebsocket"') -                self.end_headers() -                self._logger.info('Request basic authentication') -                return True - -        host, port, resource = http_header_util.parse_uri(self.path) -        if resource is None: -            self._logger.info('Invalid URI: %r', self.path) -            self._logger.info('Fallback to CGIHTTPRequestHandler') -            return True -        server_options = self.server.websocket_server_options -        if host is not None: -            validation_host = server_options.validation_host -            if validation_host is not None and host != validation_host: -                self._logger.info('Invalid host: %r (expected: %r)', -                                  host, -                                  validation_host) -                self._logger.info('Fallback to CGIHTTPRequestHandler') -                return True -        if port is not None: -            validation_port = server_options.validation_port -            if validation_port is not None and port != validation_port: -                self._logger.info('Invalid port: %r (expected: %r)', -                                  port, -                                  validation_port) -                self._logger.info('Fallback to CGIHTTPRequestHandler') -                return True -        self.path = resource - -        request = _StandaloneRequest(self, self._options.use_tls) - -        try: -            # Fallback to default http handler for request paths for which -            # we don't have request handlers. -            if not self._options.dispatcher.get_handler_suite(self.path): -                self._logger.info('No handler for resource: %r', -                                  self.path) -                self._logger.info('Fallback to CGIHTTPRequestHandler') -                return True -        except dispatch.DispatchException, e: -            self._logger.info('%s', e) -            self.send_error(e.status) -            return False - -        # If any Exceptions without except clause setup (including -        # DispatchException) is raised below this point, it will be caught -        # and logged by WebSocketServer. - -        try: -            try: -                handshake.do_handshake( -                    request, -                    self._options.dispatcher, -                    allowDraft75=self._options.allow_draft75, -                    strict=self._options.strict) -            except handshake.VersionException, e: -                self._logger.info('%s', e) -                self.send_response(common.HTTP_STATUS_BAD_REQUEST) -                self.send_header(common.SEC_WEBSOCKET_VERSION_HEADER, -                                 e.supported_versions) -                self.end_headers() -                return False -            except handshake.HandshakeException, e: -                # Handshake for ws(s) failed. -                self._logger.info('%s', e) -                self.send_error(e.status) -                return False - -            request._dispatcher = self._options.dispatcher -            self._options.dispatcher.transfer_data(request) -        except handshake.AbortedByUserException, e: -            self._logger.info('%s', e) -        return False - -    def log_request(self, code='-', size='-'): -        """Override BaseHTTPServer.log_request.""" - -        self._logger.info('"%s" %s %s', -                          self.requestline, str(code), str(size)) - -    def log_error(self, *args): -        """Override BaseHTTPServer.log_error.""" - -        # Despite the name, this method is for warnings than for errors. -        # For example, HTTP status code is logged by this method. -        self._logger.warning('%s - %s', -                             self.address_string(), -                             args[0] % args[1:]) - -    def is_cgi(self): -        """Test whether self.path corresponds to a CGI script. - -        Add extra check that self.path doesn't contains .. -        Also check if the file is a executable file or not. -        If the file is not executable, it is handled as static file or dir -        rather than a CGI script. -        """ - -        if CGIHTTPServer.CGIHTTPRequestHandler.is_cgi(self): -            if '..' in self.path: -                return False -            # strip query parameter from request path -            resource_name = self.path.split('?', 2)[0] -            # convert resource_name into real path name in filesystem. -            scriptfile = self.translate_path(resource_name) -            if not os.path.isfile(scriptfile): -                return False -            if not self.is_executable(scriptfile): -                return False -            return True -        return False - - -def _get_logger_from_class(c): -    return logging.getLogger('%s.%s' % (c.__module__, c.__name__)) - - -def _configure_logging(options): -    logging.addLevelName(common.LOGLEVEL_FINE, 'FINE') - -    logger = logging.getLogger() -    logger.setLevel(logging.getLevelName(options.log_level.upper())) -    if options.log_file: -        handler = logging.handlers.RotatingFileHandler( -                options.log_file, 'a', options.log_max, options.log_count) -    else: -        handler = logging.StreamHandler() -    formatter = logging.Formatter( -            '[%(asctime)s] [%(levelname)s] %(name)s: %(message)s') -    handler.setFormatter(formatter) -    logger.addHandler(handler) - -    deflate_log_level_name = logging.getLevelName( -        options.deflate_log_level.upper()) -    _get_logger_from_class(util._Deflater).setLevel( -        deflate_log_level_name) -    _get_logger_from_class(util._Inflater).setLevel( -        deflate_log_level_name) - - -def _build_option_parser(): -    parser = optparse.OptionParser() - -    parser.add_option('--config', dest='config_file', type='string', -                      default=None, -                      help=('Path to configuration file. See the file comment ' -                            'at the top of this file for the configuration ' -                            'file format')) -    parser.add_option('-H', '--server-host', '--server_host', -                      dest='server_host', -                      default='', -                      help='server hostname to listen to') -    parser.add_option('-V', '--validation-host', '--validation_host', -                      dest='validation_host', -                      default=None, -                      help='server hostname to validate in absolute path.') -    parser.add_option('-p', '--port', dest='port', type='int', -                      default=common.DEFAULT_WEB_SOCKET_PORT, -                      help='port to listen to') -    parser.add_option('-P', '--validation-port', '--validation_port', -                      dest='validation_port', type='int', -                      default=None, -                      help='server port to validate in absolute path.') -    parser.add_option('-w', '--websock-handlers', '--websock_handlers', -                      dest='websock_handlers', -                      default='.', -                      help=('The root directory of WebSocket handler files. ' -                            'If the path is relative, --document-root is used ' -                            'as the base.')) -    parser.add_option('-m', '--websock-handlers-map-file', -                      '--websock_handlers_map_file', -                      dest='websock_handlers_map_file', -                      default=None, -                      help=('WebSocket handlers map file. ' -                            'Each line consists of alias_resource_path and ' -                            'existing_resource_path, separated by spaces.')) -    parser.add_option('-s', '--scan-dir', '--scan_dir', dest='scan_dir', -                      default=None, -                      help=('Must be a directory under --websock-handlers. ' -                            'Only handlers under this directory are scanned ' -                            'and registered to the server. ' -                            'Useful for saving scan time when the handler ' -                            'root directory contains lots of files that are ' -                            'not handler file or are handler files but you ' -                            'don\'t want them to be registered. ')) -    parser.add_option('--allow-handlers-outside-root-dir', -                      '--allow_handlers_outside_root_dir', -                      dest='allow_handlers_outside_root_dir', -                      action='store_true', -                      default=False, -                      help=('Scans WebSocket handlers even if their canonical ' -                            'path is not under --websock-handlers.')) -    parser.add_option('-d', '--document-root', '--document_root', -                      dest='document_root', default='.', -                      help='Document root directory.') -    parser.add_option('-x', '--cgi-paths', '--cgi_paths', dest='cgi_paths', -                      default=None, -                      help=('CGI paths relative to document_root.' -                            'Comma-separated. (e.g -x /cgi,/htbin) ' -                            'Files under document_root/cgi_path are handled ' -                            'as CGI programs. Must be executable.')) -    parser.add_option('-t', '--tls', dest='use_tls', action='store_true', -                      default=False, help='use TLS (wss://)') -    parser.add_option('-k', '--private-key', '--private_key', -                      dest='private_key', -                      default='', help='TLS private key file.') -    parser.add_option('-c', '--certificate', dest='certificate', -                      default='', help='TLS certificate file.') -    parser.add_option('--tls-client-auth', dest='tls_client_auth', -                      action='store_true', default=False, -                      help='Requires TLS client auth on every connection.') -    parser.add_option('--tls-client-ca', dest='tls_client_ca', default='', -                      help=('Specifies a pem file which contains a set of ' -                            'concatenated CA certificates which are used to ' -                            'validate certificates passed from clients')) -    parser.add_option('--basic-auth', dest='use_basic_auth', -                      action='store_true', default=False, -                      help='Requires Basic authentication.') -    parser.add_option('--basic-auth-credential', -                      dest='basic_auth_credential', default='test:test', -                      help='Specifies the credential of basic authentication ' -                      'by username:password pair (e.g. test:test).') -    parser.add_option('-l', '--log-file', '--log_file', dest='log_file', -                      default='', help='Log file.') -    # Custom log level: -    # - FINE: Prints status of each frame processing step -    parser.add_option('--log-level', '--log_level', type='choice', -                      dest='log_level', default='warn', -                      choices=['fine', -                               'debug', 'info', 'warning', 'warn', 'error', -                               'critical'], -                      help='Log level.') -    parser.add_option('--deflate-log-level', '--deflate_log_level', -                      type='choice', -                      dest='deflate_log_level', default='warn', -                      choices=['debug', 'info', 'warning', 'warn', 'error', -                               'critical'], -                      help='Log level for _Deflater and _Inflater.') -    parser.add_option('--thread-monitor-interval-in-sec', -                      '--thread_monitor_interval_in_sec', -                      dest='thread_monitor_interval_in_sec', -                      type='int', default=-1, -                      help=('If positive integer is specified, run a thread ' -                            'monitor to show the status of server threads ' -                            'periodically in the specified inteval in ' -                            'second. If non-positive integer is specified, ' -                            'disable the thread monitor.')) -    parser.add_option('--log-max', '--log_max', dest='log_max', type='int', -                      default=_DEFAULT_LOG_MAX_BYTES, -                      help='Log maximum bytes') -    parser.add_option('--log-count', '--log_count', dest='log_count', -                      type='int', default=_DEFAULT_LOG_BACKUP_COUNT, -                      help='Log backup count') -    parser.add_option('--allow-draft75', dest='allow_draft75', -                      action='store_true', default=False, -                      help='Obsolete option. Ignored.') -    parser.add_option('--strict', dest='strict', action='store_true', -                      default=False, help='Obsolete option. Ignored.') -    parser.add_option('-q', '--queue', dest='request_queue_size', type='int', -                      default=_DEFAULT_REQUEST_QUEUE_SIZE, -                      help='request queue size') - -    return parser - - -class ThreadMonitor(threading.Thread): -    daemon = True - -    def __init__(self, interval_in_sec): -        threading.Thread.__init__(self, name='ThreadMonitor') - -        self._logger = util.get_class_logger(self) - -        self._interval_in_sec = interval_in_sec - -    def run(self): -        while True: -            thread_name_list = [] -            for thread in threading.enumerate(): -                thread_name_list.append(thread.name) -            self._logger.info( -                "%d active threads: %s", -                threading.active_count(), -                ', '.join(thread_name_list)) -            time.sleep(self._interval_in_sec) - - -def _parse_args_and_config(args): -    parser = _build_option_parser() - -    # First, parse options without configuration file. -    temporary_options, temporary_args = parser.parse_args(args=args) -    if temporary_args: -        logging.critical( -            'Unrecognized positional arguments: %r', temporary_args) -        sys.exit(1) - -    if temporary_options.config_file: -        try: -            config_fp = open(temporary_options.config_file, 'r') -        except IOError, e: -            logging.critical( -                'Failed to open configuration file %r: %r', -                temporary_options.config_file, -                e) -            sys.exit(1) - -        config_parser = ConfigParser.SafeConfigParser() -        config_parser.readfp(config_fp) -        config_fp.close() - -        args_from_config = [] -        for name, value in config_parser.items('pywebsocket'): -            args_from_config.append('--' + name) -            args_from_config.append(value) -        if args is None: -            args = args_from_config -        else: -            args = args_from_config + args -        return parser.parse_args(args=args) -    else: -        return temporary_options, temporary_args - - -def _main(args=None): -    """You can call this function from your own program, but please note that -    this function has some side-effects that might affect your program. For -    example, util.wrap_popen3_for_win use in this method replaces implementation -    of os.popen3. -    """ - -    options, args = _parse_args_and_config(args=args) - -    os.chdir(options.document_root) - -    _configure_logging(options) - -    # TODO(tyoshino): Clean up initialization of CGI related values. Move some -    # of code here to WebSocketRequestHandler class if it's better. -    options.cgi_directories = [] -    options.is_executable_method = None -    if options.cgi_paths: -        options.cgi_directories = options.cgi_paths.split(',') -        if sys.platform in ('cygwin', 'win32'): -            cygwin_path = None -            # For Win32 Python, it is expected that CYGWIN_PATH -            # is set to a directory of cygwin binaries. -            # For example, websocket_server.py in Chromium sets CYGWIN_PATH to -            # full path of third_party/cygwin/bin. -            if 'CYGWIN_PATH' in os.environ: -                cygwin_path = os.environ['CYGWIN_PATH'] -            util.wrap_popen3_for_win(cygwin_path) - -            def __check_script(scriptpath): -                return util.get_script_interp(scriptpath, cygwin_path) - -            options.is_executable_method = __check_script - -    if options.use_tls: -        if not (_HAS_SSL or _HAS_OPEN_SSL): -            logging.critical('TLS support requires ssl or pyOpenSSL module.') -            sys.exit(1) -        if not options.private_key or not options.certificate: -            logging.critical( -                    'To use TLS, specify private_key and certificate.') -            sys.exit(1) - -    if options.tls_client_auth: -        if not options.use_tls: -            logging.critical('TLS must be enabled for client authentication.') -            sys.exit(1) -        if not _HAS_SSL: -            logging.critical('Client authentication requires ssl module.') - -    if not options.scan_dir: -        options.scan_dir = options.websock_handlers - -    if options.use_basic_auth: -        options.basic_auth_credential = 'Basic ' + base64.b64encode( -            options.basic_auth_credential) - -    try: -        if options.thread_monitor_interval_in_sec > 0: -            # Run a thread monitor to show the status of server threads for -            # debugging. -            ThreadMonitor(options.thread_monitor_interval_in_sec).start() - -        server = WebSocketServer(options) -        server.serve_forever() -    except Exception, e: -        logging.critical('mod_pywebsocket: %s' % e) -        logging.critical('mod_pywebsocket: %s' % util.get_stack_trace()) -        sys.exit(1) - - -if __name__ == '__main__': -    _main(sys.argv[1:]) - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/stream.py b/module/lib/mod_pywebsocket/stream.py deleted file mode 100644 index edc533279..000000000 --- a/module/lib/mod_pywebsocket/stream.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""This file exports public symbols. -""" - - -from mod_pywebsocket._stream_base import BadOperationException -from mod_pywebsocket._stream_base import ConnectionTerminatedException -from mod_pywebsocket._stream_base import InvalidFrameException -from mod_pywebsocket._stream_base import InvalidUTF8Exception -from mod_pywebsocket._stream_base import UnsupportedFrameException -from mod_pywebsocket._stream_hixie75 import StreamHixie75 -from mod_pywebsocket._stream_hybi import Frame -from mod_pywebsocket._stream_hybi import Stream -from mod_pywebsocket._stream_hybi import StreamOptions - -# These methods are intended to be used by WebSocket client developers to have -# their implementations receive broken data in tests. -from mod_pywebsocket._stream_hybi import create_close_frame -from mod_pywebsocket._stream_hybi import create_header -from mod_pywebsocket._stream_hybi import create_length_header -from mod_pywebsocket._stream_hybi import create_ping_frame -from mod_pywebsocket._stream_hybi import create_pong_frame -from mod_pywebsocket._stream_hybi import create_binary_frame -from mod_pywebsocket._stream_hybi import create_text_frame -from mod_pywebsocket._stream_hybi import create_closing_handshake_body - - -# vi:sts=4 sw=4 et diff --git a/module/lib/mod_pywebsocket/util.py b/module/lib/mod_pywebsocket/util.py deleted file mode 100644 index 7bb0b5d9e..000000000 --- a/module/lib/mod_pywebsocket/util.py +++ /dev/null @@ -1,515 +0,0 @@ -# Copyright 2011, Google Inc. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -#     * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -#     * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -#     * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -"""WebSocket utilities. -""" - - -import array -import errno - -# Import hash classes from a module available and recommended for each Python -# version and re-export those symbol. Use sha and md5 module in Python 2.4, and -# hashlib module in Python 2.6. -try: -    import hashlib -    md5_hash = hashlib.md5 -    sha1_hash = hashlib.sha1 -except ImportError: -    import md5 -    import sha -    md5_hash = md5.md5 -    sha1_hash = sha.sha - -import StringIO -import logging -import os -import re -import socket -import traceback -import zlib - - -def get_stack_trace(): -    """Get the current stack trace as string. - -    This is needed to support Python 2.3. -    TODO: Remove this when we only support Python 2.4 and above. -          Use traceback.format_exc instead. -    """ - -    out = StringIO.StringIO() -    traceback.print_exc(file=out) -    return out.getvalue() - - -def prepend_message_to_exception(message, exc): -    """Prepend message to the exception.""" - -    exc.args = (message + str(exc),) -    return - - -def __translate_interp(interp, cygwin_path): -    """Translate interp program path for Win32 python to run cygwin program -    (e.g. perl).  Note that it doesn't support path that contains space, -    which is typically true for Unix, where #!-script is written. -    For Win32 python, cygwin_path is a directory of cygwin binaries. - -    Args: -      interp: interp command line -      cygwin_path: directory name of cygwin binary, or None -    Returns: -      translated interp command line. -    """ -    if not cygwin_path: -        return interp -    m = re.match('^[^ ]*/([^ ]+)( .*)?', interp) -    if m: -        cmd = os.path.join(cygwin_path, m.group(1)) -        return cmd + m.group(2) -    return interp - - -def get_script_interp(script_path, cygwin_path=None): -    """Gets #!-interpreter command line from the script. - -    It also fixes command path.  When Cygwin Python is used, e.g. in WebKit, -    it could run "/usr/bin/perl -wT hello.pl". -    When Win32 Python is used, e.g. in Chromium, it couldn't.  So, fix -    "/usr/bin/perl" to "<cygwin_path>\perl.exe". - -    Args: -      script_path: pathname of the script -      cygwin_path: directory name of cygwin binary, or None -    Returns: -      #!-interpreter command line, or None if it is not #!-script. -    """ -    fp = open(script_path) -    line = fp.readline() -    fp.close() -    m = re.match('^#!(.*)', line) -    if m: -        return __translate_interp(m.group(1), cygwin_path) -    return None - - -def wrap_popen3_for_win(cygwin_path): -    """Wrap popen3 to support #!-script on Windows. - -    Args: -      cygwin_path:  path for cygwin binary if command path is needed to be -                    translated.  None if no translation required. -    """ - -    __orig_popen3 = os.popen3 - -    def __wrap_popen3(cmd, mode='t', bufsize=-1): -        cmdline = cmd.split(' ') -        interp = get_script_interp(cmdline[0], cygwin_path) -        if interp: -            cmd = interp + ' ' + cmd -        return __orig_popen3(cmd, mode, bufsize) - -    os.popen3 = __wrap_popen3 - - -def hexify(s): -    return ' '.join(map(lambda x: '%02x' % ord(x), s)) - - -def get_class_logger(o): -    return logging.getLogger( -        '%s.%s' % (o.__class__.__module__, o.__class__.__name__)) - - -class NoopMasker(object): -    """A masking object that has the same interface as RepeatedXorMasker but -    just returns the string passed in without making any change. -    """ - -    def __init__(self): -        pass - -    def mask(self, s): -        return s - - -class RepeatedXorMasker(object): -    """A masking object that applies XOR on the string given to mask method -    with the masking bytes given to the constructor repeatedly. This object -    remembers the position in the masking bytes the last mask method call -    ended and resumes from that point on the next mask method call. -    """ - -    def __init__(self, mask): -        self._mask = map(ord, mask) -        self._mask_size = len(self._mask) -        self._count = 0 - -    def mask(self, s): -        result = array.array('B') -        result.fromstring(s) -        # Use temporary local variables to eliminate the cost to access -        # attributes -        count = self._count -        mask = self._mask -        mask_size = self._mask_size -        for i in xrange(len(result)): -            result[i] ^= mask[count] -            count = (count + 1) % mask_size -        self._count = count - -        return result.tostring() - - -class DeflateRequest(object): -    """A wrapper class for request object to intercept send and recv to perform -    deflate compression and decompression transparently. -    """ - -    def __init__(self, request): -        self._request = request -        self.connection = DeflateConnection(request.connection) - -    def __getattribute__(self, name): -        if name in ('_request', 'connection'): -            return object.__getattribute__(self, name) -        return self._request.__getattribute__(name) - -    def __setattr__(self, name, value): -        if name in ('_request', 'connection'): -            return object.__setattr__(self, name, value) -        return self._request.__setattr__(name, value) - - -# By making wbits option negative, we can suppress CMF/FLG (2 octet) and -# ADLER32 (4 octet) fields of zlib so that we can use zlib module just as -# deflate library. DICTID won't be added as far as we don't set dictionary. -# LZ77 window of 32K will be used for both compression and decompression. -# For decompression, we can just use 32K to cover any windows size. For -# compression, we use 32K so receivers must use 32K. -# -# Compression level is Z_DEFAULT_COMPRESSION. We don't have to match level -# to decode. -# -# See zconf.h, deflate.cc, inflate.cc of zlib library, and zlibmodule.c of -# Python. See also RFC1950 (ZLIB 3.3). - - -class _Deflater(object): - -    def __init__(self, window_bits): -        self._logger = get_class_logger(self) - -        self._compress = zlib.compressobj( -            zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -window_bits) - -    def compress(self, bytes): -        compressed_bytes = self._compress.compress(bytes) -        self._logger.debug('Compress input %r', bytes) -        self._logger.debug('Compress result %r', compressed_bytes) -        return compressed_bytes - -    def compress_and_flush(self, bytes): -        compressed_bytes = self._compress.compress(bytes) -        compressed_bytes += self._compress.flush(zlib.Z_SYNC_FLUSH) -        self._logger.debug('Compress input %r', bytes) -        self._logger.debug('Compress result %r', compressed_bytes) -        return compressed_bytes - -    def compress_and_finish(self, bytes): -        compressed_bytes = self._compress.compress(bytes) -        compressed_bytes += self._compress.flush(zlib.Z_FINISH) -        self._logger.debug('Compress input %r', bytes) -        self._logger.debug('Compress result %r', compressed_bytes) -        return compressed_bytes - -class _Inflater(object): - -    def __init__(self): -        self._logger = get_class_logger(self) - -        self._unconsumed = '' - -        self.reset() - -    def decompress(self, size): -        if not (size == -1 or size > 0): -            raise Exception('size must be -1 or positive') - -        data = '' - -        while True: -            if size == -1: -                data += self._decompress.decompress(self._unconsumed) -                # See Python bug http://bugs.python.org/issue12050 to -                # understand why the same code cannot be used for updating -                # self._unconsumed for here and else block. -                self._unconsumed = '' -            else: -                data += self._decompress.decompress( -                    self._unconsumed, size - len(data)) -                self._unconsumed = self._decompress.unconsumed_tail -            if self._decompress.unused_data: -                # Encountered a last block (i.e. a block with BFINAL = 1) and -                # found a new stream (unused_data). We cannot use the same -                # zlib.Decompress object for the new stream. Create a new -                # Decompress object to decompress the new one. -                # -                # It's fine to ignore unconsumed_tail if unused_data is not -                # empty. -                self._unconsumed = self._decompress.unused_data -                self.reset() -                if size >= 0 and len(data) == size: -                    # data is filled. Don't call decompress again. -                    break -                else: -                    # Re-invoke Decompress.decompress to try to decompress all -                    # available bytes before invoking read which blocks until -                    # any new byte is available. -                    continue -            else: -                # Here, since unused_data is empty, even if unconsumed_tail is -                # not empty, bytes of requested length are already in data. We -                # don't have to "continue" here. -                break - -        if data: -            self._logger.debug('Decompressed %r', data) -        return data - -    def append(self, data): -        self._logger.debug('Appended %r', data) -        self._unconsumed += data - -    def reset(self): -        self._logger.debug('Reset') -        self._decompress = zlib.decompressobj(-zlib.MAX_WBITS) - - -# Compresses/decompresses given octets using the method introduced in RFC1979. - - -class _RFC1979Deflater(object): -    """A compressor class that applies DEFLATE to given byte sequence and -    flushes using the algorithm described in the RFC1979 section 2.1. -    """ - -    def __init__(self, window_bits, no_context_takeover): -        self._deflater = None -        if window_bits is None: -            window_bits = zlib.MAX_WBITS -        self._window_bits = window_bits -        self._no_context_takeover = no_context_takeover - -    def filter(self, bytes, flush=True, bfinal=False): -        if self._deflater is None or (self._no_context_takeover and flush): -            self._deflater = _Deflater(self._window_bits) - -        if bfinal: -            result = self._deflater.compress_and_finish(bytes) -            # Add a padding block with BFINAL = 0 and BTYPE = 0. -            result = result + chr(0) -            self._deflater = None -            return result -        if flush: -            # Strip last 4 octets which is LEN and NLEN field of a -            # non-compressed block added for Z_SYNC_FLUSH. -            return self._deflater.compress_and_flush(bytes)[:-4] -        return self._deflater.compress(bytes) - -class _RFC1979Inflater(object): -    """A decompressor class for byte sequence compressed and flushed following -    the algorithm described in the RFC1979 section 2.1. -    """ - -    def __init__(self): -        self._inflater = _Inflater() - -    def filter(self, bytes): -        # Restore stripped LEN and NLEN field of a non-compressed block added -        # for Z_SYNC_FLUSH. -        self._inflater.append(bytes + '\x00\x00\xff\xff') -        return self._inflater.decompress(-1) - - -class DeflateSocket(object): -    """A wrapper class for socket object to intercept send and recv to perform -    deflate compression and decompression transparently. -    """ - -    # Size of the buffer passed to recv to receive compressed data. -    _RECV_SIZE = 4096 - -    def __init__(self, socket): -        self._socket = socket - -        self._logger = get_class_logger(self) - -        self._deflater = _Deflater(zlib.MAX_WBITS) -        self._inflater = _Inflater() - -    def recv(self, size): -        """Receives data from the socket specified on the construction up -        to the specified size. Once any data is available, returns it even -        if it's smaller than the specified size. -        """ - -        # TODO(tyoshino): Allow call with size=0. It should block until any -        # decompressed data is available. -        if size <= 0: -            raise Exception('Non-positive size passed') -        while True: -            data = self._inflater.decompress(size) -            if len(data) != 0: -                return data - -            read_data = self._socket.recv(DeflateSocket._RECV_SIZE) -            if not read_data: -                return '' -            self._inflater.append(read_data) - -    def sendall(self, bytes): -        self.send(bytes) - -    def send(self, bytes): -        self._socket.sendall(self._deflater.compress_and_flush(bytes)) -        return len(bytes) - - -class DeflateConnection(object): -    """A wrapper class for request object to intercept write and read to -    perform deflate compression and decompression transparently. -    """ - -    def __init__(self, connection): -        self._connection = connection - -        self._logger = get_class_logger(self) - -        self._deflater = _Deflater(zlib.MAX_WBITS) -        self._inflater = _Inflater() - -    def get_remote_addr(self): -        return self._connection.remote_addr -    remote_addr = property(get_remote_addr) - -    def put_bytes(self, bytes): -        self.write(bytes) - -    def read(self, size=-1): -        """Reads at most size bytes. Blocks until there's at least one byte -        available. -        """ - -        # TODO(tyoshino): Allow call with size=0. -        if not (size == -1 or size > 0): -            raise Exception('size must be -1 or positive') - -        data = '' -        while True: -            if size == -1: -                data += self._inflater.decompress(-1) -            else: -                data += self._inflater.decompress(size - len(data)) - -            if size >= 0 and len(data) != 0: -                break - -            # TODO(tyoshino): Make this read efficient by some workaround. -            # -            # In 3.0.3 and prior of mod_python, read blocks until length bytes -            # was read. We don't know the exact size to read while using -            # deflate, so read byte-by-byte. -            # -            # _StandaloneRequest.read that ultimately performs -            # socket._fileobject.read also blocks until length bytes was read -            read_data = self._connection.read(1) -            if not read_data: -                break -            self._inflater.append(read_data) -        return data - -    def write(self, bytes): -        self._connection.write(self._deflater.compress_and_flush(bytes)) - - -def _is_ewouldblock_errno(error_number): -    """Returns True iff error_number indicates that receive operation would -    block. To make this portable, we check availability of errno and then -    compare them. -    """ - -    for error_name in ['WSAEWOULDBLOCK', 'EWOULDBLOCK', 'EAGAIN']: -        if (error_name in dir(errno) and -            error_number == getattr(errno, error_name)): -            return True -    return False - - -def drain_received_data(raw_socket): -    # Set the socket non-blocking. -    original_timeout = raw_socket.gettimeout() -    raw_socket.settimeout(0.0) - -    drained_data = [] - -    # Drain until the socket is closed or no data is immediately -    # available for read. -    while True: -        try: -            data = raw_socket.recv(1) -            if not data: -                break -            drained_data.append(data) -        except socket.error, e: -            # e can be either a pair (errno, string) or just a string (or -            # something else) telling what went wrong. We suppress only -            # the errors that indicates that the socket blocks. Those -            # exceptions can be parsed as a pair (errno, string). -            try: -                error_number, message = e -            except: -                # Failed to parse socket.error. -                raise e - -            if _is_ewouldblock_errno(error_number): -                break -            else: -                raise e - -    # Rollback timeout value. -    raw_socket.settimeout(original_timeout) - -    return ''.join(drained_data) - - -# vi:sts=4 sw=4 et  | 
