• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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