1#!/usr/bin/env python 2# 3# Copyright 2011, Google Inc. 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are 8# met: 9# 10# * Redistributions of source code must retain the above copyright 11# notice, this list of conditions and the following disclaimer. 12# * Redistributions in binary form must reproduce the above 13# copyright notice, this list of conditions and the following disclaimer 14# in the documentation and/or other materials provided with the 15# distribution. 16# * Neither the name of Google Inc. nor the names of its 17# contributors may be used to endorse or promote products derived from 18# this software without specific prior written permission. 19# 20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 32 33"""Tests for handshake.hybi00 module.""" 34 35 36import unittest 37 38import set_sys_path # Update sys.path to locate mod_pywebsocket module. 39 40from mod_pywebsocket.handshake._base import HandshakeException 41from mod_pywebsocket.handshake import hybi00 as handshake 42from test import mock 43 44 45_GOOD_REQUEST = ( 46 80, 47 'GET', 48 '/demo', 49 { 50 'Host': 'example.com', 51 'Connection': 'Upgrade', 52 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 53 'Sec-WebSocket-Protocol': 'sample', 54 'Upgrade': 'WebSocket', 55 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 56 'Origin': 'http://example.com', 57 }, 58 '^n:ds[4U') 59 60_GOOD_REQUEST_CAPITALIZED_HEADER_VALUES = ( 61 80, 62 'GET', 63 '/demo', 64 { 65 'Host': 'example.com', 66 'Connection': 'UPGRADE', 67 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 68 'Sec-WebSocket-Protocol': 'sample', 69 'Upgrade': 'WEBSOCKET', 70 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 71 'Origin': 'http://example.com', 72 }, 73 '^n:ds[4U') 74 75_GOOD_RESPONSE_DEFAULT_PORT = ( 76 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' 77 'Upgrade: WebSocket\r\n' 78 'Connection: Upgrade\r\n' 79 'Sec-WebSocket-Location: ws://example.com/demo\r\n' 80 'Sec-WebSocket-Origin: http://example.com\r\n' 81 'Sec-WebSocket-Protocol: sample\r\n' 82 '\r\n' 83 '8jKS\'y:G*Co,Wxa-') 84 85_GOOD_RESPONSE_SECURE = ( 86 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' 87 'Upgrade: WebSocket\r\n' 88 'Connection: Upgrade\r\n' 89 'Sec-WebSocket-Location: wss://example.com/demo\r\n' 90 'Sec-WebSocket-Origin: http://example.com\r\n' 91 'Sec-WebSocket-Protocol: sample\r\n' 92 '\r\n' 93 '8jKS\'y:G*Co,Wxa-') 94 95_GOOD_REQUEST_NONDEFAULT_PORT = ( 96 8081, 97 'GET', 98 '/demo', 99 { 100 'Host': 'example.com:8081', 101 'Connection': 'Upgrade', 102 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 103 'Sec-WebSocket-Protocol': 'sample', 104 'Upgrade': 'WebSocket', 105 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 106 'Origin': 'http://example.com', 107 }, 108 '^n:ds[4U') 109 110_GOOD_RESPONSE_NONDEFAULT_PORT = ( 111 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' 112 'Upgrade: WebSocket\r\n' 113 'Connection: Upgrade\r\n' 114 'Sec-WebSocket-Location: ws://example.com:8081/demo\r\n' 115 'Sec-WebSocket-Origin: http://example.com\r\n' 116 'Sec-WebSocket-Protocol: sample\r\n' 117 '\r\n' 118 '8jKS\'y:G*Co,Wxa-') 119 120_GOOD_RESPONSE_SECURE_NONDEF = ( 121 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' 122 'Upgrade: WebSocket\r\n' 123 'Connection: Upgrade\r\n' 124 'Sec-WebSocket-Location: wss://example.com:8081/demo\r\n' 125 'Sec-WebSocket-Origin: http://example.com\r\n' 126 'Sec-WebSocket-Protocol: sample\r\n' 127 '\r\n' 128 '8jKS\'y:G*Co,Wxa-') 129 130_GOOD_REQUEST_NO_PROTOCOL = ( 131 80, 132 'GET', 133 '/demo', 134 { 135 'Host': 'example.com', 136 'Connection': 'Upgrade', 137 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 138 'Upgrade': 'WebSocket', 139 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 140 'Origin': 'http://example.com', 141 }, 142 '^n:ds[4U') 143 144_GOOD_RESPONSE_NO_PROTOCOL = ( 145 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' 146 'Upgrade: WebSocket\r\n' 147 'Connection: Upgrade\r\n' 148 'Sec-WebSocket-Location: ws://example.com/demo\r\n' 149 'Sec-WebSocket-Origin: http://example.com\r\n' 150 '\r\n' 151 '8jKS\'y:G*Co,Wxa-') 152 153_GOOD_REQUEST_WITH_OPTIONAL_HEADERS = ( 154 80, 155 'GET', 156 '/demo', 157 { 158 'Host': 'example.com', 159 'Connection': 'Upgrade', 160 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 161 'EmptyValue': '', 162 'Sec-WebSocket-Protocol': 'sample', 163 'AKey': 'AValue', 164 'Upgrade': 'WebSocket', 165 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 166 'Origin': 'http://example.com', 167 }, 168 '^n:ds[4U') 169 170# TODO(tyoshino): Include \r \n in key3, challenge response. 171 172_GOOD_REQUEST_WITH_NONPRINTABLE_KEY = ( 173 80, 174 'GET', 175 '/demo', 176 { 177 'Host': 'example.com', 178 'Connection': 'Upgrade', 179 'Sec-WebSocket-Key2': 'y R2 48 Q1O4 e|BV3 i5 1 u- 65', 180 'Sec-WebSocket-Protocol': 'sample', 181 'Upgrade': 'WebSocket', 182 'Sec-WebSocket-Key1': '36 7 74 i 92 2\'m 9 0G', 183 'Origin': 'http://example.com', 184 }, 185 ''.join(map(chr, [0x01, 0xd1, 0xdd, 0x3b, 0xd1, 0x56, 0x63, 0xff]))) 186 187_GOOD_RESPONSE_WITH_NONPRINTABLE_KEY = ( 188 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' 189 'Upgrade: WebSocket\r\n' 190 'Connection: Upgrade\r\n' 191 'Sec-WebSocket-Location: ws://example.com/demo\r\n' 192 'Sec-WebSocket-Origin: http://example.com\r\n' 193 'Sec-WebSocket-Protocol: sample\r\n' 194 '\r\n' + 195 ''.join(map(chr, [0x0b, 0x99, 0xfa, 0x55, 0xbd, 0x01, 0x23, 0x7b, 196 0x45, 0xa2, 0xf1, 0xd0, 0x87, 0x8a, 0xee, 0xeb]))) 197 198_BAD_REQUESTS = ( 199 ( # HTTP request 200 80, 201 'GET', 202 '/demo', 203 { 204 'Host': 'www.google.com', 205 'User-Agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5;' 206 ' en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3' 207 ' GTB6 GTBA', 208 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,' 209 '*/*;q=0.8', 210 'Accept-Language': 'en-us,en;q=0.5', 211 'Accept-Encoding': 'gzip,deflate', 212 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 213 'Keep-Alive': '300', 214 'Connection': 'keep-alive', 215 }), 216 ( # Wrong method 217 80, 218 'POST', 219 '/demo', 220 { 221 'Host': 'example.com', 222 'Connection': 'Upgrade', 223 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 224 'Sec-WebSocket-Protocol': 'sample', 225 'Upgrade': 'WebSocket', 226 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 227 'Origin': 'http://example.com', 228 }, 229 '^n:ds[4U'), 230 ( # Missing Upgrade 231 80, 232 'GET', 233 '/demo', 234 { 235 'Host': 'example.com', 236 'Connection': 'Upgrade', 237 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 238 'Sec-WebSocket-Protocol': 'sample', 239 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 240 'Origin': 'http://example.com', 241 }, 242 '^n:ds[4U'), 243 ( # Wrong Upgrade 244 80, 245 'GET', 246 '/demo', 247 { 248 'Host': 'example.com', 249 'Connection': 'Upgrade', 250 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 251 'Sec-WebSocket-Protocol': 'sample', 252 'Upgrade': 'NonWebSocket', 253 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 254 'Origin': 'http://example.com', 255 }, 256 '^n:ds[4U'), 257 ( # Empty WebSocket-Protocol 258 80, 259 'GET', 260 '/demo', 261 { 262 'Host': 'example.com', 263 'Connection': 'Upgrade', 264 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 265 'Sec-WebSocket-Protocol': '', 266 'Upgrade': 'WebSocket', 267 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 268 'Origin': 'http://example.com', 269 }, 270 '^n:ds[4U'), 271 ( # Wrong port number format 272 80, 273 'GET', 274 '/demo', 275 { 276 'Host': 'example.com:0x50', 277 'Connection': 'Upgrade', 278 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 279 'Sec-WebSocket-Protocol': 'sample', 280 'Upgrade': 'WebSocket', 281 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 282 'Origin': 'http://example.com', 283 }, 284 '^n:ds[4U'), 285 ( # Header/connection port mismatch 286 8080, 287 'GET', 288 '/demo', 289 { 290 'Host': 'example.com', 291 'Connection': 'Upgrade', 292 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 293 'Sec-WebSocket-Protocol': 'sample', 294 'Upgrade': 'WebSocket', 295 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 296 'Origin': 'http://example.com', 297 }, 298 '^n:ds[4U'), 299 ( # Illegal WebSocket-Protocol 300 80, 301 'GET', 302 '/demo', 303 { 304 'Host': 'example.com', 305 'Connection': 'Upgrade', 306 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 307 'Sec-WebSocket-Protocol': 'illegal\x09protocol', 308 'Upgrade': 'WebSocket', 309 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 310 'Origin': 'http://example.com', 311 }, 312 '^n:ds[4U'), 313) 314 315 316def _create_request(request_def): 317 data = '' 318 if len(request_def) > 4: 319 data = request_def[4] 320 conn = mock.MockConn(data) 321 conn.local_addr = ('0.0.0.0', request_def[0]) 322 return mock.MockRequest( 323 method=request_def[1], 324 uri=request_def[2], 325 headers_in=request_def[3], 326 connection=conn) 327 328 329def _create_get_memorized_lines(lines): 330 """Creates a function that returns the given string.""" 331 332 def get_memorized_lines(): 333 return lines 334 return get_memorized_lines 335 336 337def _create_requests_with_lines(request_lines_set): 338 requests = [] 339 for lines in request_lines_set: 340 request = _create_request(_GOOD_REQUEST) 341 request.connection.get_memorized_lines = _create_get_memorized_lines( 342 lines) 343 requests.append(request) 344 return requests 345 346 347class Hybi00HandshakerTest(unittest.TestCase): 348 349 def test_good_request_default_port(self): 350 request = _create_request(_GOOD_REQUEST) 351 handshaker = handshake.Handshaker(request, 352 mock.MockDispatcher()) 353 handshaker.do_handshake() 354 self.assertEqual(_GOOD_RESPONSE_DEFAULT_PORT, 355 request.connection.written_data()) 356 self.assertEqual('/demo', request.ws_resource) 357 self.assertEqual('http://example.com', request.ws_origin) 358 self.assertEqual('ws://example.com/demo', request.ws_location) 359 self.assertEqual('sample', request.ws_protocol) 360 361 def test_good_request_capitalized_header_values(self): 362 request = _create_request(_GOOD_REQUEST_CAPITALIZED_HEADER_VALUES) 363 handshaker = handshake.Handshaker(request, 364 mock.MockDispatcher()) 365 handshaker.do_handshake() 366 self.assertEqual(_GOOD_RESPONSE_DEFAULT_PORT, 367 request.connection.written_data()) 368 369 def test_good_request_secure_default_port(self): 370 request = _create_request(_GOOD_REQUEST) 371 request.connection.local_addr = ('0.0.0.0', 443) 372 request.is_https_ = True 373 handshaker = handshake.Handshaker(request, 374 mock.MockDispatcher()) 375 handshaker.do_handshake() 376 self.assertEqual(_GOOD_RESPONSE_SECURE, 377 request.connection.written_data()) 378 self.assertEqual('sample', request.ws_protocol) 379 380 def test_good_request_nondefault_port(self): 381 request = _create_request(_GOOD_REQUEST_NONDEFAULT_PORT) 382 handshaker = handshake.Handshaker(request, 383 mock.MockDispatcher()) 384 handshaker.do_handshake() 385 self.assertEqual(_GOOD_RESPONSE_NONDEFAULT_PORT, 386 request.connection.written_data()) 387 self.assertEqual('sample', request.ws_protocol) 388 389 def test_good_request_secure_non_default_port(self): 390 request = _create_request(_GOOD_REQUEST_NONDEFAULT_PORT) 391 request.is_https_ = True 392 handshaker = handshake.Handshaker(request, 393 mock.MockDispatcher()) 394 handshaker.do_handshake() 395 self.assertEqual(_GOOD_RESPONSE_SECURE_NONDEF, 396 request.connection.written_data()) 397 self.assertEqual('sample', request.ws_protocol) 398 399 def test_good_request_default_no_protocol(self): 400 request = _create_request(_GOOD_REQUEST_NO_PROTOCOL) 401 handshaker = handshake.Handshaker(request, 402 mock.MockDispatcher()) 403 handshaker.do_handshake() 404 self.assertEqual(_GOOD_RESPONSE_NO_PROTOCOL, 405 request.connection.written_data()) 406 self.assertEqual(None, request.ws_protocol) 407 408 def test_good_request_optional_headers(self): 409 request = _create_request(_GOOD_REQUEST_WITH_OPTIONAL_HEADERS) 410 handshaker = handshake.Handshaker(request, 411 mock.MockDispatcher()) 412 handshaker.do_handshake() 413 self.assertEqual('AValue', 414 request.headers_in['AKey']) 415 self.assertEqual('', 416 request.headers_in['EmptyValue']) 417 418 def test_good_request_with_nonprintable_key(self): 419 request = _create_request(_GOOD_REQUEST_WITH_NONPRINTABLE_KEY) 420 handshaker = handshake.Handshaker(request, 421 mock.MockDispatcher()) 422 handshaker.do_handshake() 423 self.assertEqual(_GOOD_RESPONSE_WITH_NONPRINTABLE_KEY, 424 request.connection.written_data()) 425 self.assertEqual('sample', request.ws_protocol) 426 427 def test_bad_requests(self): 428 for request in map(_create_request, _BAD_REQUESTS): 429 handshaker = handshake.Handshaker(request, 430 mock.MockDispatcher()) 431 self.assertRaises(HandshakeException, handshaker.do_handshake) 432 433 434if __name__ == '__main__': 435 unittest.main() 436 437 438# vi:sts=4 sw=4 et 439