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