1#!/usr/bin/env python
2#
3# Copyright 2012, 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
36import unittest
37
38import set_sys_path  # Update sys.path to locate mod_pywebsocket module.
39
40from mod_pywebsocket.common import ExtensionParameter
41from mod_pywebsocket.common import ExtensionParsingException
42from mod_pywebsocket.common import format_extensions
43from mod_pywebsocket.common import parse_extensions
44from mod_pywebsocket.handshake._base import HandshakeException
45from mod_pywebsocket.handshake._base import validate_subprotocol
46
47
48class HandshakerTest(unittest.TestCase):
49    """A unittest for handshake module."""
50
51    def test_validate_subprotocol(self):
52        # should succeed.
53        validate_subprotocol('sample', hixie=True)
54        validate_subprotocol('Sample', hixie=True)
55        validate_subprotocol('sample\x7eprotocol', hixie=True)
56        validate_subprotocol('sample\x20protocol', hixie=True)
57        validate_subprotocol('sample', hixie=False)
58        validate_subprotocol('Sample', hixie=False)
59        validate_subprotocol('sample\x7eprotocol', hixie=False)
60
61        # should fail.
62        self.assertRaises(HandshakeException,
63                          validate_subprotocol,
64                          '',
65                          hixie=True)
66        self.assertRaises(HandshakeException,
67                          validate_subprotocol,
68                          'sample\x19protocol',
69                          hixie=True)
70        self.assertRaises(HandshakeException,
71                          validate_subprotocol,
72                          'sample\x7fprotocol',
73                          hixie=True)
74        self.assertRaises(HandshakeException,
75                          validate_subprotocol,
76                          # "Japan" in Japanese
77                          u'\u65e5\u672c',
78                          hixie=True)
79        self.assertRaises(HandshakeException,
80                          validate_subprotocol,
81                          '',
82                          hixie=False)
83        self.assertRaises(HandshakeException,
84                          validate_subprotocol,
85                          'sample\x09protocol',
86                          hixie=False)
87        self.assertRaises(HandshakeException,
88                          validate_subprotocol,
89                          'sample\x19protocol',
90                          hixie=False)
91        self.assertRaises(HandshakeException,
92                          validate_subprotocol,
93                          'sample\x20protocol',
94                          hixie=False)
95        self.assertRaises(HandshakeException,
96                          validate_subprotocol,
97                          'sample\x7fprotocol',
98                          hixie=False)
99        self.assertRaises(HandshakeException,
100                          validate_subprotocol,
101                          # "Japan" in Japanese
102                          u'\u65e5\u672c',
103                          hixie=False)
104
105
106_TEST_TOKEN_EXTENSION_DATA = [
107    ('foo', [('foo', [])]),
108    ('foo; bar', [('foo', [('bar', None)])]),
109    ('foo; bar=baz', [('foo', [('bar', 'baz')])]),
110    ('foo; bar=baz; car=cdr', [('foo', [('bar', 'baz'), ('car', 'cdr')])]),
111    ('foo; bar=baz, car; cdr',
112     [('foo', [('bar', 'baz')]), ('car', [('cdr', None)])]),
113    ('a, b, c, d',
114     [('a', []), ('b', []), ('c', []), ('d', [])]),
115    ]
116
117
118_TEST_QUOTED_EXTENSION_DATA = [
119    ('foo; bar=""', [('foo', [('bar', '')])]),
120    ('foo; bar=" baz "', [('foo', [('bar', ' baz ')])]),
121    ('foo; bar=",baz;"', [('foo', [('bar', ',baz;')])]),
122    ('foo; bar="\\\r\\\nbaz"', [('foo', [('bar', '\r\nbaz')])]),
123    ('foo; bar="\\"baz"', [('foo', [('bar', '"baz')])]),
124    ('foo; bar="\xbbbaz"', [('foo', [('bar', '\xbbbaz')])]),
125    ]
126
127
128_TEST_REDUNDANT_TOKEN_EXTENSION_DATA = [
129    ('foo \t ', [('foo', [])]),
130    ('foo; \r\n bar', [('foo', [('bar', None)])]),
131    ('foo; bar=\r\n \r\n baz', [('foo', [('bar', 'baz')])]),
132    ('foo ;bar = baz ', [('foo', [('bar', 'baz')])]),
133    ('foo,bar,,baz', [('foo', []), ('bar', []), ('baz', [])]),
134    ]
135
136
137_TEST_REDUNDANT_QUOTED_EXTENSION_DATA = [
138    ('foo; bar="\r\n \r\n baz"', [('foo', [('bar', '  baz')])]),
139    ]
140
141
142class ExtensionsParserTest(unittest.TestCase):
143
144    def _verify_extension_list(self, expected_list, actual_list):
145        """Verifies that ExtensionParameter objects in actual_list have the
146        same members as extension definitions in expected_list. Extension
147        definition used in this test is a pair of an extension name and a
148        parameter dictionary.
149        """
150
151        self.assertEqual(len(expected_list), len(actual_list))
152        for expected, actual in zip(expected_list, actual_list):
153            (name, parameters) = expected
154            self.assertEqual(name, actual._name)
155            self.assertEqual(parameters, actual._parameters)
156
157    def test_parse(self):
158        for formatted_string, definition in _TEST_TOKEN_EXTENSION_DATA:
159            self._verify_extension_list(
160                definition, parse_extensions(formatted_string,
161                                             allow_quoted_string=False))
162
163        for formatted_string, unused_definition in _TEST_QUOTED_EXTENSION_DATA:
164            self.assertRaises(
165                ExtensionParsingException, parse_extensions,
166                formatted_string, False)
167
168    def test_parse_with_allow_quoted_string(self):
169        for formatted_string, definition in _TEST_TOKEN_EXTENSION_DATA:
170            self._verify_extension_list(
171                definition, parse_extensions(formatted_string,
172                                             allow_quoted_string=True))
173
174        for formatted_string, definition in _TEST_QUOTED_EXTENSION_DATA:
175            self._verify_extension_list(
176                definition, parse_extensions(formatted_string,
177                                             allow_quoted_string=True))
178
179    def test_parse_redundant_data(self):
180        for (formatted_string,
181             definition) in _TEST_REDUNDANT_TOKEN_EXTENSION_DATA:
182            self._verify_extension_list(
183                definition, parse_extensions(formatted_string,
184                                             allow_quoted_string=False))
185
186        for (formatted_string,
187             definition) in _TEST_REDUNDANT_QUOTED_EXTENSION_DATA:
188            self.assertRaises(
189                ExtensionParsingException, parse_extensions,
190                formatted_string, False)
191
192    def test_parse_redundant_data_with_allow_quoted_string(self):
193        for (formatted_string,
194             definition) in _TEST_REDUNDANT_TOKEN_EXTENSION_DATA:
195            self._verify_extension_list(
196                definition, parse_extensions(formatted_string,
197                                             allow_quoted_string=True))
198
199        for (formatted_string,
200             definition) in _TEST_REDUNDANT_QUOTED_EXTENSION_DATA:
201            self._verify_extension_list(
202                definition, parse_extensions(formatted_string,
203                                             allow_quoted_string=True))
204
205    def test_parse_bad_data(self):
206        _TEST_BAD_EXTENSION_DATA = [
207            ('foo; ; '),
208            ('foo; a a'),
209            ('foo foo'),
210            (',,,'),
211            ('foo; bar='),
212            ('foo; bar="hoge'),
213            ('foo; bar="a\r"'),
214            ('foo; bar="\\\xff"'),
215            ('foo; bar=\ra'),
216            ]
217
218        for formatted_string in _TEST_BAD_EXTENSION_DATA:
219            self.assertRaises(
220                ExtensionParsingException, parse_extensions, formatted_string)
221
222
223class FormatExtensionsTest(unittest.TestCase):
224
225    def test_format_extensions(self):
226        for formatted_string, definitions in _TEST_TOKEN_EXTENSION_DATA:
227            extensions = []
228            for definition in definitions:
229                (name, parameters) = definition
230                extension = ExtensionParameter(name)
231                extension._parameters = parameters
232                extensions.append(extension)
233            self.assertEqual(
234                formatted_string, format_extensions(extensions))
235
236
237if __name__ == '__main__':
238    unittest.main()
239
240
241# vi:sts=4 sw=4 et
242