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