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