testing/mochitest/pywebsocket/mod_pywebsocket/handshake/hybi00.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/hybi00.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,242 @@
     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 +"""This file provides the opening handshake processor for the WebSocket
    1.35 +protocol version HyBi 00.
    1.36 +
    1.37 +Specification:
    1.38 +http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
    1.39 +"""
    1.40 +
    1.41 +
    1.42 +# Note: request.connection.write/read are used in this module, even though
    1.43 +# mod_python document says that they should be used only in connection
    1.44 +# handlers. Unfortunately, we have no other options. For example,
    1.45 +# request.write/read are not suitable because they don't allow direct raw bytes
    1.46 +# writing/reading.
    1.47 +
    1.48 +
    1.49 +import logging
    1.50 +import re
    1.51 +import struct
    1.52 +
    1.53 +from mod_pywebsocket import common
    1.54 +from mod_pywebsocket.stream import StreamHixie75
    1.55 +from mod_pywebsocket import util
    1.56 +from mod_pywebsocket.handshake._base import HandshakeException
    1.57 +from mod_pywebsocket.handshake._base import build_location
    1.58 +from mod_pywebsocket.handshake._base import check_header_lines
    1.59 +from mod_pywebsocket.handshake._base import format_header
    1.60 +from mod_pywebsocket.handshake._base import get_mandatory_header
    1.61 +from mod_pywebsocket.handshake._base import validate_subprotocol
    1.62 +
    1.63 +
    1.64 +_MANDATORY_HEADERS = [
    1.65 +    # key, expected value or None
    1.66 +    [common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75],
    1.67 +    [common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE],
    1.68 +]
    1.69 +
    1.70 +
    1.71 +class Handshaker(object):
    1.72 +    """Opening handshake processor for the WebSocket protocol version HyBi 00.
    1.73 +    """
    1.74 +
    1.75 +    def __init__(self, request, dispatcher):
    1.76 +        """Construct an instance.
    1.77 +
    1.78 +        Args:
    1.79 +            request: mod_python request.
    1.80 +            dispatcher: Dispatcher (dispatch.Dispatcher).
    1.81 +
    1.82 +        Handshaker will add attributes such as ws_resource in performing
    1.83 +        handshake.
    1.84 +        """
    1.85 +
    1.86 +        self._logger = util.get_class_logger(self)
    1.87 +
    1.88 +        self._request = request
    1.89 +        self._dispatcher = dispatcher
    1.90 +
    1.91 +    def do_handshake(self):
    1.92 +        """Perform WebSocket Handshake.
    1.93 +
    1.94 +        On _request, we set
    1.95 +            ws_resource, ws_protocol, ws_location, ws_origin, ws_challenge,
    1.96 +            ws_challenge_md5: WebSocket handshake information.
    1.97 +            ws_stream: Frame generation/parsing class.
    1.98 +            ws_version: Protocol version.
    1.99 +
   1.100 +        Raises:
   1.101 +            HandshakeException: when any error happened in parsing the opening
   1.102 +                                handshake request.
   1.103 +        """
   1.104 +
   1.105 +        # 5.1 Reading the client's opening handshake.
   1.106 +        # dispatcher sets it in self._request.
   1.107 +        check_header_lines(self._request, _MANDATORY_HEADERS)
   1.108 +        self._set_resource()
   1.109 +        self._set_subprotocol()
   1.110 +        self._set_location()
   1.111 +        self._set_origin()
   1.112 +        self._set_challenge_response()
   1.113 +        self._set_protocol_version()
   1.114 +
   1.115 +        self._dispatcher.do_extra_handshake(self._request)
   1.116 +
   1.117 +        self._send_handshake()
   1.118 +
   1.119 +    def _set_resource(self):
   1.120 +        self._request.ws_resource = self._request.uri
   1.121 +
   1.122 +    def _set_subprotocol(self):
   1.123 +        # |Sec-WebSocket-Protocol|
   1.124 +        subprotocol = self._request.headers_in.get(
   1.125 +            common.SEC_WEBSOCKET_PROTOCOL_HEADER)
   1.126 +        if subprotocol is not None:
   1.127 +            validate_subprotocol(subprotocol, hixie=True)
   1.128 +        self._request.ws_protocol = subprotocol
   1.129 +
   1.130 +    def _set_location(self):
   1.131 +        # |Host|
   1.132 +        host = self._request.headers_in.get(common.HOST_HEADER)
   1.133 +        if host is not None:
   1.134 +            self._request.ws_location = build_location(self._request)
   1.135 +        # TODO(ukai): check host is this host.
   1.136 +
   1.137 +    def _set_origin(self):
   1.138 +        # |Origin|
   1.139 +        origin = self._request.headers_in.get(common.ORIGIN_HEADER)
   1.140 +        if origin is not None:
   1.141 +            self._request.ws_origin = origin
   1.142 +
   1.143 +    def _set_protocol_version(self):
   1.144 +        # |Sec-WebSocket-Draft|
   1.145 +        draft = self._request.headers_in.get(common.SEC_WEBSOCKET_DRAFT_HEADER)
   1.146 +        if draft is not None and draft != '0':
   1.147 +            raise HandshakeException('Illegal value for %s: %s' %
   1.148 +                                     (common.SEC_WEBSOCKET_DRAFT_HEADER,
   1.149 +                                      draft))
   1.150 +
   1.151 +        self._logger.debug('Protocol version is HyBi 00')
   1.152 +        self._request.ws_version = common.VERSION_HYBI00
   1.153 +        self._request.ws_stream = StreamHixie75(self._request, True)
   1.154 +
   1.155 +    def _set_challenge_response(self):
   1.156 +        # 5.2 4-8.
   1.157 +        self._request.ws_challenge = self._get_challenge()
   1.158 +        # 5.2 9. let /response/ be the MD5 finterprint of /challenge/
   1.159 +        self._request.ws_challenge_md5 = util.md5_hash(
   1.160 +            self._request.ws_challenge).digest()
   1.161 +        self._logger.debug(
   1.162 +            'Challenge: %r (%s)',
   1.163 +            self._request.ws_challenge,
   1.164 +            util.hexify(self._request.ws_challenge))
   1.165 +        self._logger.debug(
   1.166 +            'Challenge response: %r (%s)',
   1.167 +            self._request.ws_challenge_md5,
   1.168 +            util.hexify(self._request.ws_challenge_md5))
   1.169 +
   1.170 +    def _get_key_value(self, key_field):
   1.171 +        key_value = get_mandatory_header(self._request, key_field)
   1.172 +
   1.173 +        self._logger.debug('%s: %r', key_field, key_value)
   1.174 +
   1.175 +        # 5.2 4. let /key-number_n/ be the digits (characters in the range
   1.176 +        # U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)) in /key_n/,
   1.177 +        # interpreted as a base ten integer, ignoring all other characters
   1.178 +        # in /key_n/.
   1.179 +        try:
   1.180 +            key_number = int(re.sub("\\D", "", key_value))
   1.181 +        except:
   1.182 +            raise HandshakeException('%s field contains no digit' % key_field)
   1.183 +        # 5.2 5. let /spaces_n/ be the number of U+0020 SPACE characters
   1.184 +        # in /key_n/.
   1.185 +        spaces = re.subn(" ", "", key_value)[1]
   1.186 +        if spaces == 0:
   1.187 +            raise HandshakeException('%s field contains no space' % key_field)
   1.188 +
   1.189 +        self._logger.debug(
   1.190 +            '%s: Key-number is %d and number of spaces is %d',
   1.191 +            key_field, key_number, spaces)
   1.192 +
   1.193 +        # 5.2 6. if /key-number_n/ is not an integral multiple of /spaces_n/
   1.194 +        # then abort the WebSocket connection.
   1.195 +        if key_number % spaces != 0:
   1.196 +            raise HandshakeException(
   1.197 +                '%s: Key-number (%d) is not an integral multiple of spaces '
   1.198 +                '(%d)' % (key_field, key_number, spaces))
   1.199 +        # 5.2 7. let /part_n/ be /key-number_n/ divided by /spaces_n/.
   1.200 +        part = key_number / spaces
   1.201 +        self._logger.debug('%s: Part is %d', key_field, part)
   1.202 +        return part
   1.203 +
   1.204 +    def _get_challenge(self):
   1.205 +        # 5.2 4-7.
   1.206 +        key1 = self._get_key_value(common.SEC_WEBSOCKET_KEY1_HEADER)
   1.207 +        key2 = self._get_key_value(common.SEC_WEBSOCKET_KEY2_HEADER)
   1.208 +        # 5.2 8. let /challenge/ be the concatenation of /part_1/,
   1.209 +        challenge = ''
   1.210 +        challenge += struct.pack('!I', key1)  # network byteorder int
   1.211 +        challenge += struct.pack('!I', key2)  # network byteorder int
   1.212 +        challenge += self._request.connection.read(8)
   1.213 +        return challenge
   1.214 +
   1.215 +    def _send_handshake(self):
   1.216 +        response = []
   1.217 +
   1.218 +        # 5.2 10. send the following line.
   1.219 +        response.append('HTTP/1.1 101 WebSocket Protocol Handshake\r\n')
   1.220 +
   1.221 +        # 5.2 11. send the following fields to the client.
   1.222 +        response.append(format_header(
   1.223 +            common.UPGRADE_HEADER, common.WEBSOCKET_UPGRADE_TYPE_HIXIE75))
   1.224 +        response.append(format_header(
   1.225 +            common.CONNECTION_HEADER, common.UPGRADE_CONNECTION_TYPE))
   1.226 +        response.append(format_header(
   1.227 +            common.SEC_WEBSOCKET_LOCATION_HEADER, self._request.ws_location))
   1.228 +        response.append(format_header(
   1.229 +            common.SEC_WEBSOCKET_ORIGIN_HEADER, self._request.ws_origin))
   1.230 +        if self._request.ws_protocol:
   1.231 +            response.append(format_header(
   1.232 +                common.SEC_WEBSOCKET_PROTOCOL_HEADER,
   1.233 +                self._request.ws_protocol))
   1.234 +        # 5.2 12. send two bytes 0x0D 0x0A.
   1.235 +        response.append('\r\n')
   1.236 +        # 5.2 13. send /response/
   1.237 +        response.append(self._request.ws_challenge_md5)
   1.238 +
   1.239 +        raw_response = ''.join(response)
   1.240 +        self._request.connection.write(raw_response)
   1.241 +        self._logger.debug('Sent server\'s opening handshake: %r',
   1.242 +                           raw_response)
   1.243 +
   1.244 +
   1.245 +# vi:sts=4 sw=4 et

mercurial