testing/mochitest/pywebsocket/mod_pywebsocket/handshake/draft75.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/mochitest/pywebsocket/mod_pywebsocket/handshake/draft75.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,190 @@
     1.4 +# Copyright 2011, Google Inc.
     1.5 +# All rights reserved.
     1.6 +#
     1.7 +# Redistribution and use in source and binary forms, with or without
     1.8 +# modification, are permitted provided that the following conditions are
     1.9 +# met:
    1.10 +#
    1.11 +#     * Redistributions of source code must retain the above copyright
    1.12 +# notice, this list of conditions and the following disclaimer.
    1.13 +#     * Redistributions in binary form must reproduce the above
    1.14 +# copyright notice, this list of conditions and the following disclaimer
    1.15 +# in the documentation and/or other materials provided with the
    1.16 +# distribution.
    1.17 +#     * Neither the name of Google Inc. nor the names of its
    1.18 +# contributors may be used to endorse or promote products derived from
    1.19 +# this software without specific prior written permission.
    1.20 +#
    1.21 +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    1.22 +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    1.23 +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    1.24 +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    1.25 +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    1.26 +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    1.27 +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    1.28 +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    1.29 +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    1.30 +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    1.31 +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    1.32 +
    1.33 +
    1.34 +"""WebSocket handshaking defined in draft-hixie-thewebsocketprotocol-75."""
    1.35 +
    1.36 +
    1.37 +# Note: request.connection.write is used in this module, even though mod_python
    1.38 +# document says that it should be used only in connection handlers.
    1.39 +# Unfortunately, we have no other options. For example, request.write is not
    1.40 +# suitable because it doesn't allow direct raw bytes writing.
    1.41 +
    1.42 +
    1.43 +import logging
    1.44 +import re
    1.45 +
    1.46 +from mod_pywebsocket import common
    1.47 +from mod_pywebsocket.stream import StreamHixie75
    1.48 +from mod_pywebsocket import util
    1.49 +from mod_pywebsocket.handshake._base import HandshakeException
    1.50 +from mod_pywebsocket.handshake._base import build_location
    1.51 +from mod_pywebsocket.handshake._base import validate_subprotocol
    1.52 +
    1.53 +
    1.54 +_MANDATORY_HEADERS = [
    1.55 +    # key, expected value or None
    1.56 +    ['Upgrade', 'WebSocket'],
    1.57 +    ['Connection', 'Upgrade'],
    1.58 +    ['Host', None],
    1.59 +    ['Origin', None],
    1.60 +]
    1.61 +
    1.62 +_FIRST_FIVE_LINES = map(re.compile, [
    1.63 +    r'^GET /[\S]* HTTP/1.1\r\n$',
    1.64 +    r'^Upgrade: WebSocket\r\n$',
    1.65 +    r'^Connection: Upgrade\r\n$',
    1.66 +    r'^Host: [\S]+\r\n$',
    1.67 +    r'^Origin: [\S]+\r\n$',
    1.68 +])
    1.69 +
    1.70 +_SIXTH_AND_LATER = re.compile(
    1.71 +    r'^'
    1.72 +    r'(WebSocket-Protocol: [\x20-\x7e]+\r\n)?'
    1.73 +    r'(Cookie: [^\r]*\r\n)*'
    1.74 +    r'(Cookie2: [^\r]*\r\n)?'
    1.75 +    r'(Cookie: [^\r]*\r\n)*'
    1.76 +    r'\r\n')
    1.77 +
    1.78 +
    1.79 +class Handshaker(object):
    1.80 +    """This class performs WebSocket handshake."""
    1.81 +
    1.82 +    def __init__(self, request, dispatcher, strict=False):
    1.83 +        """Construct an instance.
    1.84 +
    1.85 +        Args:
    1.86 +            request: mod_python request.
    1.87 +            dispatcher: Dispatcher (dispatch.Dispatcher).
    1.88 +            strict: Strictly check handshake request. Default: False.
    1.89 +                If True, request.connection must provide get_memorized_lines
    1.90 +                method.
    1.91 +
    1.92 +        Handshaker will add attributes such as ws_resource in performing
    1.93 +        handshake.
    1.94 +        """
    1.95 +
    1.96 +        self._logger = util.get_class_logger(self)
    1.97 +
    1.98 +        self._request = request
    1.99 +        self._dispatcher = dispatcher
   1.100 +        self._strict = strict
   1.101 +
   1.102 +    def do_handshake(self):
   1.103 +        """Perform WebSocket Handshake.
   1.104 +
   1.105 +        On _request, we set
   1.106 +            ws_resource, ws_origin, ws_location, ws_protocol
   1.107 +            ws_challenge_md5: WebSocket handshake information.
   1.108 +            ws_stream: Frame generation/parsing class.
   1.109 +            ws_version: Protocol version.
   1.110 +        """
   1.111 +
   1.112 +        self._check_header_lines()
   1.113 +        self._set_resource()
   1.114 +        self._set_origin()
   1.115 +        self._set_location()
   1.116 +        self._set_subprotocol()
   1.117 +        self._set_protocol_version()
   1.118 +
   1.119 +        self._dispatcher.do_extra_handshake(self._request)
   1.120 +
   1.121 +        self._send_handshake()
   1.122 +
   1.123 +        self._logger.debug('Sent opening handshake response')
   1.124 +
   1.125 +    def _set_resource(self):
   1.126 +        self._request.ws_resource = self._request.uri
   1.127 +
   1.128 +    def _set_origin(self):
   1.129 +        self._request.ws_origin = self._request.headers_in['Origin']
   1.130 +
   1.131 +    def _set_location(self):
   1.132 +        self._request.ws_location = build_location(self._request)
   1.133 +
   1.134 +    def _set_subprotocol(self):
   1.135 +        subprotocol = self._request.headers_in.get('WebSocket-Protocol')
   1.136 +        if subprotocol is not None:
   1.137 +            validate_subprotocol(subprotocol, hixie=True)
   1.138 +        self._request.ws_protocol = subprotocol
   1.139 +
   1.140 +    def _set_protocol_version(self):
   1.141 +        self._logger.debug('IETF Hixie 75 protocol')
   1.142 +        self._request.ws_version = common.VERSION_HIXIE75
   1.143 +        self._request.ws_stream = StreamHixie75(self._request)
   1.144 +
   1.145 +    def _sendall(self, data):
   1.146 +        self._request.connection.write(data)
   1.147 +
   1.148 +    def _send_handshake(self):
   1.149 +        self._sendall('HTTP/1.1 101 Web Socket Protocol Handshake\r\n')
   1.150 +        self._sendall('Upgrade: WebSocket\r\n')
   1.151 +        self._sendall('Connection: Upgrade\r\n')
   1.152 +        self._sendall('WebSocket-Origin: %s\r\n' % self._request.ws_origin)
   1.153 +        self._sendall('WebSocket-Location: %s\r\n' % self._request.ws_location)
   1.154 +        if self._request.ws_protocol:
   1.155 +            self._sendall(
   1.156 +                'WebSocket-Protocol: %s\r\n' % self._request.ws_protocol)
   1.157 +        self._sendall('\r\n')
   1.158 +
   1.159 +    def _check_header_lines(self):
   1.160 +        for key, expected_value in _MANDATORY_HEADERS:
   1.161 +            actual_value = self._request.headers_in.get(key)
   1.162 +            if not actual_value:
   1.163 +                raise HandshakeException('Header %s is not defined' % key)
   1.164 +            if expected_value:
   1.165 +                if actual_value != expected_value:
   1.166 +                    raise HandshakeException(
   1.167 +                        'Expected %r for header %s but found %r' %
   1.168 +                        (expected_value, key, actual_value))
   1.169 +        if self._strict:
   1.170 +            try:
   1.171 +                lines = self._request.connection.get_memorized_lines()
   1.172 +            except AttributeError, e:
   1.173 +                raise AttributeError(
   1.174 +                    'Strict handshake is specified but the connection '
   1.175 +                    'doesn\'t provide get_memorized_lines()')
   1.176 +            self._check_first_lines(lines)
   1.177 +
   1.178 +    def _check_first_lines(self, lines):
   1.179 +        if len(lines) < len(_FIRST_FIVE_LINES):
   1.180 +            raise HandshakeException('Too few header lines: %d' % len(lines))
   1.181 +        for line, regexp in zip(lines, _FIRST_FIVE_LINES):
   1.182 +            if not regexp.search(line):
   1.183 +                raise HandshakeException(
   1.184 +                    'Unexpected header: %r doesn\'t match %r'
   1.185 +                    % (line, regexp.pattern))
   1.186 +        sixth_and_later = ''.join(lines[5:])
   1.187 +        if not _SIXTH_AND_LATER.search(sixth_and_later):
   1.188 +            raise HandshakeException(
   1.189 +                'Unexpected header: %r doesn\'t match %r'
   1.190 +                % (sixth_and_later, _SIXTH_AND_LATER.pattern))
   1.191 +
   1.192 +
   1.193 +# vi:sts=4 sw=4 et

mercurial