2010-01-14 Yuzo Fujishima <yuzo@google.com>
[WebKit-https.git] / WebKitTools / pywebsocket / test / test_handshake.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2009, 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 module."""
34
35
36 import unittest
37
38 import config  # This must be imported before mod_pywebsocket.
39 from mod_pywebsocket import handshake
40
41 import mock
42
43
44 _GOOD_REQUEST = (
45     80,
46     '/demo',
47     {
48         'Upgrade':'WebSocket',
49         'Connection':'Upgrade',
50         'Host':'example.com',
51         'Origin':'http://example.com',
52         'WebSocket-Protocol':'sample',
53     }
54 )
55
56 _GOOD_RESPONSE_DEFAULT_PORT = (
57     'HTTP/1.1 101 Web Socket Protocol Handshake\r\n'
58     'Upgrade: WebSocket\r\n'
59     'Connection: Upgrade\r\n'
60     'WebSocket-Origin: http://example.com\r\n'
61     'WebSocket-Location: ws://example.com/demo\r\n'
62     'WebSocket-Protocol: sample\r\n'
63     '\r\n')
64
65 _GOOD_RESPONSE_SECURE = (
66     'HTTP/1.1 101 Web Socket Protocol Handshake\r\n'
67     'Upgrade: WebSocket\r\n'
68     'Connection: Upgrade\r\n'
69     'WebSocket-Origin: http://example.com\r\n'
70     'WebSocket-Location: wss://example.com/demo\r\n'
71     'WebSocket-Protocol: sample\r\n'
72     '\r\n')
73
74 _GOOD_REQUEST_NONDEFAULT_PORT = (
75     8081,
76     '/demo',
77     {
78         'Upgrade':'WebSocket',
79         'Connection':'Upgrade',
80         'Host':'example.com:8081',
81         'Origin':'http://example.com',
82         'WebSocket-Protocol':'sample',
83     }
84 )
85
86 _GOOD_RESPONSE_NONDEFAULT_PORT = (
87     'HTTP/1.1 101 Web Socket Protocol Handshake\r\n'
88     'Upgrade: WebSocket\r\n'
89     'Connection: Upgrade\r\n'
90     'WebSocket-Origin: http://example.com\r\n'
91     'WebSocket-Location: ws://example.com:8081/demo\r\n'
92     'WebSocket-Protocol: sample\r\n'
93     '\r\n')
94
95 _GOOD_RESPONSE_SECURE_NONDEF = (
96     'HTTP/1.1 101 Web Socket Protocol Handshake\r\n'
97     'Upgrade: WebSocket\r\n'
98     'Connection: Upgrade\r\n'
99     'WebSocket-Origin: http://example.com\r\n'
100     'WebSocket-Location: wss://example.com:8081/demo\r\n'
101     'WebSocket-Protocol: sample\r\n'
102     '\r\n')
103
104 _GOOD_REQUEST_NO_PROTOCOL = (
105     80,
106     '/demo',
107     {
108         'Upgrade':'WebSocket',
109         'Connection':'Upgrade',
110         'Host':'example.com',
111         'Origin':'http://example.com',
112     }
113 )
114
115 _GOOD_RESPONSE_NO_PROTOCOL = (
116     'HTTP/1.1 101 Web Socket Protocol Handshake\r\n'
117     'Upgrade: WebSocket\r\n'
118     'Connection: Upgrade\r\n'
119     'WebSocket-Origin: http://example.com\r\n'
120     'WebSocket-Location: ws://example.com/demo\r\n'
121     '\r\n')
122
123 _GOOD_REQUEST_WITH_OPTIONAL_HEADERS = (
124     80,
125     '/demo',
126     {
127         'Upgrade':'WebSocket',
128         'Connection':'Upgrade',
129         'Host':'example.com',
130         'Origin':'http://example.com',
131         'WebSocket-Protocol':'sample',
132         'AKey':'AValue',
133         'EmptyValue':'',
134     }
135 )
136
137 _BAD_REQUESTS = (
138     (  # HTTP request
139         80,
140         '/demo',
141         {
142             'Host':'www.google.com',
143             'User-Agent':'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5;'
144                          ' en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3'
145                          ' GTB6 GTBA',
146             'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,'
147                      '*/*;q=0.8',
148             'Accept-Language':'en-us,en;q=0.5',
149             'Accept-Encoding':'gzip,deflate',
150             'Accept-Charset':'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
151             'Keep-Alive':'300',
152             'Connection':'keep-alive',
153         }
154     ),
155     (  # Missing Upgrade
156         80,
157         '/demo',
158         {
159             'Connection':'Upgrade',
160             'Host':'example.com',
161             'Origin':'http://example.com',
162             'WebSocket-Protocol':'sample',
163         }
164     ),
165     (  # Wrong Upgrade
166         80,
167         '/demo',
168         {
169             'Upgrade':'NonWebSocket',
170             'Connection':'Upgrade',
171             'Host':'example.com',
172             'Origin':'http://example.com',
173             'WebSocket-Protocol':'sample',
174         }
175     ),
176     (  # Empty WebSocket-Protocol
177         80,
178         '/demo',
179         {
180             'Upgrade':'WebSocket',
181             'Connection':'Upgrade',
182             'Host':'example.com',
183             'Origin':'http://example.com',
184             'WebSocket-Protocol':'',
185         }
186     ),
187     (  # Wrong port number format
188         80,
189         '/demo',
190         {
191             'Upgrade':'WebSocket',
192             'Connection':'Upgrade',
193             'Host':'example.com:0x50',
194             'Origin':'http://example.com',
195             'WebSocket-Protocol':'sample',
196         }
197     ),
198     (  # Header/connection port mismatch
199         8080,
200         '/demo',
201         {
202             'Upgrade':'WebSocket',
203             'Connection':'Upgrade',
204             'Host':'example.com',
205             'Origin':'http://example.com',
206             'WebSocket-Protocol':'sample',
207         }
208     ),
209     (  # Illegal WebSocket-Protocol
210         80,
211         '/demo',
212         {
213             'Upgrade':'WebSocket',
214             'Connection':'Upgrade',
215             'Host':'example.com',
216             'Origin':'http://example.com',
217             'WebSocket-Protocol':'illegal\x09protocol',
218         }
219     ),
220 )
221
222 _STRICTLY_GOOD_REQUESTS = (
223     (
224         'GET /demo HTTP/1.1\r\n',
225         'Upgrade: WebSocket\r\n',
226         'Connection: Upgrade\r\n',
227         'Host: example.com\r\n',
228         'Origin: http://example.com\r\n',
229         '\r\n',
230     ),
231     (  # WebSocket-Protocol
232         'GET /demo HTTP/1.1\r\n',
233         'Upgrade: WebSocket\r\n',
234         'Connection: Upgrade\r\n',
235         'Host: example.com\r\n',
236         'Origin: http://example.com\r\n',
237         'WebSocket-Protocol: sample\r\n',
238         '\r\n',
239     ),
240     (  # WebSocket-Protocol and Cookie
241         'GET /demo HTTP/1.1\r\n',
242         'Upgrade: WebSocket\r\n',
243         'Connection: Upgrade\r\n',
244         'Host: example.com\r\n',
245         'Origin: http://example.com\r\n',
246         'WebSocket-Protocol: sample\r\n',
247         'Cookie: xyz\r\n'
248         '\r\n',
249     ),
250     (  # Cookie
251         'GET /demo HTTP/1.1\r\n',
252         'Upgrade: WebSocket\r\n',
253         'Connection: Upgrade\r\n',
254         'Host: example.com\r\n',
255         'Origin: http://example.com\r\n',
256         'Cookie: abc/xyz\r\n'
257         'Cookie2: $Version=1\r\n'
258         'Cookie: abc\r\n'
259         '\r\n',
260     ),
261 )
262
263 _NOT_STRICTLY_GOOD_REQUESTS = (
264     (  # Extra space after GET
265         'GET  /demo HTTP/1.1\r\n',
266         'Upgrade: WebSocket\r\n',
267         'Connection: Upgrade\r\n',
268         'Host: example.com\r\n',
269         'Origin: http://example.com\r\n',
270         '\r\n',
271     ),
272     (  # Resource name doesn't stat with '/'
273         'GET demo HTTP/1.1\r\n',
274         'Upgrade: WebSocket\r\n',
275         'Connection: Upgrade\r\n',
276         'Host: example.com\r\n',
277         'Origin: http://example.com\r\n',
278         '\r\n',
279     ),
280     (  # No space after :
281         'GET /demo HTTP/1.1\r\n',
282         'Upgrade:WebSocket\r\n',
283         'Connection: Upgrade\r\n',
284         'Host: example.com\r\n',
285         'Origin: http://example.com\r\n',
286         '\r\n',
287     ),
288     (  # Lower case Upgrade header
289         'GET /demo HTTP/1.1\r\n',
290         'upgrade: WebSocket\r\n',
291         'Connection: Upgrade\r\n',
292         'Host: example.com\r\n',
293         'Origin: http://example.com\r\n',
294         '\r\n',
295     ),
296     (  # Connection comes before Upgrade
297         'GET /demo HTTP/1.1\r\n',
298         'Connection: Upgrade\r\n',
299         'Upgrade: WebSocket\r\n',
300         'Host: example.com\r\n',
301         'Origin: http://example.com\r\n',
302         '\r\n',
303     ),
304     (  # Origin comes before Host
305         'GET /demo HTTP/1.1\r\n',
306         'Upgrade: WebSocket\r\n',
307         'Connection: Upgrade\r\n',
308         'Origin: http://example.com\r\n',
309         'Host: example.com\r\n',
310         '\r\n',
311     ),
312     (  # Host continued to the next line
313         'GET /demo HTTP/1.1\r\n',
314         'Upgrade: WebSocket\r\n',
315         'Connection: Upgrade\r\n',
316         'Host: example\r\n',
317         ' .com\r\n',
318         'Origin: http://example.com\r\n',
319         '\r\n',
320     ),
321     ( # Cookie comes before WebSocket-Protocol
322         'GET /demo HTTP/1.1\r\n',
323         'Upgrade: WebSocket\r\n',
324         'Connection: Upgrade\r\n',
325         'Host: example.com\r\n',
326         'Origin: http://example.com\r\n',
327         'Cookie: xyz\r\n'
328         'WebSocket-Protocol: sample\r\n',
329         '\r\n',
330     ),
331     (  # Unknown header
332         'GET /demo HTTP/1.1\r\n',
333         'Upgrade: WebSocket\r\n',
334         'Connection: Upgrade\r\n',
335         'Host: example.com\r\n',
336         'Origin: http://example.com\r\n',
337         'Content-Type: text/html\r\n'
338         '\r\n',
339     ),
340     (  # Cookie with continuation lines
341         'GET /demo HTTP/1.1\r\n',
342         'Upgrade: WebSocket\r\n',
343         'Connection: Upgrade\r\n',
344         'Host: example.com\r\n',
345         'Origin: http://example.com\r\n',
346         'Cookie: xyz\r\n',
347         ' abc\r\n',
348         ' defg\r\n',
349         '\r\n',
350     ),
351     (  # Wrong-case cookie
352         'GET /demo HTTP/1.1\r\n',
353         'Upgrade: WebSocket\r\n',
354         'Connection: Upgrade\r\n',
355         'Host: example.com\r\n',
356         'Origin: http://example.com\r\n',
357         'cookie: abc/xyz\r\n'
358         '\r\n',
359     ),
360     (  # Cookie, no space after colon
361         'GET /demo HTTP/1.1\r\n',
362         'Upgrade: WebSocket\r\n',
363         'Connection: Upgrade\r\n',
364         'Host: example.com\r\n',
365         'Origin: http://example.com\r\n',
366         'Cookie:abc/xyz\r\n'
367         '\r\n',
368     ),
369 )
370
371
372 def _create_request(request_def):
373     conn = mock.MockConn('')
374     conn.local_addr = ('0.0.0.0', request_def[0])
375     return mock.MockRequest(
376             uri=request_def[1],
377             headers_in=request_def[2],
378             connection=conn)
379
380
381 def _create_get_memorized_lines(lines):
382     def get_memorized_lines():
383         return lines
384     return get_memorized_lines
385
386
387 def _create_requests_with_lines(request_lines_set):
388     requests = []
389     for lines in request_lines_set:
390         request = _create_request(_GOOD_REQUEST)
391         request.connection.get_memorized_lines = _create_get_memorized_lines(
392                 lines)
393         requests.append(request)
394     return requests
395
396
397 class HandshakerTest(unittest.TestCase):
398     def test_validate_protocol(self):
399         handshake._validate_protocol('sample')  # should succeed.
400         handshake._validate_protocol('Sample')  # should succeed.
401         handshake._validate_protocol('sample\x20protocol')  # should succeed.
402         handshake._validate_protocol('sample\x7eprotocol')  # should succeed.
403         self.assertRaises(handshake.HandshakeError,
404                           handshake._validate_protocol,
405                           '')
406         self.assertRaises(handshake.HandshakeError,
407                           handshake._validate_protocol,
408                           'sample\x19protocol')
409         self.assertRaises(handshake.HandshakeError,
410                           handshake._validate_protocol,
411                           'sample\x7fprotocol')
412         self.assertRaises(handshake.HandshakeError,
413                           handshake._validate_protocol,
414                           # "Japan" in Japanese
415                           u'\u65e5\u672c')
416
417     def test_good_request_default_port(self):
418         request = _create_request(_GOOD_REQUEST)
419         handshaker = handshake.Handshaker(request,
420                                           mock.MockDispatcher())
421         handshaker.do_handshake()
422         self.assertEqual(_GOOD_RESPONSE_DEFAULT_PORT,
423                          request.connection.written_data())
424         self.assertEqual('/demo', request.ws_resource)
425         self.assertEqual('http://example.com', request.ws_origin)
426         self.assertEqual('ws://example.com/demo', request.ws_location)
427         self.assertEqual('sample', request.ws_protocol)
428
429     def test_good_request_secure_default_port(self):
430         request = _create_request(_GOOD_REQUEST)
431         request.connection.local_addr = ('0.0.0.0', 443)
432         request.is_https_ = True
433         handshaker = handshake.Handshaker(request,
434                                           mock.MockDispatcher())
435         handshaker.do_handshake()
436         self.assertEqual(_GOOD_RESPONSE_SECURE,
437                          request.connection.written_data())
438         self.assertEqual('sample', request.ws_protocol)
439
440     def test_good_request_nondefault_port(self):
441         request = _create_request(_GOOD_REQUEST_NONDEFAULT_PORT)
442         handshaker = handshake.Handshaker(request,
443                                           mock.MockDispatcher())
444         handshaker.do_handshake()
445         self.assertEqual(_GOOD_RESPONSE_NONDEFAULT_PORT,
446                          request.connection.written_data())
447         self.assertEqual('sample', request.ws_protocol)
448
449     def test_good_request_secure_non_default_port(self):
450         request = _create_request(_GOOD_REQUEST_NONDEFAULT_PORT)
451         request.is_https_ = True
452         handshaker = handshake.Handshaker(request,
453                                           mock.MockDispatcher())
454         handshaker.do_handshake()
455         self.assertEqual(_GOOD_RESPONSE_SECURE_NONDEF,
456                          request.connection.written_data())
457         self.assertEqual('sample', request.ws_protocol)
458
459     def test_good_request_default_no_protocol(self):
460         request = _create_request(_GOOD_REQUEST_NO_PROTOCOL)
461         handshaker = handshake.Handshaker(request,
462                                           mock.MockDispatcher())
463         handshaker.do_handshake()
464         self.assertEqual(_GOOD_RESPONSE_NO_PROTOCOL,
465                          request.connection.written_data())
466         self.assertEqual(None, request.ws_protocol)
467
468     def test_good_request_optional_headers(self):
469         request = _create_request(_GOOD_REQUEST_WITH_OPTIONAL_HEADERS)
470         handshaker = handshake.Handshaker(request,
471                                           mock.MockDispatcher())
472         handshaker.do_handshake()
473         self.assertEqual('AValue',
474                          request.headers_in['AKey'])
475         self.assertEqual('',
476                          request.headers_in['EmptyValue'])
477
478     def test_bad_requests(self):
479         for request in map(_create_request, _BAD_REQUESTS):
480             handshaker = handshake.Handshaker(request,
481                                               mock.MockDispatcher())
482             self.assertRaises(handshake.HandshakeError, handshaker.do_handshake)
483
484     def test_strictly_good_requests(self):
485         for request in _create_requests_with_lines(_STRICTLY_GOOD_REQUESTS):
486             strict_handshaker = handshake.Handshaker(request,
487                                                      mock.MockDispatcher(),
488                                                      True)
489             strict_handshaker.do_handshake()
490
491     def test_not_strictly_good_requests(self):
492         for request in _create_requests_with_lines(_NOT_STRICTLY_GOOD_REQUESTS):
493             strict_handshaker = handshake.Handshaker(request,
494                                                      mock.MockDispatcher(),
495                                                      True)
496             self.assertRaises(handshake.HandshakeError,
497                               strict_handshaker.do_handshake)
498
499
500
501 if __name__ == '__main__':
502     unittest.main()
503
504
505 # vi:sts=4 sw=4 et