pywebsocket-0.7.9/0000750023300700116100000000000012121621356013265 5ustar tyoshinoengpywebsocket-0.7.9/src/0000750023300700116100000000000012121621357014055 5ustar tyoshinoengpywebsocket-0.7.9/src/test/0000750023300700116100000000000012121621357015034 5ustar tyoshinoengpywebsocket-0.7.9/src/test/cert/0000750023300700116100000000000012121621357015771 5ustar tyoshinoengpywebsocket-0.7.9/src/test/cert/cacert.pem0000640023300700116100000000176111763631226017752 0ustar tyoshinoeng-----BEGIN CERTIFICATE----- MIICvDCCAiWgAwIBAgIJAKqVghkGF1rSMA0GCSqGSIb3DQEBBQUAMEkxCzAJBgNV BAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEUMBIGA1UEChMLcHl3ZWJzb2NrZXQxFDAS BgNVBAMTC3B5d2Vic29ja2V0MB4XDTEyMDYwNjA3MjQzM1oXDTM5MTAyMzA3MjQz M1owSTELMAkGA1UEBhMCSlAxDjAMBgNVBAgTBVRva3lvMRQwEgYDVQQKEwtweXdl YnNvY2tldDEUMBIGA1UEAxMLcHl3ZWJzb2NrZXQwgZ8wDQYJKoZIhvcNAQEBBQAD gY0AMIGJAoGBAKoSEW2biQxVrMMKdn/8PJzDYiSXDPR9WQbLRRQ1Gm5jkCYiahXW u2CbTThfPPfi2NHA3I+HlT7gO9yR7RVUvN6ISUzGwXDEq4f4UNqtQOhQaqqK+CZ9 LO/BhO/YYfNrbSPlYzHUKaT9ese7xO9VzVKLW+qUf2Mjh4/+SzxBDNP7AgMBAAGj gaswgagwHQYDVR0OBBYEFOsWdxCSuyhwaZeab6BoTho3++bzMHkGA1UdIwRyMHCA FOsWdxCSuyhwaZeab6BoTho3++bzoU2kSzBJMQswCQYDVQQGEwJKUDEOMAwGA1UE CBMFVG9reW8xFDASBgNVBAoTC3B5d2Vic29ja2V0MRQwEgYDVQQDEwtweXdlYnNv Y2tldIIJAKqVghkGF1rSMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEA gsMI1WEYqNw/jhUIdrTBcCxJ0X6hJvA9ziKANVm1Rs+4P3YDArkQ8bCr6xY+Kw7s Zp0yE7dM8GMdi+DU6hL3t3E5eMkTS1yZr9WCK4f2RLo+et98selZydpHemF3DJJ3 gAj8Sx4LBaG8Cb/WnEMPv3MxG3fBE5favF6V4jU07hQ= -----END CERTIFICATE----- pywebsocket-0.7.9/src/test/cert/key.pem0000640023300700116100000000157311763631226017302 0ustar tyoshinoeng-----BEGIN RSA PRIVATE KEY----- MIICXgIBAAKBgQDeEM46WgSkHCmTXCOCGvIGAeYrpA/dd0l2iQOiId4Edcbi3fs1 Jzqiko4SYis+H/R437aUyyfWy9Y311wI8Ak+yc4kLQDJ30rgmeX7I6ni1sk9lvoB iN5aibDPA2dvBIYd72IcVakHmi5mKnNbTGID+YKD22i/uEsLi5MRuFRzewIDAQAB AoGBAIuCuV1Vcnb7rm8CwtgZP5XgmY8vSjxTldafa6XvawEYUTP0S77v/1llg1Yv UIV+I+PQgG9oVoYOl22LoimHS/Z3e1fsot5tDYszGe8/Gkst4oaReSoxvBUa6WXp QSo7YFCajuHtE+W/gzF+UHbdzzXIDjQZ314LNF5t+4UnsEPBAkEA+girImqWoM2t 3UR8f8oekERwsmEMf9DH5YpH4cvUnvI+kwesC/r2U8Sho++fyEMUNm7aIXGqNLga ogAM+4NX4QJBAONdSxSay22egTGNoIhLndljWkuOt/9FWj2klf/4QxD4blMJQ5Oq QdOGAh7nVQjpPLQ5D7CBVAKpGM2CD+QJBtsCQEP2kz35pxPylG3urcC2mfQxBkkW ZCViBNP58GwJ0bOauTOSBEwFXWuLqTw8aDwxL49UNmqc0N0fpe2fAehj3UECQQCm FH/DjU8Lw7ybddjNtm6XXPuYNagxz3cbkB4B3FchDleIUDwMoVF0MW9bI5/54mV1 QDk1tUKortxvQZJaAD4BAkEAhGOHQqPd6bBBoFBvpaLzPJMxwLKrB+Wtkq/QlC72 ClRiMn2g8SALiIL3BDgGXKcKE/Wy7jo/af/JCzQ/cPqt/A== -----END RSA PRIVATE KEY----- pywebsocket-0.7.9/src/test/cert/client_cert.p120000640023300700116100000000502611763631226020623 0ustar tyoshinoeng0 0  *H   0 0 *H 0|0u *H 0 *H  0 M&Hցo WQ BZUi(6z P?jTӈ9*zBNw Yi\)@olяp/W9 0PwvnФL&u'wG8Y`'=' d,< hrqo2>0#Io=((v՗;qK B_L}w1EWT1j~Z߰uLqAR,Cћ_>H]EP k ;7X7*#J!gqh1+~J2ieN^e!8ź:Qu<f 'B4%64 nk;Gi?L-ݰ_Лta[UJEVz&UŔ'yrfXRZ&H !MW+ng`$ Jj]|?H_@fsIf#r'q=qU:nu 4B[C;1_=x]83 2Ud{Ȣe#ؐ:GܬJ-Kl3R{1Nf2u +wu ^xPXnAuG_^侱CԻZ`ob{E sȎL)~+/8.i, Kz}>] la&Y3I%pj+}unDW&X:ZUfPБߙ *&kjdh rc19k$F}uGk1O0g[\pPiM@wl ATwLJ{q0Q3g[7$rgnF\P NDNoնESWqVD[Y"K~-N\Te [Fzt\oKP?e+Tyˀ+Gʁ1n{$h(R5}D7;"لҟ^P Ue+5S+rh:dl1ル~"*\Ab=.>}ٷF$`tnZ.-_SuoKIA$4x+E[M{uoױ~ ʶ"""IhJSS@^ `wd45 5=ujIx2Ga%G`,Lal6^+ $p0z_e.S({gWQ.xW9Vň^+AhA(<\ڋD@X X]ƠtҀ5)]gҥMkR;37-f2^׷Y+GXWbR$1$L p 15L2iF_@p^x( 8 b%t&A(}9  ,| 7*OK;r-l$n0hظEsD% _|N%i"aIV4XCvЪpAgY% 0* *H 00 *H  00 *H  0w$&qumL/zҊ'ٞ|`e}q63~6l,3f?Ȳjd0FΊAۇoY/Rv#^1/^oj@"aVPe%15GJd%.@cJS0t;cJp=>7tG)lH}L pg hQ>^%Z^; ˹O= ^^HZK>j)5-ȳOb *fCAqÁg^ |;_UZQpywebsocket-0.7.9/src/test/cert/cert.pem0000640023300700116100000000601211763631226017440 0ustar tyoshinoengCertificate: Data: Version: 3 (0x2) Serial Number: 1 (0x1) Signature Algorithm: sha1WithRSAEncryption Issuer: C=JP, ST=Tokyo, O=pywebsocket, CN=pywebsocket Validity Not Before: Jun 6 07:25:08 2012 GMT Not After : Oct 23 07:25:08 2039 GMT Subject: C=JP, ST=Tokyo, O=pywebsocket, CN=pywebsocket Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public Key: (1024 bit) Modulus (1024 bit): 00:de:10:ce:3a:5a:04:a4:1c:29:93:5c:23:82:1a: f2:06:01:e6:2b:a4:0f:dd:77:49:76:89:03:a2:21: de:04:75:c6:e2:dd:fb:35:27:3a:a2:92:8e:12:62: 2b:3e:1f:f4:78:df:b6:94:cb:27:d6:cb:d6:37:d7: 5c:08:f0:09:3e:c9:ce:24:2d:00:c9:df:4a:e0:99: e5:fb:23:a9:e2:d6:c9:3d:96:fa:01:88:de:5a:89: b0:cf:03:67:6f:04:86:1d:ef:62:1c:55:a9:07:9a: 2e:66:2a:73:5b:4c:62:03:f9:82:83:db:68:bf:b8: 4b:0b:8b:93:11:b8:54:73:7b Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: CA:FALSE Netscape Cert Type: SSL Server Netscape Comment: OpenSSL Generated Certificate X509v3 Subject Key Identifier: 82:A1:73:8B:16:0C:7C:E4:D3:46:95:13:95:1A:32:C1:84:E9:06:00 X509v3 Authority Key Identifier: keyid:EB:16:77:10:92:BB:28:70:69:97:9A:6F:A0:68:4E:1A:37:FB:E6:F3 Signature Algorithm: sha1WithRSAEncryption 6b:b3:46:29:02:df:b0:c8:8e:c4:d7:7f:a0:1e:0d:1a:eb:2f: df:d1:48:57:36:5f:95:8c:1b:f0:51:d6:52:e7:8d:84:3b:9f: d8:ed:22:9c:aa:bd:ee:9b:90:1d:84:a3:4c:0b:cb:eb:64:73: ba:f7:15:ce:da:5f:db:8b:15:07:a6:28:7f:b9:8c:11:9b:64: d3:f1:be:52:4f:c3:d8:58:fe:de:56:63:63:3b:51:ed:a7:81: f9:05:51:70:63:32:09:0e:94:7e:05:fe:a1:56:18:34:98:d5: 99:1e:4e:27:38:89:90:6a:e5:ce:60:35:01:f5:de:34:60:b1: cb:ae -----BEGIN CERTIFICATE----- MIICmDCCAgGgAwIBAgIBATANBgkqhkiG9w0BAQUFADBJMQswCQYDVQQGEwJKUDEO MAwGA1UECBMFVG9reW8xFDASBgNVBAoTC3B5d2Vic29ja2V0MRQwEgYDVQQDEwtw eXdlYnNvY2tldDAeFw0xMjA2MDYwNzI1MDhaFw0zOTEwMjMwNzI1MDhaMEkxCzAJ BgNVBAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEUMBIGA1UEChMLcHl3ZWJzb2NrZXQx FDASBgNVBAMTC3B5d2Vic29ja2V0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB gQDeEM46WgSkHCmTXCOCGvIGAeYrpA/dd0l2iQOiId4Edcbi3fs1Jzqiko4SYis+ H/R437aUyyfWy9Y311wI8Ak+yc4kLQDJ30rgmeX7I6ni1sk9lvoBiN5aibDPA2dv BIYd72IcVakHmi5mKnNbTGID+YKD22i/uEsLi5MRuFRzewIDAQABo4GPMIGMMAkG A1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMCwGCWCGSAGG+EIBDQQfFh1PcGVu U1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUgqFzixYMfOTTRpUT lRoywYTpBgAwHwYDVR0jBBgwFoAU6xZ3EJK7KHBpl5pvoGhOGjf75vMwDQYJKoZI hvcNAQEFBQADgYEAa7NGKQLfsMiOxNd/oB4NGusv39FIVzZflYwb8FHWUueNhDuf 2O0inKq97puQHYSjTAvL62RzuvcVztpf24sVB6Yof7mMEZtk0/G+Uk/D2Fj+3lZj YztR7aeB+QVRcGMyCQ6UfgX+oVYYNJjVmR5OJziJkGrlzmA1AfXeNGCxy64= -----END CERTIFICATE----- pywebsocket-0.7.9/src/test/__init__.py0000640023300700116100000000000011525067446017146 0ustar tyoshinoengpywebsocket-0.7.9/src/test/test_mock.py0000750023300700116100000001206011647752737017423 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for mock module.""" import Queue import threading import unittest import set_sys_path # Update sys.path to locate mod_pywebsocket module. from test import mock class MockConnTest(unittest.TestCase): """A unittest for MockConn class.""" def setUp(self): self._conn = mock.MockConn('ABC\r\nDEFG\r\n\r\nHIJK') def test_readline(self): self.assertEqual('ABC\r\n', self._conn.readline()) self.assertEqual('DEFG\r\n', self._conn.readline()) self.assertEqual('\r\n', self._conn.readline()) self.assertEqual('HIJK', self._conn.readline()) self.assertEqual('', self._conn.readline()) def test_read(self): self.assertEqual('ABC\r\nD', self._conn.read(6)) self.assertEqual('EFG\r\n\r\nHI', self._conn.read(9)) self.assertEqual('JK', self._conn.read(10)) self.assertEqual('', self._conn.read(10)) def test_read_and_readline(self): self.assertEqual('ABC\r\nD', self._conn.read(6)) self.assertEqual('EFG\r\n', self._conn.readline()) self.assertEqual('\r\nHIJK', self._conn.read(9)) self.assertEqual('', self._conn.readline()) def test_write(self): self._conn.write('Hello\r\n') self._conn.write('World\r\n') self.assertEqual('Hello\r\nWorld\r\n', self._conn.written_data()) class MockBlockingConnTest(unittest.TestCase): """A unittest for MockBlockingConn class.""" def test_read(self): """Tests that data put to MockBlockingConn by put_bytes method can be read from it. """ class LineReader(threading.Thread): """A test class that launches a thread, calls readline on the specified conn repeatedly and puts the read data to the specified queue. """ def __init__(self, conn, queue): threading.Thread.__init__(self) self._queue = queue self._conn = conn self.setDaemon(True) self.start() def run(self): while True: data = self._conn.readline() self._queue.put(data) conn = mock.MockBlockingConn() queue = Queue.Queue() reader = LineReader(conn, queue) self.failUnless(queue.empty()) conn.put_bytes('Foo bar\r\n') read = queue.get() self.assertEqual('Foo bar\r\n', read) class MockTableTest(unittest.TestCase): """A unittest for MockTable class.""" def test_create_from_dict(self): table = mock.MockTable({'Key': 'Value'}) self.assertEqual('Value', table.get('KEY')) self.assertEqual('Value', table['key']) def test_create_from_list(self): table = mock.MockTable([('Key', 'Value')]) self.assertEqual('Value', table.get('KEY')) self.assertEqual('Value', table['key']) def test_create_from_tuple(self): table = mock.MockTable((('Key', 'Value'),)) self.assertEqual('Value', table.get('KEY')) self.assertEqual('Value', table['key']) def test_set_and_get(self): table = mock.MockTable() self.assertEqual(None, table.get('Key')) table['Key'] = 'Value' self.assertEqual('Value', table.get('Key')) self.assertEqual('Value', table.get('key')) self.assertEqual('Value', table.get('KEY')) self.assertEqual('Value', table['Key']) self.assertEqual('Value', table['key']) self.assertEqual('Value', table['KEY']) if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/mock.py0000640023300700116100000001530012121531475016340 0ustar tyoshinoeng# Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Mocks for testing. """ import Queue import threading from mod_pywebsocket import common from mod_pywebsocket.stream import StreamHixie75 class _MockConnBase(object): """Base class of mocks for mod_python.apache.mp_conn. This enables tests to check what is written to a (mock) mp_conn. """ def __init__(self): self._write_data = [] self.remote_addr = 'fake_address' def write(self, data): """Override mod_python.apache.mp_conn.write.""" self._write_data.append(data) def written_data(self): """Get bytes written to this mock.""" return ''.join(self._write_data) class MockConn(_MockConnBase): """Mock for mod_python.apache.mp_conn. This enables tests to specify what should be read from a (mock) mp_conn as well as to check what is written to it. """ def __init__(self, read_data): """Constructs an instance. Args: read_data: bytes that should be returned when read* methods are called. """ _MockConnBase.__init__(self) self._read_data = read_data self._read_pos = 0 def readline(self): """Override mod_python.apache.mp_conn.readline.""" if self._read_pos >= len(self._read_data): return '' end_index = self._read_data.find('\n', self._read_pos) + 1 if not end_index: end_index = len(self._read_data) return self._read_up_to(end_index) def read(self, length): """Override mod_python.apache.mp_conn.read.""" if self._read_pos >= len(self._read_data): return '' end_index = min(len(self._read_data), self._read_pos + length) return self._read_up_to(end_index) def _read_up_to(self, end_index): line = self._read_data[self._read_pos:end_index] self._read_pos = end_index return line class MockBlockingConn(_MockConnBase): """Blocking mock for mod_python.apache.mp_conn. This enables tests to specify what should be read from a (mock) mp_conn as well as to check what is written to it. Callers of read* methods will block if there is no bytes available. """ def __init__(self): _MockConnBase.__init__(self) self._queue = Queue.Queue() def readline(self): """Override mod_python.apache.mp_conn.readline.""" line = '' while True: c = self._queue.get() line += c if c == '\n': return line def read(self, length): """Override mod_python.apache.mp_conn.read.""" data = '' for unused in range(length): data += self._queue.get() return data def put_bytes(self, bytes): """Put bytes to be read from this mock. Args: bytes: bytes to be read. """ for byte in bytes: self._queue.put(byte) class MockTable(dict): """Mock table. This mimics mod_python mp_table. Note that only the methods used by tests are overridden. """ def __init__(self, copy_from={}): if isinstance(copy_from, dict): copy_from = copy_from.items() for key, value in copy_from: self.__setitem__(key, value) def __getitem__(self, key): return super(MockTable, self).__getitem__(key.lower()) def __setitem__(self, key, value): super(MockTable, self).__setitem__(key.lower(), value) def get(self, key, def_value=None): return super(MockTable, self).get(key.lower(), def_value) class MockRequest(object): """Mock request. This mimics mod_python request. """ def __init__(self, uri=None, headers_in={}, connection=None, method='GET', protocol='HTTP/1.1', is_https=False): """Construct an instance. Arguments: uri: URI of the request. headers_in: Request headers. connection: Connection used for the request. method: request method. is_https: Whether this request is over SSL. See the document of mod_python Request for details. """ self.uri = uri self.unparsed_uri = uri self.connection = connection self.method = method self.protocol = protocol self.headers_in = MockTable(headers_in) # self.is_https_ needs to be accessible from tests. To avoid name # conflict with self.is_https(), it is named as such. self.is_https_ = is_https self.ws_stream = StreamHixie75(self, True) self.ws_close_code = None self.ws_close_reason = None self.ws_version = common.VERSION_HYBI00 self.ws_deflate = False self.drain_received_data_called = False def is_https(self): """Return whether this request is over SSL.""" return self.is_https_ def _drain_received_data(self): self.drain_received_data_called = True class MockDispatcher(object): """Mock for dispatch.Dispatcher.""" def __init__(self): self.do_extra_handshake_called = False def do_extra_handshake(self, conn_context): self.do_extra_handshake_called = True def transfer_data(self, conn_context): pass # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/test_stream.py0000750023300700116100000000547011566670120017755 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for stream module.""" import unittest import set_sys_path # Update sys.path to locate mod_pywebsocket module. from mod_pywebsocket import common from mod_pywebsocket import stream class StreamTest(unittest.TestCase): """A unittest for stream module.""" def test_create_header(self): # more, rsv1, ..., rsv4 are all true header = stream.create_header(common.OPCODE_TEXT, 1, 1, 1, 1, 1, 1) self.assertEqual('\xf1\x81', header) # Maximum payload size header = stream.create_header( common.OPCODE_TEXT, (1 << 63) - 1, 0, 0, 0, 0, 0) self.assertEqual('\x01\x7f\x7f\xff\xff\xff\xff\xff\xff\xff', header) # Invalid opcode 0x10 self.assertRaises(ValueError, stream.create_header, 0x10, 0, 0, 0, 0, 0, 0) # Invalid value 0xf passed to more parameter self.assertRaises(ValueError, stream.create_header, common.OPCODE_TEXT, 0, 0xf, 0, 0, 0, 0) # Too long payload_length self.assertRaises(ValueError, stream.create_header, common.OPCODE_TEXT, 1 << 63, 0, 0, 0, 0, 0) if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/test_stream_hixie75.py0000750023300700116100000000435511566670120021320 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for stream module.""" import unittest import set_sys_path # Update sys.path to locate mod_pywebsocket module. from mod_pywebsocket.stream import StreamHixie75 from test.test_msgutil import _create_request_hixie75 class StreamHixie75Test(unittest.TestCase): """A unittest for StreamHixie75 class.""" def test_payload_length(self): for length, bytes in ((0, '\x00'), (0x7f, '\x7f'), (0x80, '\x81\x00'), (0x1234, '\x80\xa4\x34')): test_stream = StreamHixie75(_create_request_hixie75(bytes)) self.assertEqual( length, test_stream._read_payload_length_hixie75()) if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/run_all.py0000750023300700116100000000601511522430402017040 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Run all tests in the same directory. This suite is expected to be run under pywebsocket's src directory, i.e. the directory containing mod_pywebsocket, test, etc. To change loggin level, please specify --log-level option. python test/run_test.py --log-level debug To pass any option to unittest module, please specify options after '--'. For example, run this for making the test runner verbose. python test/run_test.py --log-level debug -- -v """ import logging import optparse import os import re import sys import unittest _TEST_MODULE_PATTERN = re.compile(r'^(test_.+)\.py$') def _list_test_modules(directory): module_names = [] for filename in os.listdir(directory): match = _TEST_MODULE_PATTERN.search(filename) if match: module_names.append(match.group(1)) return module_names def _suite(): loader = unittest.TestLoader() return loader.loadTestsFromNames( _list_test_modules(os.path.join(os.path.split(__file__)[0], '.'))) if __name__ == '__main__': parser = optparse.OptionParser() parser.add_option('--log-level', '--log_level', type='choice', dest='log_level', default='warning', choices=['debug', 'info', 'warning', 'warn', 'error', 'critical']) options, args = parser.parse_args() logging.basicConfig(level=logging.getLevelName(options.log_level.upper())) unittest.main(defaultTest='_suite', argv=[sys.argv[0]] + args) # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/test_extensions.py0000750023300700116100000001236612033002200020635 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2012, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for extensions module.""" import unittest import set_sys_path # Update sys.path to locate mod_pywebsocket module. from mod_pywebsocket import common from mod_pywebsocket import extensions class CompressionMethodParameterParserTest(unittest.TestCase): """A unittest for _parse_compression_method which parses the compression method description used by perframe-compression and permessage-compression extension in their "method" extension parameter. """ def test_parse_method_simple(self): method_list = extensions._parse_compression_method('foo') self.assertEqual(1, len(method_list)) method = method_list[0] self.assertEqual('foo', method.name()) self.assertEqual(0, len(method.get_parameters())) def test_parse_method_with_parameter(self): method_list = extensions._parse_compression_method('foo; x; y=10') self.assertEqual(1, len(method_list)) method = method_list[0] self.assertEqual('foo', method.name()) self.assertEqual(2, len(method.get_parameters())) self.assertTrue(method.has_parameter('x')) self.assertEqual(None, method.get_parameter_value('x')) self.assertTrue(method.has_parameter('y')) self.assertEqual('10', method.get_parameter_value('y')) def test_parse_method_with_quoted_parameter(self): method_list = extensions._parse_compression_method( 'foo; x="Hello World"; y=10') self.assertEqual(1, len(method_list)) method = method_list[0] self.assertEqual('foo', method.name()) self.assertEqual(2, len(method.get_parameters())) self.assertTrue(method.has_parameter('x')) self.assertEqual('Hello World', method.get_parameter_value('x')) self.assertTrue(method.has_parameter('y')) self.assertEqual('10', method.get_parameter_value('y')) def test_parse_method_multiple(self): method_list = extensions._parse_compression_method('foo, bar') self.assertEqual(2, len(method_list)) self.assertEqual('foo', method_list[0].name()) self.assertEqual(0, len(method_list[0].get_parameters())) self.assertEqual('bar', method_list[1].name()) self.assertEqual(0, len(method_list[1].get_parameters())) def test_parse_method_multiple_methods_with_quoted_parameter(self): method_list = extensions._parse_compression_method( 'foo; x="Hello World", bar; y=10') self.assertEqual(2, len(method_list)) self.assertEqual('foo', method_list[0].name()) self.assertEqual(1, len(method_list[0].get_parameters())) self.assertTrue(method_list[0].has_parameter('x')) self.assertEqual('Hello World', method_list[0].get_parameter_value('x')) self.assertEqual('bar', method_list[1].name()) self.assertEqual(1, len(method_list[1].get_parameters())) self.assertTrue(method_list[1].has_parameter('y')) self.assertEqual('10', method_list[1].get_parameter_value('y')) def test_create_method_desc_simple(self): params = common.ExtensionParameter('foo') desc = extensions._create_accepted_method_desc('foo', params.get_parameters()) self.assertEqual('foo', desc) def test_create_method_desc_with_parameters(self): params = common.ExtensionParameter('foo') params.add_parameter('x', 'Hello, World') params.add_parameter('y', '10') desc = extensions._create_accepted_method_desc('foo', params.get_parameters()) self.assertEqual('foo; x="Hello, World"; y=10', desc) if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/test_dispatch.py0000750023300700116100000003150711652205055020256 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for dispatch module.""" import os import unittest import set_sys_path # Update sys.path to locate mod_pywebsocket module. from mod_pywebsocket import dispatch from mod_pywebsocket import handshake from test import mock _TEST_HANDLERS_DIR = os.path.join( os.path.split(__file__)[0], 'testdata', 'handlers') _TEST_HANDLERS_SUB_DIR = os.path.join(_TEST_HANDLERS_DIR, 'sub') class DispatcherTest(unittest.TestCase): """A unittest for dispatch module.""" def test_normalize_path(self): self.assertEqual(os.path.abspath('/a/b').replace('\\', '/'), dispatch._normalize_path('/a/b')) self.assertEqual(os.path.abspath('/a/b').replace('\\', '/'), dispatch._normalize_path('\\a\\b')) self.assertEqual(os.path.abspath('/a/b').replace('\\', '/'), dispatch._normalize_path('/a/c/../b')) self.assertEqual(os.path.abspath('abc').replace('\\', '/'), dispatch._normalize_path('abc')) def test_converter(self): converter = dispatch._create_path_to_resource_converter('/a/b') # Python built by MSC inserts a drive name like 'C:\' via realpath(). # Converter Generator expands provided path using realpath() and uses # the path including a drive name to verify the prefix. os_root = os.path.realpath('/') self.assertEqual('/h', converter(os_root + 'a/b/h_wsh.py')) self.assertEqual('/c/h', converter(os_root + 'a/b/c/h_wsh.py')) self.assertEqual(None, converter(os_root + 'a/b/h.py')) self.assertEqual(None, converter('a/b/h_wsh.py')) converter = dispatch._create_path_to_resource_converter('a/b') self.assertEqual('/h', converter(dispatch._normalize_path( 'a/b/h_wsh.py'))) converter = dispatch._create_path_to_resource_converter('/a/b///') self.assertEqual('/h', converter(os_root + 'a/b/h_wsh.py')) self.assertEqual('/h', converter(dispatch._normalize_path( '/a/b/../b/h_wsh.py'))) converter = dispatch._create_path_to_resource_converter( '/a/../a/b/../b/') self.assertEqual('/h', converter(os_root + 'a/b/h_wsh.py')) converter = dispatch._create_path_to_resource_converter(r'\a\b') self.assertEqual('/h', converter(os_root + r'a\b\h_wsh.py')) self.assertEqual('/h', converter(os_root + r'a/b/h_wsh.py')) def test_enumerate_handler_file_paths(self): paths = list( dispatch._enumerate_handler_file_paths(_TEST_HANDLERS_DIR)) paths.sort() self.assertEqual(8, len(paths)) expected_paths = [ os.path.join(_TEST_HANDLERS_DIR, 'abort_by_user_wsh.py'), os.path.join(_TEST_HANDLERS_DIR, 'blank_wsh.py'), os.path.join(_TEST_HANDLERS_DIR, 'origin_check_wsh.py'), os.path.join(_TEST_HANDLERS_DIR, 'sub', 'exception_in_transfer_wsh.py'), os.path.join(_TEST_HANDLERS_DIR, 'sub', 'non_callable_wsh.py'), os.path.join(_TEST_HANDLERS_DIR, 'sub', 'plain_wsh.py'), os.path.join(_TEST_HANDLERS_DIR, 'sub', 'wrong_handshake_sig_wsh.py'), os.path.join(_TEST_HANDLERS_DIR, 'sub', 'wrong_transfer_sig_wsh.py'), ] for expected, actual in zip(expected_paths, paths): self.assertEqual(expected, actual) def test_source_handler_file(self): self.assertRaises( dispatch.DispatchException, dispatch._source_handler_file, '') self.assertRaises( dispatch.DispatchException, dispatch._source_handler_file, 'def') self.assertRaises( dispatch.DispatchException, dispatch._source_handler_file, '1/0') self.failUnless(dispatch._source_handler_file( 'def web_socket_do_extra_handshake(request):pass\n' 'def web_socket_transfer_data(request):pass\n')) def test_source_warnings(self): dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) warnings = dispatcher.source_warnings() warnings.sort() expected_warnings = [ (os.path.realpath(os.path.join( _TEST_HANDLERS_DIR, 'blank_wsh.py')) + ': web_socket_do_extra_handshake is not defined.'), (os.path.realpath(os.path.join( _TEST_HANDLERS_DIR, 'sub', 'non_callable_wsh.py')) + ': web_socket_do_extra_handshake is not callable.'), (os.path.realpath(os.path.join( _TEST_HANDLERS_DIR, 'sub', 'wrong_handshake_sig_wsh.py')) + ': web_socket_do_extra_handshake is not defined.'), (os.path.realpath(os.path.join( _TEST_HANDLERS_DIR, 'sub', 'wrong_transfer_sig_wsh.py')) + ': web_socket_transfer_data is not defined.'), ] self.assertEquals(4, len(warnings)) for expected, actual in zip(expected_warnings, warnings): self.assertEquals(expected, actual) def test_do_extra_handshake(self): dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) request = mock.MockRequest() request.ws_resource = '/origin_check' request.ws_origin = 'http://example.com' dispatcher.do_extra_handshake(request) # Must not raise exception. request.ws_origin = 'http://bad.example.com' try: dispatcher.do_extra_handshake(request) self.fail('Could not catch HandshakeException with 403 status') except handshake.HandshakeException, e: self.assertEquals(403, e.status) except Exception, e: self.fail('Unexpected exception: %r' % e) def test_abort_extra_handshake(self): dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) request = mock.MockRequest() request.ws_resource = '/abort_by_user' self.assertRaises(handshake.AbortedByUserException, dispatcher.do_extra_handshake, request) def test_transfer_data(self): dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) request = mock.MockRequest(connection=mock.MockConn('\xff\x00')) request.ws_resource = '/origin_check' request.ws_protocol = 'p1' dispatcher.transfer_data(request) self.assertEqual('origin_check_wsh.py is called for /origin_check, p1' '\xff\x00', request.connection.written_data()) request = mock.MockRequest(connection=mock.MockConn('\xff\x00')) request.ws_resource = '/sub/plain' request.ws_protocol = None dispatcher.transfer_data(request) self.assertEqual('sub/plain_wsh.py is called for /sub/plain, None' '\xff\x00', request.connection.written_data()) request = mock.MockRequest(connection=mock.MockConn('\xff\x00')) request.ws_resource = '/sub/plain?' request.ws_protocol = None dispatcher.transfer_data(request) self.assertEqual('sub/plain_wsh.py is called for /sub/plain?, None' '\xff\x00', request.connection.written_data()) request = mock.MockRequest(connection=mock.MockConn('\xff\x00')) request.ws_resource = '/sub/plain?q=v' request.ws_protocol = None dispatcher.transfer_data(request) self.assertEqual('sub/plain_wsh.py is called for /sub/plain?q=v, None' '\xff\x00', request.connection.written_data()) def test_transfer_data_no_handler(self): dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) for resource in ['/blank', '/sub/non_callable', '/sub/no_wsh_at_the_end', '/does/not/exist']: request = mock.MockRequest(connection=mock.MockConn('')) request.ws_resource = resource request.ws_protocol = 'p2' try: dispatcher.transfer_data(request) self.fail() except dispatch.DispatchException, e: self.failUnless(str(e).find('No handler') != -1) except Exception: self.fail() def test_transfer_data_handler_exception(self): dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) request = mock.MockRequest(connection=mock.MockConn('')) request.ws_resource = '/sub/exception_in_transfer' request.ws_protocol = 'p3' try: dispatcher.transfer_data(request) self.fail() except Exception, e: self.failUnless(str(e).find('Intentional') != -1, 'Unexpected exception: %s' % e) def test_abort_transfer_data(self): dispatcher = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) request = mock.MockRequest() request.ws_resource = '/abort_by_user' self.assertRaises(handshake.AbortedByUserException, dispatcher.transfer_data, request) def test_scan_dir(self): disp = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) self.assertEqual(4, len(disp._handler_suite_map)) self.failUnless('/origin_check' in disp._handler_suite_map) self.failUnless( '/sub/exception_in_transfer' in disp._handler_suite_map) self.failUnless('/sub/plain' in disp._handler_suite_map) def test_scan_sub_dir(self): disp = dispatch.Dispatcher(_TEST_HANDLERS_DIR, _TEST_HANDLERS_SUB_DIR) self.assertEqual(2, len(disp._handler_suite_map)) self.failIf('/origin_check' in disp._handler_suite_map) self.failUnless( '/sub/exception_in_transfer' in disp._handler_suite_map) self.failUnless('/sub/plain' in disp._handler_suite_map) def test_scan_sub_dir_as_root(self): disp = dispatch.Dispatcher(_TEST_HANDLERS_SUB_DIR, _TEST_HANDLERS_SUB_DIR) self.assertEqual(2, len(disp._handler_suite_map)) self.failIf('/origin_check' in disp._handler_suite_map) self.failIf('/sub/exception_in_transfer' in disp._handler_suite_map) self.failIf('/sub/plain' in disp._handler_suite_map) self.failUnless('/exception_in_transfer' in disp._handler_suite_map) self.failUnless('/plain' in disp._handler_suite_map) def test_scan_dir_must_under_root(self): dispatch.Dispatcher('a/b', 'a/b/c') # OK dispatch.Dispatcher('a/b///', 'a/b') # OK self.assertRaises(dispatch.DispatchException, dispatch.Dispatcher, 'a/b/c', 'a/b') def test_resource_path_alias(self): disp = dispatch.Dispatcher(_TEST_HANDLERS_DIR, None) disp.add_resource_path_alias('/', '/origin_check') self.assertEqual(5, len(disp._handler_suite_map)) self.failUnless('/origin_check' in disp._handler_suite_map) self.failUnless( '/sub/exception_in_transfer' in disp._handler_suite_map) self.failUnless('/sub/plain' in disp._handler_suite_map) self.failUnless('/' in disp._handler_suite_map) self.assertRaises(dispatch.DispatchException, disp.add_resource_path_alias, '/alias', '/not-exist') if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/test_endtoend.py0000750023300700116100000004240712045716437020270 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2012, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Test for end-to-end.""" import logging import os import signal import socket import subprocess import sys import time import unittest import set_sys_path # Update sys.path to locate mod_pywebsocket module. from test import client_for_testing from test import mux_client_for_testing # Special message that tells the echo server to start closing handshake _GOODBYE_MESSAGE = 'Goodbye' # If you want to use external server to run end to end tests, set following # parameters correctly. _use_external_server = False _external_server_port = 0 # Test body functions def _echo_check_procedure(client): client.connect() client.send_message('test') client.assert_receive('test') client.send_message('helloworld') client.assert_receive('helloworld') client.send_close() client.assert_receive_close() client.assert_connection_closed() def _echo_check_procedure_with_binary(client): client.connect() client.send_message('binary', binary=True) client.assert_receive('binary', binary=True) client.send_message('\x00\x80\xfe\xff\x00\x80', binary=True) client.assert_receive('\x00\x80\xfe\xff\x00\x80', binary=True) client.send_close() client.assert_receive_close() client.assert_connection_closed() def _echo_check_procedure_with_goodbye(client): client.connect() client.send_message('test') client.assert_receive('test') client.send_message(_GOODBYE_MESSAGE) client.assert_receive(_GOODBYE_MESSAGE) client.assert_receive_close() client.send_close() client.assert_connection_closed() def _echo_check_procedure_with_code_and_reason(client, code, reason): client.connect() client.send_close(code, reason) client.assert_receive_close(code, reason) client.assert_connection_closed() def _unmasked_frame_check_procedure(client): client.connect() client.send_message('test', mask=False) client.assert_receive_close(client_for_testing.STATUS_PROTOCOL_ERROR, '') client.assert_connection_closed() def _mux_echo_check_procedure(mux_client): mux_client.connect() mux_client.send_flow_control(1, 1024) logical_channel_options = client_for_testing.ClientOptions() logical_channel_options.server_host = 'localhost' logical_channel_options.server_port = 80 logical_channel_options.origin = 'http://localhost' logical_channel_options.resource = '/echo' mux_client.add_channel(2, logical_channel_options) mux_client.send_flow_control(2, 1024) mux_client.send_message(2, 'test') mux_client.assert_receive(2, 'test') mux_client.add_channel(3, logical_channel_options) mux_client.send_flow_control(3, 1024) mux_client.send_message(2, 'hello') mux_client.send_message(3, 'world') mux_client.assert_receive(2, 'hello') mux_client.assert_receive(3, 'world') # Don't send close message on channel id 1 so that server-initiated # closing handshake won't occur. mux_client.send_close(2) mux_client.send_close(3) mux_client.assert_receive_close(2) mux_client.assert_receive_close(3) mux_client.send_physical_connection_close() mux_client.assert_physical_connection_receive_close() class EndToEndTest(unittest.TestCase): """An end-to-end test that launches pywebsocket standalone server as a separate process, connects to it using the client_for_testing module, and checks if the server behaves correctly by exchanging opening handshake and frames over a TCP connection. """ def setUp(self): self.server_stderr = None self.top_dir = os.path.join(os.path.split(__file__)[0], '..') os.putenv('PYTHONPATH', os.path.pathsep.join(sys.path)) self.standalone_command = os.path.join( self.top_dir, 'mod_pywebsocket', 'standalone.py') self.document_root = os.path.join(self.top_dir, 'example') s = socket.socket() s.bind(('localhost', 0)) (_, self.test_port) = s.getsockname() s.close() self._options = client_for_testing.ClientOptions() self._options.server_host = 'localhost' self._options.origin = 'http://localhost' self._options.resource = '/echo' # TODO(toyoshim): Eliminate launching a standalone server on using # external server. if _use_external_server: self._options.server_port = _external_server_port else: self._options.server_port = self.test_port def _run_python_command(self, commandline, stdout=None, stderr=None): return subprocess.Popen([sys.executable] + commandline, close_fds=True, stdout=stdout, stderr=stderr) def _run_server(self, allow_draft75=False): args = [self.standalone_command, '-H', 'localhost', '-V', 'localhost', '-p', str(self.test_port), '-P', str(self.test_port), '-d', self.document_root] # Inherit the level set to the root logger by test runner. root_logger = logging.getLogger() log_level = root_logger.getEffectiveLevel() if log_level != logging.NOTSET: args.append('--log-level') args.append(logging.getLevelName(log_level).lower()) if allow_draft75: args.append('--allow-draft75') return self._run_python_command(args, stderr=self.server_stderr) def _kill_process(self, pid): if sys.platform in ('win32', 'cygwin'): subprocess.call( ('taskkill.exe', '/f', '/pid', str(pid)), close_fds=True) else: os.kill(pid, signal.SIGKILL) def _run_hybi_test_with_client_options(self, test_function, options): server = self._run_server() try: # TODO(tyoshino): add some logic to poll the server until it # becomes ready time.sleep(0.2) client = client_for_testing.create_client(options) try: test_function(client) finally: client.close_socket() finally: self._kill_process(server.pid) def _run_hybi_test(self, test_function): self._run_hybi_test_with_client_options(test_function, self._options) def _run_hybi_deflate_test(self, test_function): server = self._run_server() try: time.sleep(0.2) self._options.enable_deflate_stream() client = client_for_testing.create_client(self._options) try: test_function(client) finally: client.close_socket() finally: self._kill_process(server.pid) def _run_hybi_deflate_frame_test(self, test_function): server = self._run_server() try: time.sleep(0.2) self._options.enable_deflate_frame() client = client_for_testing.create_client(self._options) try: test_function(client) finally: client.close_socket() finally: self._kill_process(server.pid) def _run_hybi_close_with_code_and_reason_test(self, test_function, code, reason): server = self._run_server() try: time.sleep(0.2) client = client_for_testing.create_client(self._options) try: test_function(client, code, reason) finally: client.close_socket() finally: self._kill_process(server.pid) def _run_hybi_http_fallback_test(self, options, status): server = self._run_server() try: time.sleep(0.2) client = client_for_testing.create_client(options) try: client.connect() self.fail('Could not catch HttpStatusException') except client_for_testing.HttpStatusException, e: self.assertEqual(status, e.status) except Exception, e: self.fail('Catch unexpected exception') finally: client.close_socket() finally: self._kill_process(server.pid) def _run_hybi_mux_test(self, test_function): server = self._run_server() try: time.sleep(0.2) client = mux_client_for_testing.MuxClient(self._options) try: test_function(client) finally: client.close_socket() finally: self._kill_process(server.pid) def test_echo(self): self._run_hybi_test(_echo_check_procedure) def test_echo_binary(self): self._run_hybi_test(_echo_check_procedure_with_binary) def test_echo_server_close(self): self._run_hybi_test(_echo_check_procedure_with_goodbye) def test_unmasked_frame(self): self._run_hybi_test(_unmasked_frame_check_procedure) def test_echo_deflate(self): self._run_hybi_deflate_test(_echo_check_procedure) def test_echo_deflate_server_close(self): self._run_hybi_deflate_test(_echo_check_procedure_with_goodbye) def test_echo_deflate_frame(self): self._run_hybi_deflate_frame_test(_echo_check_procedure) def test_echo_deflate_frame_server_close(self): self._run_hybi_deflate_frame_test( _echo_check_procedure_with_goodbye) def test_echo_close_with_code_and_reason(self): self._options.resource = '/close' self._run_hybi_close_with_code_and_reason_test( _echo_check_procedure_with_code_and_reason, 3333, 'sunsunsunsun') def test_echo_close_with_empty_body(self): self._options.resource = '/close' self._run_hybi_close_with_code_and_reason_test( _echo_check_procedure_with_code_and_reason, None, '') def test_mux_echo(self): self._run_hybi_mux_test(_mux_echo_check_procedure) def test_close_on_protocol_error(self): """Tests that the server sends a close frame with protocol error status code when the client sends data with some protocol error. """ def test_function(client): client.connect() # Intermediate frame without any preceding start of fragmentation # frame. client.send_frame_of_arbitrary_bytes('\x80\x80', '') client.assert_receive_close( client_for_testing.STATUS_PROTOCOL_ERROR) self._run_hybi_test(test_function) def test_close_on_unsupported_frame(self): """Tests that the server sends a close frame with unsupported operation status code when the client sends data asking some operation that is not supported by the server. """ def test_function(client): client.connect() # Text frame with RSV3 bit raised. client.send_frame_of_arbitrary_bytes('\x91\x80', '') client.assert_receive_close( client_for_testing.STATUS_UNSUPPORTED_DATA) self._run_hybi_test(test_function) def test_close_on_invalid_frame(self): """Tests that the server sends a close frame with invalid frame payload data status code when the client sends an invalid frame like containing invalid UTF-8 character. """ def test_function(client): client.connect() # Text frame with invalid UTF-8 string. client.send_message('\x80', raw=True) client.assert_receive_close( client_for_testing.STATUS_INVALID_FRAME_PAYLOAD_DATA) self._run_hybi_test(test_function) def test_close_on_internal_endpoint_error(self): """Tests that the server sends a close frame with internal endpoint error status code when the handler does bad operation. """ self._options.resource = '/internal_error' def test_function(client): client.connect() client.assert_receive_close( client_for_testing.STATUS_INTERNAL_ENDPOINT_ERROR) self._run_hybi_test(test_function) def _run_hybi00_test(self, test_function): server = self._run_server() try: time.sleep(0.2) client = client_for_testing.create_client_hybi00(self._options) try: test_function(client) finally: client.close_socket() finally: self._kill_process(server.pid) def test_echo_hybi00(self): self._run_hybi00_test(_echo_check_procedure) def test_echo_server_close_hybi00(self): self._run_hybi00_test(_echo_check_procedure_with_goodbye) # TODO(toyoshim): Add tests to verify invalid absolute uri handling like # host unmatch, port unmatch and invalid port description (':' without port # number). def test_absolute_uri(self): """Tests absolute uri request.""" options = self._options options.resource = 'ws://localhost:%d/echo' % options.server_port self._run_hybi_test_with_client_options(_echo_check_procedure, options) def test_origin_check(self): """Tests http fallback on origin check fail.""" options = self._options options.resource = '/origin_check' # Server shows warning message for http 403 fallback. This warning # message is confusing. Following pipe disposes warning messages. self.server_stderr = subprocess.PIPE self._run_hybi_http_fallback_test(options, 403) def test_version_check(self): """Tests http fallback on version check fail.""" options = self._options options.version = 99 self.server_stderr = subprocess.PIPE self._run_hybi_http_fallback_test(options, 400) def _check_example_echo_client_result( self, expected, stdoutdata, stderrdata): actual = stdoutdata.decode("utf-8") if actual != expected: raise Exception('Unexpected result on example echo client: ' '%r (expected) vs %r (actual)' % (expected, actual)) if stderrdata is not None: raise Exception('Unexpected error message on example echo ' 'client: %r' % stderrdata) def test_example_echo_client(self): """Tests that the echo_client.py example can talk with the server.""" server = self._run_server() try: time.sleep(0.2) client_command = os.path.join( self.top_dir, 'example', 'echo_client.py') args = [client_command, '-p', str(self._options.server_port)] client = self._run_python_command(args, stdout=subprocess.PIPE) stdoutdata, stderrdata = client.communicate() expected = ('Send: Hello\n' 'Recv: Hello\n' u'Send: \u65e5\u672c\n' u'Recv: \u65e5\u672c\n' 'Send close\n' 'Recv ack\n') self._check_example_echo_client_result( expected, stdoutdata, stderrdata) # Process a big message for which extended payload length is used. # To handle extended payload length, ws_version attribute will be # accessed. This test checks that ws_version is correctly set. big_message = 'a' * 1024 args = [client_command, '-p', str(self._options.server_port), '-m', big_message] client = self._run_python_command(args, stdout=subprocess.PIPE) stdoutdata, stderrdata = client.communicate() expected = ('Send: %s\nRecv: %s\nSend close\nRecv ack\n' % (big_message, big_message)) self._check_example_echo_client_result( expected, stdoutdata, stderrdata) finally: self._kill_process(server.pid) if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/testdata/0000750023300700116100000000000012121621357016645 5ustar tyoshinoengpywebsocket-0.7.9/src/test/testdata/handlers/0000750023300700116100000000000012121621357020445 5ustar tyoshinoengpywebsocket-0.7.9/src/test/testdata/handlers/sub/0000750023300700116100000000000012121621357021236 5ustar tyoshinoengpywebsocket-0.7.9/src/test/testdata/handlers/sub/exception_in_transfer_wsh.py0000640023300700116100000000343011522430402027053 0ustar tyoshinoeng# Copyright 2009, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Exception in web_socket_transfer_data(). """ def web_socket_do_extra_handshake(request): pass def web_socket_transfer_data(request): raise Exception('Intentional Exception for %s, %s' % (request.ws_resource, request.ws_protocol)) # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/testdata/handlers/sub/non_callable_wsh.py0000640023300700116100000000316111522430402025075 0ustar tyoshinoeng# Copyright 2009, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Non-callable handlers. """ web_socket_do_extra_handshake = True web_socket_transfer_data = 1 # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py0000640023300700116100000000347611522430402026377 0ustar tyoshinoeng# Copyright 2009, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Wrong web_socket_transfer_data() signature. """ def web_socket_do_extra_handshake(request): pass def no_web_socket_transfer_data(request): request.connection.write( 'sub/wrong_transfer_sig_wsh.py is called for %s, %s' % (request.ws_resource, request.ws_protocol)) # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/testdata/handlers/sub/plain_wsh.py0000640023300700116100000000337511522430402023576 0ustar tyoshinoeng# Copyright 2009, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. def web_socket_do_extra_handshake(request): pass def web_socket_transfer_data(request): request.connection.write('sub/plain_wsh.py is called for %s, %s' % (request.ws_resource, request.ws_protocol)) # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py0000640023300700116100000000350311522430402026470 0ustar tyoshinoeng# Copyright 2009, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Wrong web_socket_do_extra_handshake signature. """ def no_web_socket_do_extra_handshake(request): pass def web_socket_transfer_data(request): request.connection.write( 'sub/wrong_handshake_sig_wsh.py is called for %s, %s' % (request.ws_resource, request.ws_protocol)) # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/testdata/handlers/sub/no_wsh_at_the_end.py0000640023300700116100000000345711522430402025262 0ustar tyoshinoeng# Copyright 2009, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Correct signatures, wrong file name. """ def web_socket_do_extra_handshake(request): pass def web_socket_transfer_data(request): request.connection.write( 'sub/no_wsh_at_the_end.py is called for %s, %s' % (request.ws_resource, request.ws_protocol)) # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/testdata/handlers/blank_wsh.py0000640023300700116100000000302511522430402022761 0ustar tyoshinoeng# Copyright 2009, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # intentionally left blank pywebsocket-0.7.9/src/test/testdata/handlers/abort_by_user_wsh.py0000640023300700116100000000340611626642153024551 0ustar tyoshinoeng# Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from mod_pywebsocket import handshake def web_socket_do_extra_handshake(request): raise handshake.AbortedByUserException("abort for test") def web_socket_transfer_data(request): raise handshake.AbortedByUserException("abort for test") # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/testdata/handlers/origin_check_wsh.py0000640023300700116100000000357411522430402024327 0ustar tyoshinoeng# Copyright 2009, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. def web_socket_do_extra_handshake(request): if request.ws_origin == 'http://example.com': return raise ValueError('Unacceptable origin: %r' % request.ws_origin) def web_socket_transfer_data(request): request.connection.write('origin_check_wsh.py is called for %s, %s' % (request.ws_resource, request.ws_protocol)) # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/testdata/hello.pl0000640023300700116100000000304012014611264020300 0ustar tyoshinoeng#!/usr/bin/perl -wT # # Copyright 2012, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. print "Hello\n"; pywebsocket-0.7.9/src/test/testdata/README0000640023300700116100000000002411522430402017513 0ustar tyoshinoengTest data directory pywebsocket-0.7.9/src/test/test_handshake_hybi00.py0000750023300700116100000004170112121531475021555 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for handshake.hybi00 module.""" import unittest import set_sys_path # Update sys.path to locate mod_pywebsocket module. from mod_pywebsocket.handshake._base import HandshakeException from mod_pywebsocket.handshake.hybi00 import Handshaker from mod_pywebsocket.handshake.hybi00 import _validate_subprotocol from test import mock _TEST_KEY1 = '4 @1 46546xW%0l 1 5' _TEST_KEY2 = '12998 5 Y3 1 .P00' _TEST_KEY3 = '^n:ds[4U' _TEST_CHALLENGE_RESPONSE = '8jKS\'y:G*Co,Wxa-' _GOOD_REQUEST = ( 80, 'GET', '/demo', { 'Host': 'example.com', 'Connection': 'Upgrade', 'Sec-WebSocket-Key2': _TEST_KEY2, 'Sec-WebSocket-Protocol': 'sample', 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': _TEST_KEY1, 'Origin': 'http://example.com', }, _TEST_KEY3) _GOOD_REQUEST_CAPITALIZED_HEADER_VALUES = ( 80, 'GET', '/demo', { 'Host': 'example.com', 'Connection': 'UPGRADE', 'Sec-WebSocket-Key2': _TEST_KEY2, 'Sec-WebSocket-Protocol': 'sample', 'Upgrade': 'WEBSOCKET', 'Sec-WebSocket-Key1': _TEST_KEY1, 'Origin': 'http://example.com', }, _TEST_KEY3) _GOOD_REQUEST_CASE_MIXED_HEADER_NAMES = ( 80, 'GET', '/demo', { 'hOsT': 'example.com', 'cOnNeCtIoN': 'Upgrade', 'sEc-wEbsOcKeT-kEy2': _TEST_KEY2, 'sEc-wEbsOcKeT-pRoToCoL': 'sample', 'uPgRaDe': 'WebSocket', 'sEc-wEbsOcKeT-kEy1': _TEST_KEY1, 'oRiGiN': 'http://example.com', }, _TEST_KEY3) _GOOD_RESPONSE_DEFAULT_PORT = ( 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' 'Upgrade: WebSocket\r\n' 'Connection: Upgrade\r\n' 'Sec-WebSocket-Location: ws://example.com/demo\r\n' 'Sec-WebSocket-Origin: http://example.com\r\n' 'Sec-WebSocket-Protocol: sample\r\n' '\r\n' + _TEST_CHALLENGE_RESPONSE) _GOOD_RESPONSE_SECURE = ( 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' 'Upgrade: WebSocket\r\n' 'Connection: Upgrade\r\n' 'Sec-WebSocket-Location: wss://example.com/demo\r\n' 'Sec-WebSocket-Origin: http://example.com\r\n' 'Sec-WebSocket-Protocol: sample\r\n' '\r\n' + _TEST_CHALLENGE_RESPONSE) _GOOD_REQUEST_NONDEFAULT_PORT = ( 8081, 'GET', '/demo', { 'Host': 'example.com:8081', 'Connection': 'Upgrade', 'Sec-WebSocket-Key2': _TEST_KEY2, 'Sec-WebSocket-Protocol': 'sample', 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': _TEST_KEY1, 'Origin': 'http://example.com', }, _TEST_KEY3) _GOOD_RESPONSE_NONDEFAULT_PORT = ( 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' 'Upgrade: WebSocket\r\n' 'Connection: Upgrade\r\n' 'Sec-WebSocket-Location: ws://example.com:8081/demo\r\n' 'Sec-WebSocket-Origin: http://example.com\r\n' 'Sec-WebSocket-Protocol: sample\r\n' '\r\n' + _TEST_CHALLENGE_RESPONSE) _GOOD_RESPONSE_SECURE_NONDEF = ( 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' 'Upgrade: WebSocket\r\n' 'Connection: Upgrade\r\n' 'Sec-WebSocket-Location: wss://example.com:8081/demo\r\n' 'Sec-WebSocket-Origin: http://example.com\r\n' 'Sec-WebSocket-Protocol: sample\r\n' '\r\n' + _TEST_CHALLENGE_RESPONSE) _GOOD_REQUEST_NO_PROTOCOL = ( 80, 'GET', '/demo', { 'Host': 'example.com', 'Connection': 'Upgrade', 'Sec-WebSocket-Key2': _TEST_KEY2, 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': _TEST_KEY1, 'Origin': 'http://example.com', }, _TEST_KEY3) _GOOD_RESPONSE_NO_PROTOCOL = ( 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' 'Upgrade: WebSocket\r\n' 'Connection: Upgrade\r\n' 'Sec-WebSocket-Location: ws://example.com/demo\r\n' 'Sec-WebSocket-Origin: http://example.com\r\n' '\r\n' + _TEST_CHALLENGE_RESPONSE) _GOOD_REQUEST_WITH_OPTIONAL_HEADERS = ( 80, 'GET', '/demo', { 'Host': 'example.com', 'Connection': 'Upgrade', 'Sec-WebSocket-Key2': _TEST_KEY2, 'EmptyValue': '', 'Sec-WebSocket-Protocol': 'sample', 'AKey': 'AValue', 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': _TEST_KEY1, 'Origin': 'http://example.com', }, _TEST_KEY3) # TODO(tyoshino): Include \r \n in key3, challenge response. _GOOD_REQUEST_WITH_NONPRINTABLE_KEY = ( 80, 'GET', '/demo', { 'Host': 'example.com', 'Connection': 'Upgrade', 'Sec-WebSocket-Key2': 'y R2 48 Q1O4 e|BV3 i5 1 u- 65', 'Sec-WebSocket-Protocol': 'sample', 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': '36 7 74 i 92 2\'m 9 0G', 'Origin': 'http://example.com', }, ''.join(map(chr, [0x01, 0xd1, 0xdd, 0x3b, 0xd1, 0x56, 0x63, 0xff]))) _GOOD_RESPONSE_WITH_NONPRINTABLE_KEY = ( 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' 'Upgrade: WebSocket\r\n' 'Connection: Upgrade\r\n' 'Sec-WebSocket-Location: ws://example.com/demo\r\n' 'Sec-WebSocket-Origin: http://example.com\r\n' 'Sec-WebSocket-Protocol: sample\r\n' '\r\n' + ''.join(map(chr, [0x0b, 0x99, 0xfa, 0x55, 0xbd, 0x01, 0x23, 0x7b, 0x45, 0xa2, 0xf1, 0xd0, 0x87, 0x8a, 0xee, 0xeb]))) _GOOD_REQUEST_WITH_QUERY_PART = ( 80, 'GET', '/demo?e=mc2', { 'Host': 'example.com', 'Connection': 'Upgrade', 'Sec-WebSocket-Key2': _TEST_KEY2, 'Sec-WebSocket-Protocol': 'sample', 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': _TEST_KEY1, 'Origin': 'http://example.com', }, _TEST_KEY3) _GOOD_RESPONSE_WITH_QUERY_PART = ( 'HTTP/1.1 101 WebSocket Protocol Handshake\r\n' 'Upgrade: WebSocket\r\n' 'Connection: Upgrade\r\n' 'Sec-WebSocket-Location: ws://example.com/demo?e=mc2\r\n' 'Sec-WebSocket-Origin: http://example.com\r\n' 'Sec-WebSocket-Protocol: sample\r\n' '\r\n' + _TEST_CHALLENGE_RESPONSE) _BAD_REQUESTS = ( ( # HTTP request 80, 'GET', '/demo', { 'Host': 'www.google.com', 'User-Agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5;' ' en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3' ' GTB6 GTBA', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,' '*/*;q=0.8', 'Accept-Language': 'en-us,en;q=0.5', 'Accept-Encoding': 'gzip,deflate', 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'Keep-Alive': '300', 'Connection': 'keep-alive', }), ( # Wrong method 80, 'POST', '/demo', { 'Host': 'example.com', 'Connection': 'Upgrade', 'Sec-WebSocket-Key2': _TEST_KEY2, 'Sec-WebSocket-Protocol': 'sample', 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': _TEST_KEY1, 'Origin': 'http://example.com', }, _TEST_KEY3), ( # Missing Upgrade 80, 'GET', '/demo', { 'Host': 'example.com', 'Connection': 'Upgrade', 'Sec-WebSocket-Key2': _TEST_KEY2, 'Sec-WebSocket-Protocol': 'sample', 'Sec-WebSocket-Key1': _TEST_KEY1, 'Origin': 'http://example.com', }, _TEST_KEY3), ( # Wrong Upgrade 80, 'GET', '/demo', { 'Host': 'example.com', 'Connection': 'Upgrade', 'Sec-WebSocket-Key2': _TEST_KEY2, 'Sec-WebSocket-Protocol': 'sample', 'Upgrade': 'NonWebSocket', 'Sec-WebSocket-Key1': _TEST_KEY1, 'Origin': 'http://example.com', }, _TEST_KEY3), ( # Empty WebSocket-Protocol 80, 'GET', '/demo', { 'Host': 'example.com', 'Connection': 'Upgrade', 'Sec-WebSocket-Key2': _TEST_KEY2, 'Sec-WebSocket-Protocol': '', 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': _TEST_KEY1, 'Origin': 'http://example.com', }, _TEST_KEY3), ( # Wrong port number format 80, 'GET', '/demo', { 'Host': 'example.com:0x50', 'Connection': 'Upgrade', 'Sec-WebSocket-Key2': _TEST_KEY2, 'Sec-WebSocket-Protocol': 'sample', 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': _TEST_KEY1, 'Origin': 'http://example.com', }, _TEST_KEY3), ( # Header/connection port mismatch 8080, 'GET', '/demo', { 'Host': 'example.com', 'Connection': 'Upgrade', 'Sec-WebSocket-Key2': _TEST_KEY2, 'Sec-WebSocket-Protocol': 'sample', 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': _TEST_KEY1, 'Origin': 'http://example.com', }, _TEST_KEY3), ( # Illegal WebSocket-Protocol 80, 'GET', '/demo', { 'Host': 'example.com', 'Connection': 'Upgrade', 'Sec-WebSocket-Key2': _TEST_KEY2, 'Sec-WebSocket-Protocol': 'illegal\x09protocol', 'Upgrade': 'WebSocket', 'Sec-WebSocket-Key1': _TEST_KEY1, 'Origin': 'http://example.com', }, _TEST_KEY3), ) def _create_request(request_def): data = '' if len(request_def) > 4: data = request_def[4] conn = mock.MockConn(data) conn.local_addr = ('0.0.0.0', request_def[0]) return mock.MockRequest( method=request_def[1], uri=request_def[2], headers_in=request_def[3], connection=conn) def _create_get_memorized_lines(lines): """Creates a function that returns the given string.""" def get_memorized_lines(): return lines return get_memorized_lines def _create_requests_with_lines(request_lines_set): requests = [] for lines in request_lines_set: request = _create_request(_GOOD_REQUEST) request.connection.get_memorized_lines = _create_get_memorized_lines( lines) requests.append(request) return requests class HyBi00HandshakerTest(unittest.TestCase): def test_good_request_default_port(self): request = _create_request(_GOOD_REQUEST) handshaker = Handshaker(request, mock.MockDispatcher()) handshaker.do_handshake() self.assertEqual(_GOOD_RESPONSE_DEFAULT_PORT, request.connection.written_data()) self.assertEqual('/demo', request.ws_resource) self.assertEqual('http://example.com', request.ws_origin) self.assertEqual('ws://example.com/demo', request.ws_location) self.assertEqual('sample', request.ws_protocol) def test_good_request_capitalized_header_values(self): request = _create_request(_GOOD_REQUEST_CAPITALIZED_HEADER_VALUES) handshaker = Handshaker(request, mock.MockDispatcher()) handshaker.do_handshake() self.assertEqual(_GOOD_RESPONSE_DEFAULT_PORT, request.connection.written_data()) def test_good_request_case_mixed_header_names(self): request = _create_request(_GOOD_REQUEST_CASE_MIXED_HEADER_NAMES) handshaker = Handshaker(request, mock.MockDispatcher()) handshaker.do_handshake() self.assertEqual(_GOOD_RESPONSE_DEFAULT_PORT, request.connection.written_data()) def test_good_request_secure_default_port(self): request = _create_request(_GOOD_REQUEST) request.connection.local_addr = ('0.0.0.0', 443) request.is_https_ = True handshaker = Handshaker(request, mock.MockDispatcher()) handshaker.do_handshake() self.assertEqual(_GOOD_RESPONSE_SECURE, request.connection.written_data()) self.assertEqual('sample', request.ws_protocol) def test_good_request_nondefault_port(self): request = _create_request(_GOOD_REQUEST_NONDEFAULT_PORT) handshaker = Handshaker(request, mock.MockDispatcher()) handshaker.do_handshake() self.assertEqual(_GOOD_RESPONSE_NONDEFAULT_PORT, request.connection.written_data()) self.assertEqual('sample', request.ws_protocol) def test_good_request_secure_non_default_port(self): request = _create_request(_GOOD_REQUEST_NONDEFAULT_PORT) request.is_https_ = True handshaker = Handshaker(request, mock.MockDispatcher()) handshaker.do_handshake() self.assertEqual(_GOOD_RESPONSE_SECURE_NONDEF, request.connection.written_data()) self.assertEqual('sample', request.ws_protocol) def test_good_request_default_no_protocol(self): request = _create_request(_GOOD_REQUEST_NO_PROTOCOL) handshaker = Handshaker(request, mock.MockDispatcher()) handshaker.do_handshake() self.assertEqual(_GOOD_RESPONSE_NO_PROTOCOL, request.connection.written_data()) self.assertEqual(None, request.ws_protocol) def test_good_request_optional_headers(self): request = _create_request(_GOOD_REQUEST_WITH_OPTIONAL_HEADERS) handshaker = Handshaker(request, mock.MockDispatcher()) handshaker.do_handshake() self.assertEqual('AValue', request.headers_in['AKey']) self.assertEqual('', request.headers_in['EmptyValue']) def test_good_request_with_nonprintable_key(self): request = _create_request(_GOOD_REQUEST_WITH_NONPRINTABLE_KEY) handshaker = Handshaker(request, mock.MockDispatcher()) handshaker.do_handshake() self.assertEqual(_GOOD_RESPONSE_WITH_NONPRINTABLE_KEY, request.connection.written_data()) self.assertEqual('sample', request.ws_protocol) def test_good_request_with_query_part(self): request = _create_request(_GOOD_REQUEST_WITH_QUERY_PART) handshaker = Handshaker(request, mock.MockDispatcher()) handshaker.do_handshake() self.assertEqual(_GOOD_RESPONSE_WITH_QUERY_PART, request.connection.written_data()) self.assertEqual('ws://example.com/demo?e=mc2', request.ws_location) def test_bad_requests(self): for request in map(_create_request, _BAD_REQUESTS): handshaker = Handshaker(request, mock.MockDispatcher()) self.assertRaises(HandshakeException, handshaker.do_handshake) class HyBi00ValidateSubprotocolTest(unittest.TestCase): def test_validate_subprotocol(self): # should succeed. _validate_subprotocol('sample') _validate_subprotocol('Sample') _validate_subprotocol('sample\x7eprotocol') _validate_subprotocol('sample\x20protocol') # should fail. self.assertRaises(HandshakeException, _validate_subprotocol, '') self.assertRaises(HandshakeException, _validate_subprotocol, 'sample\x19protocol') self.assertRaises(HandshakeException, _validate_subprotocol, 'sample\x7fprotocol') self.assertRaises(HandshakeException, _validate_subprotocol, # "Japan" in Japanese u'\u65e5\u672c') if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/test_handshake_hybi.py0000750023300700116100000005465412045672641021436 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for handshake module.""" import unittest import set_sys_path # Update sys.path to locate mod_pywebsocket module. from mod_pywebsocket import common from mod_pywebsocket.handshake._base import AbortedByUserException from mod_pywebsocket.handshake._base import HandshakeException from mod_pywebsocket.handshake._base import VersionException from mod_pywebsocket.handshake.hybi import Handshaker import mock class RequestDefinition(object): """A class for holding data for constructing opening handshake strings for testing the opening handshake processor. """ def __init__(self, method, uri, headers): self.method = method self.uri = uri self.headers = headers def _create_good_request_def(): return RequestDefinition( 'GET', '/demo', {'Host': 'server.example.com', 'Upgrade': 'websocket', 'Connection': 'Upgrade', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': '13', 'Origin': 'http://example.com'}) def _create_request(request_def): conn = mock.MockConn('') return mock.MockRequest( method=request_def.method, uri=request_def.uri, headers_in=request_def.headers, connection=conn) def _create_handshaker(request): handshaker = Handshaker(request, mock.MockDispatcher()) return handshaker class SubprotocolChoosingDispatcher(object): """A dispatcher for testing. This dispatcher sets the i-th subprotocol of requested ones to ws_protocol where i is given on construction as index argument. If index is negative, default_value will be set to ws_protocol. """ def __init__(self, index, default_value=None): self.index = index self.default_value = default_value def do_extra_handshake(self, conn_context): if self.index >= 0: conn_context.ws_protocol = conn_context.ws_requested_protocols[ self.index] else: conn_context.ws_protocol = self.default_value def transfer_data(self, conn_context): pass class HandshakeAbortedException(Exception): pass class AbortingDispatcher(object): """A dispatcher for testing. This dispatcher raises an exception in do_extra_handshake to reject the request. """ def do_extra_handshake(self, conn_context): raise HandshakeAbortedException('An exception to reject the request') def transfer_data(self, conn_context): pass class AbortedByUserDispatcher(object): """A dispatcher for testing. This dispatcher raises an AbortedByUserException in do_extra_handshake to reject the request. """ def do_extra_handshake(self, conn_context): raise AbortedByUserException('An AbortedByUserException to reject the ' 'request') def transfer_data(self, conn_context): pass _EXPECTED_RESPONSE = ( 'HTTP/1.1 101 Switching Protocols\r\n' 'Upgrade: websocket\r\n' 'Connection: Upgrade\r\n' 'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n') class HandshakerTest(unittest.TestCase): """A unittest for draft-ietf-hybi-thewebsocketprotocol-06 and later handshake processor. """ def test_do_handshake(self): request = _create_request(_create_good_request_def()) dispatcher = mock.MockDispatcher() handshaker = Handshaker(request, dispatcher) handshaker.do_handshake() self.assertTrue(dispatcher.do_extra_handshake_called) self.assertEqual( _EXPECTED_RESPONSE, request.connection.written_data()) self.assertEqual('/demo', request.ws_resource) self.assertEqual('http://example.com', request.ws_origin) self.assertEqual(None, request.ws_protocol) self.assertEqual(None, request.ws_extensions) self.assertEqual(common.VERSION_HYBI_LATEST, request.ws_version) def test_do_handshake_with_extra_headers(self): request_def = _create_good_request_def() # Add headers not related to WebSocket opening handshake. request_def.headers['FooKey'] = 'BarValue' request_def.headers['EmptyKey'] = '' request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual( _EXPECTED_RESPONSE, request.connection.written_data()) def test_do_handshake_with_capitalized_value(self): request_def = _create_good_request_def() request_def.headers['upgrade'] = 'WEBSOCKET' request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual( _EXPECTED_RESPONSE, request.connection.written_data()) request_def = _create_good_request_def() request_def.headers['Connection'] = 'UPGRADE' request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual( _EXPECTED_RESPONSE, request.connection.written_data()) def test_do_handshake_with_multiple_connection_values(self): request_def = _create_good_request_def() request_def.headers['Connection'] = 'Upgrade, keep-alive, , ' request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual( _EXPECTED_RESPONSE, request.connection.written_data()) def test_aborting_handshake(self): handshaker = Handshaker( _create_request(_create_good_request_def()), AbortingDispatcher()) # do_extra_handshake raises an exception. Check that it's not caught by # do_handshake. self.assertRaises(HandshakeAbortedException, handshaker.do_handshake) def test_do_handshake_with_protocol(self): request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Protocol'] = 'chat, superchat' request = _create_request(request_def) handshaker = Handshaker(request, SubprotocolChoosingDispatcher(0)) handshaker.do_handshake() EXPECTED_RESPONSE = ( 'HTTP/1.1 101 Switching Protocols\r\n' 'Upgrade: websocket\r\n' 'Connection: Upgrade\r\n' 'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n' 'Sec-WebSocket-Protocol: chat\r\n\r\n') self.assertEqual(EXPECTED_RESPONSE, request.connection.written_data()) self.assertEqual('chat', request.ws_protocol) def test_do_handshake_protocol_not_in_request_but_in_response(self): request_def = _create_good_request_def() request = _create_request(request_def) handshaker = Handshaker( request, SubprotocolChoosingDispatcher(-1, 'foobar')) # No request has been made but ws_protocol is set. HandshakeException # must be raised. self.assertRaises(HandshakeException, handshaker.do_handshake) def test_do_handshake_with_protocol_no_protocol_selection(self): request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Protocol'] = 'chat, superchat' request = _create_request(request_def) handshaker = _create_handshaker(request) # ws_protocol is not set. HandshakeException must be raised. self.assertRaises(HandshakeException, handshaker.do_handshake) def test_do_handshake_with_extensions(self): request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Extensions'] = ( 'deflate-stream, unknown') EXPECTED_RESPONSE = ( 'HTTP/1.1 101 Switching Protocols\r\n' 'Upgrade: websocket\r\n' 'Connection: Upgrade\r\n' 'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n' 'Sec-WebSocket-Extensions: deflate-stream\r\n\r\n') request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual(EXPECTED_RESPONSE, request.connection.written_data()) self.assertEqual(1, len(request.ws_extensions)) extension = request.ws_extensions[0] self.assertEqual(common.DEFLATE_STREAM_EXTENSION, extension.name()) self.assertEqual(0, len(extension.get_parameter_names())) self.assertEqual(1, len(request.ws_extension_processors)) self.assertEqual(common.DEFLATE_STREAM_EXTENSION, request.ws_extension_processors[0].name()) def test_do_handshake_with_perframe_compress(self): request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Extensions'] = ( 'perframe-compress; method=deflate') request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual(1, len(request.ws_extensions)) self.assertEqual(common.PERFRAME_COMPRESSION_EXTENSION, request.ws_extensions[0].name()) self.assertEqual(1, len(request.ws_extension_processors)) self.assertEqual(common.PERFRAME_COMPRESSION_EXTENSION, request.ws_extension_processors[0].name()) def test_do_handshake_with_permessage_compress(self): request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Extensions'] = ( 'permessage-compress; method=deflate') request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual(1, len(request.ws_extensions)) self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION, request.ws_extensions[0].name()) self.assertEqual(1, len(request.ws_extension_processors)) self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION, request.ws_extension_processors[0].name()) def test_do_handshake_with_quoted_extensions(self): request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Extensions'] = ( 'deflate-stream, , ' 'unknown; e = "mc^2"; ma="\r\n \\\rf "; pv=nrt') request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual(2, len(request.ws_requested_extensions)) first_extension = request.ws_requested_extensions[0] self.assertEqual('deflate-stream', first_extension.name()) self.assertEqual(0, len(first_extension.get_parameter_names())) second_extension = request.ws_requested_extensions[1] self.assertEqual('unknown', second_extension.name()) self.assertEqual( ['e', 'ma', 'pv'], second_extension.get_parameter_names()) self.assertEqual('mc^2', second_extension.get_parameter_value('e')) self.assertEqual(' \rf ', second_extension.get_parameter_value('ma')) self.assertEqual('nrt', second_extension.get_parameter_value('pv')) def test_do_handshake_with_optional_headers(self): request_def = _create_good_request_def() request_def.headers['EmptyValue'] = '' request_def.headers['AKey'] = 'AValue' request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual( 'AValue', request.headers_in['AKey']) self.assertEqual( '', request.headers_in['EmptyValue']) def test_abort_extra_handshake(self): handshaker = Handshaker( _create_request(_create_good_request_def()), AbortedByUserDispatcher()) # do_extra_handshake raises an AbortedByUserException. Check that it's # not caught by do_handshake. self.assertRaises(AbortedByUserException, handshaker.do_handshake) def test_do_handshake_with_mux_and_deflate_frame(self): request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Extensions'] = ('%s, %s' % ( common.MUX_EXTENSION, common.DEFLATE_FRAME_EXTENSION)) request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() # mux should be rejected. self.assertEqual(1, len(request.ws_extensions)) self.assertEqual(common.DEFLATE_FRAME_EXTENSION, request.ws_extensions[0].name()) self.assertEqual(2, len(request.ws_extension_processors)) self.assertEqual(common.MUX_EXTENSION, request.ws_extension_processors[0].name()) self.assertEqual(common.DEFLATE_FRAME_EXTENSION, request.ws_extension_processors[1].name()) self.assertFalse(hasattr(request, 'mux_processor')) def test_do_handshake_with_deflate_frame_and_mux(self): request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Extensions'] = ('%s, %s' % ( common.DEFLATE_FRAME_EXTENSION, common.MUX_EXTENSION)) request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() # mux should be rejected. self.assertEqual(1, len(request.ws_extensions)) first_extension = request.ws_extensions[0] self.assertEqual(common.DEFLATE_FRAME_EXTENSION, first_extension.name()) self.assertEqual(2, len(request.ws_extension_processors)) self.assertEqual(common.DEFLATE_FRAME_EXTENSION, request.ws_extension_processors[0].name()) self.assertEqual(common.MUX_EXTENSION, request.ws_extension_processors[1].name()) self.assertFalse(hasattr(request, 'mux')) def test_do_handshake_with_permessage_compress_and_mux(self): request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Extensions'] = ( '%s; method=deflate, %s' % ( common.PERMESSAGE_COMPRESSION_EXTENSION, common.MUX_EXTENSION)) request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual(1, len(request.ws_extensions)) self.assertEqual(common.MUX_EXTENSION, request.ws_extensions[0].name()) self.assertEqual(2, len(request.ws_extension_processors)) self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION, request.ws_extension_processors[0].name()) self.assertEqual(common.MUX_EXTENSION, request.ws_extension_processors[1].name()) self.assertTrue(hasattr(request, 'mux_processor')) self.assertTrue(request.mux_processor.is_active()) mux_extensions = request.mux_processor.extensions() self.assertEqual(1, len(mux_extensions)) self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION, mux_extensions[0].name()) def test_do_handshake_with_mux_and_permessage_compress(self): request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Extensions'] = ( '%s, %s; method=deflate' % ( common.MUX_EXTENSION, common.PERMESSAGE_COMPRESSION_EXTENSION)) request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() # mux should be rejected. self.assertEqual(1, len(request.ws_extensions)) first_extension = request.ws_extensions[0] self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION, first_extension.name()) self.assertEqual(2, len(request.ws_extension_processors)) self.assertEqual(common.MUX_EXTENSION, request.ws_extension_processors[0].name()) self.assertEqual(common.PERMESSAGE_COMPRESSION_EXTENSION, request.ws_extension_processors[1].name()) self.assertFalse(hasattr(request, 'mux_processor')) def test_bad_requests(self): bad_cases = [ ('HTTP request', RequestDefinition( 'GET', '/demo', {'Host': 'www.google.com', 'User-Agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5;' ' en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3' ' GTB6 GTBA', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,' '*/*;q=0.8', 'Accept-Language': 'en-us,en;q=0.5', 'Accept-Encoding': 'gzip,deflate', 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'Keep-Alive': '300', 'Connection': 'keep-alive'}), None, True)] request_def = _create_good_request_def() request_def.method = 'POST' bad_cases.append(('Wrong method', request_def, None, True)) request_def = _create_good_request_def() del request_def.headers['Host'] bad_cases.append(('Missing Host', request_def, None, True)) request_def = _create_good_request_def() del request_def.headers['Upgrade'] bad_cases.append(('Missing Upgrade', request_def, None, True)) request_def = _create_good_request_def() request_def.headers['Upgrade'] = 'nonwebsocket' bad_cases.append(('Wrong Upgrade', request_def, None, True)) request_def = _create_good_request_def() del request_def.headers['Connection'] bad_cases.append(('Missing Connection', request_def, None, True)) request_def = _create_good_request_def() request_def.headers['Connection'] = 'Downgrade' bad_cases.append(('Wrong Connection', request_def, None, True)) request_def = _create_good_request_def() del request_def.headers['Sec-WebSocket-Key'] bad_cases.append(('Missing Sec-WebSocket-Key', request_def, 400, True)) request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Key'] = ( 'dGhlIHNhbXBsZSBub25jZQ==garbage') bad_cases.append(('Wrong Sec-WebSocket-Key (with garbage on the tail)', request_def, 400, True)) request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Key'] = 'YQ==' # BASE64 of 'a' bad_cases.append( ('Wrong Sec-WebSocket-Key (decoded value is not 16 octets long)', request_def, 400, True)) request_def = _create_good_request_def() # The last character right before == must be any of A, Q, w and g. request_def.headers['Sec-WebSocket-Key'] = ( 'AQIDBAUGBwgJCgsMDQ4PEC==') bad_cases.append( ('Wrong Sec-WebSocket-Key (padding bits are not zero)', request_def, 400, True)) request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Key'] = ( 'dGhlIHNhbXBsZSBub25jZQ==,dGhlIHNhbXBsZSBub25jZQ==') bad_cases.append( ('Wrong Sec-WebSocket-Key (multiple values)', request_def, 400, True)) request_def = _create_good_request_def() del request_def.headers['Sec-WebSocket-Version'] bad_cases.append(('Missing Sec-WebSocket-Version', request_def, None, True)) request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Version'] = '3' bad_cases.append(('Wrong Sec-WebSocket-Version', request_def, None, False)) request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Version'] = '13, 13' bad_cases.append(('Wrong Sec-WebSocket-Version (multiple values)', request_def, 400, True)) request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Protocol'] = 'illegal\x09protocol' bad_cases.append(('Illegal Sec-WebSocket-Protocol', request_def, 400, True)) request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Protocol'] = '' bad_cases.append(('Empty Sec-WebSocket-Protocol', request_def, 400, True)) for (case_name, request_def, expected_status, expect_handshake_exception) in bad_cases: request = _create_request(request_def) handshaker = Handshaker(request, mock.MockDispatcher()) try: handshaker.do_handshake() self.fail('No exception thrown for \'%s\' case' % case_name) except HandshakeException, e: self.assertTrue(expect_handshake_exception) self.assertEqual(expected_status, e.status) except VersionException, e: self.assertFalse(expect_handshake_exception) if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/set_sys_path.py0000640023300700116100000000342711525067446020134 0ustar tyoshinoeng# Copyright 2009, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Configuration for testing. Test files should import this module before mod_pywebsocket. """ import os import sys # Add the parent directory to sys.path to enable importing mod_pywebsocket. sys.path.insert(0, os.path.join(os.path.split(__file__)[0], '..')) # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/test_mux.py0000640023300700116100000026331412055352027017271 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2012, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for mux module.""" import Queue import copy import logging import optparse import struct import sys import unittest import time import zlib import set_sys_path # Update sys.path to locate mod_pywebsocket module. from mod_pywebsocket import common from mod_pywebsocket import mux from mod_pywebsocket._stream_base import ConnectionTerminatedException from mod_pywebsocket._stream_base import UnsupportedFrameException from mod_pywebsocket._stream_hybi import Frame from mod_pywebsocket._stream_hybi import Stream from mod_pywebsocket._stream_hybi import StreamOptions from mod_pywebsocket._stream_hybi import create_binary_frame from mod_pywebsocket._stream_hybi import create_close_frame from mod_pywebsocket._stream_hybi import create_closing_handshake_body from mod_pywebsocket._stream_hybi import parse_frame from mod_pywebsocket.extensions import MuxExtensionProcessor import mock _TEST_HEADERS = {'Host': 'server.example.com', 'Upgrade': 'websocket', 'Connection': 'Upgrade', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Version': '13', 'Origin': 'http://example.com'} class _OutgoingChannelData(object): def __init__(self): self.messages = [] self.control_messages = [] self.builder = mux._InnerMessageBuilder() class _MockMuxConnection(mock.MockBlockingConn): """Mock class of mod_python connection for mux.""" def __init__(self): mock.MockBlockingConn.__init__(self) self._control_blocks = [] self._channel_data = {} self._current_opcode = None self._pending_fragments = [] self.server_close_code = None def write(self, data): """Override MockBlockingConn.write.""" self._current_data = data self._position = 0 def _receive_bytes(length): if self._position + length > len(self._current_data): raise ConnectionTerminatedException( 'Failed to receive %d bytes from encapsulated ' 'frame' % length) data = self._current_data[self._position:self._position+length] self._position += length return data # Parse physical frames and assemble a message if the message is # fragmented. opcode, payload, fin, rsv1, rsv2, rsv3 = ( parse_frame(_receive_bytes, unmask_receive=False)) self._pending_fragments.append(payload) if self._current_opcode is None: if opcode == common.OPCODE_CONTINUATION: raise Exception('Sending invalid continuation opcode') self._current_opcode = opcode else: if opcode != common.OPCODE_CONTINUATION: raise Exception('Sending invalid opcode %d' % opcode) if not fin: return inner_frame_data = ''.join(self._pending_fragments) self._pending_fragments = [] self._current_opcode = None # Handle a control message on the physical channel. # TODO(bashi): Support other opcodes if needed. if opcode == common.OPCODE_CLOSE: if len(payload) >= 2: self.server_close_code = struct.unpack('!H', payload[:2])[0] close_body = create_closing_handshake_body( common.STATUS_NORMAL_CLOSURE, '') close_frame = create_close_frame(close_body, mask=True) self.put_bytes(close_frame) return # Parse the payload of the message on physical channel. parser = mux._MuxFramePayloadParser(inner_frame_data) channel_id = parser.read_channel_id() if channel_id == mux._CONTROL_CHANNEL_ID: self._control_blocks.extend(list(parser.read_control_blocks())) return if not channel_id in self._channel_data: self._channel_data[channel_id] = _OutgoingChannelData() channel_data = self._channel_data[channel_id] # Parse logical frames and assemble an inner (logical) message. (inner_fin, inner_rsv1, inner_rsv2, inner_rsv3, inner_opcode, inner_payload) = parser.read_inner_frame() inner_frame = Frame(inner_fin, inner_rsv1, inner_rsv2, inner_rsv3, inner_opcode, inner_payload) message = channel_data.builder.build(inner_frame) if message is None: return if (message.opcode == common.OPCODE_TEXT or message.opcode == common.OPCODE_BINARY): channel_data.messages.append(message.payload) self.on_data_message(message.payload) else: channel_data.control_messages.append( {'opcode': message.opcode, 'message': message.payload}) def on_data_message(self, message): pass def get_written_control_blocks(self): return self._control_blocks def get_written_messages(self, channel_id): return self._channel_data[channel_id].messages def get_written_control_messages(self, channel_id): return self._channel_data[channel_id].control_messages class _FailOnWriteConnection(_MockMuxConnection): """Specicialized version of _MockMuxConnection. Its write() method raises an exception for testing when a data message is written. """ def on_data_message(self, message): """Override to raise an exception.""" raise Exception('Intentional failure') class _ChannelEvent(object): """A structure that records channel events.""" def __init__(self): self.request = None self.messages = [] self.exception = None self.client_initiated_closing = False class _MuxMockDispatcher(object): """Mock class of dispatch.Dispatcher for mux.""" def __init__(self): self.channel_events = {} def do_extra_handshake(self, request): if request.ws_requested_protocols is not None: request.ws_protocol = request.ws_requested_protocols[0] def _do_echo(self, request, channel_events): while True: message = request.ws_stream.receive_message() if message == None: channel_events.client_initiated_closing = True return if message == 'Goodbye': return channel_events.messages.append(message) # echo back request.ws_stream.send_message(message) def _do_ping(self, request, channel_events): request.ws_stream.send_ping('Ping!') def _do_ping_while_hello_world(self, request, channel_events): request.ws_stream.send_message('Hello ', end=False) request.ws_stream.send_ping('Ping!') request.ws_stream.send_message('World!', end=True) def _do_two_ping_while_hello_world(self, request, channel_events): request.ws_stream.send_message('Hello ', end=False) request.ws_stream.send_ping('Ping!') request.ws_stream.send_ping('Pong!') request.ws_stream.send_message('World!', end=True) def transfer_data(self, request): self.channel_events[request.channel_id] = _ChannelEvent() self.channel_events[request.channel_id].request = request try: # Note: more handler will be added. if request.uri.endswith('echo'): self._do_echo(request, self.channel_events[request.channel_id]) elif request.uri.endswith('ping'): self._do_ping(request, self.channel_events[request.channel_id]) elif request.uri.endswith('two_ping_while_hello_world'): self._do_two_ping_while_hello_world( request, self.channel_events[request.channel_id]) elif request.uri.endswith('ping_while_hello_world'): self._do_ping_while_hello_world( request, self.channel_events[request.channel_id]) else: raise ValueError('Cannot handle path %r' % request.path) if not request.server_terminated: request.ws_stream.close_connection() except ConnectionTerminatedException, e: self.channel_events[request.channel_id].exception = e except Exception, e: self.channel_events[request.channel_id].exception = e raise def _create_mock_request(connection=None, logical_channel_extensions=None): if connection is None: connection = _MockMuxConnection() request = mock.MockRequest(uri='/echo', headers_in=_TEST_HEADERS, connection=connection) request.ws_stream = Stream(request, options=StreamOptions()) request.mux_processor = MuxExtensionProcessor( common.ExtensionParameter(common.MUX_EXTENSION)) if logical_channel_extensions is not None: request.mux_processor.set_extensions(logical_channel_extensions) request.mux_processor.set_quota(8 * 1024) return request def _create_add_channel_request_frame(channel_id, encoding, encoded_handshake): # Allow invalid encoding for testing. first_byte = ((mux._MUX_OPCODE_ADD_CHANNEL_REQUEST << 5) | encoding) payload = (chr(first_byte) + mux._encode_channel_id(channel_id) + mux._encode_number(len(encoded_handshake)) + encoded_handshake) return create_binary_frame( (mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + payload), mask=True) def _create_drop_channel_frame(channel_id, code=None, message=''): payload = mux._create_drop_channel(channel_id, code, message) return create_binary_frame( (mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + payload), mask=True) def _create_flow_control_frame(channel_id, replenished_quota): payload = mux._create_flow_control(channel_id, replenished_quota) return create_binary_frame( (mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + payload), mask=True) def _create_logical_frame(channel_id, message, opcode=common.OPCODE_BINARY, fin=True, rsv1=False, rsv2=False, rsv3=False, mask=True): bits = chr((fin << 7) | (rsv1 << 6) | (rsv2 << 5) | (rsv3 << 4) | opcode) payload = mux._encode_channel_id(channel_id) + bits + message return create_binary_frame(payload, mask=True) def _create_request_header(path='/echo', extensions=None): headers = ( 'GET %s HTTP/1.1\r\n' 'Host: server.example.com\r\n' 'Connection: Upgrade\r\n' 'Origin: http://example.com\r\n') % path if extensions: headers += '%s: %s' % ( common.SEC_WEBSOCKET_EXTENSIONS_HEADER, extensions) return headers class MuxTest(unittest.TestCase): """A unittest for mux module.""" def test_channel_id_decode(self): data = '\x00\x01\xbf\xff\xdf\xff\xff\xff\xff\xff\xff' parser = mux._MuxFramePayloadParser(data) channel_id = parser.read_channel_id() self.assertEqual(0, channel_id) channel_id = parser.read_channel_id() self.assertEqual(1, channel_id) channel_id = parser.read_channel_id() self.assertEqual(2 ** 14 - 1, channel_id) channel_id = parser.read_channel_id() self.assertEqual(2 ** 21 - 1, channel_id) channel_id = parser.read_channel_id() self.assertEqual(2 ** 29 - 1, channel_id) self.assertEqual(len(data), parser._read_position) def test_channel_id_encode(self): encoded = mux._encode_channel_id(0) self.assertEqual('\x00', encoded) encoded = mux._encode_channel_id(2 ** 14 - 1) self.assertEqual('\xbf\xff', encoded) encoded = mux._encode_channel_id(2 ** 14) self.assertEqual('\xc0@\x00', encoded) encoded = mux._encode_channel_id(2 ** 21 - 1) self.assertEqual('\xdf\xff\xff', encoded) encoded = mux._encode_channel_id(2 ** 21) self.assertEqual('\xe0 \x00\x00', encoded) encoded = mux._encode_channel_id(2 ** 29 - 1) self.assertEqual('\xff\xff\xff\xff', encoded) # channel_id is too large self.assertRaises(ValueError, mux._encode_channel_id, 2 ** 29) def test_read_multiple_control_blocks(self): # Use AddChannelRequest because it can contain arbitrary length of data data = ('\x00\x01\x01a' '\x00\x02\x7d%s' '\x00\x03\x7e\xff\xff%s' '\x00\x04\x7f\x00\x00\x00\x00\x00\x01\x00\x00%s') % ( 'a' * 0x7d, 'b' * 0xffff, 'c' * 0x10000) parser = mux._MuxFramePayloadParser(data) blocks = list(parser.read_control_blocks()) self.assertEqual(4, len(blocks)) self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[0].opcode) self.assertEqual(1, blocks[0].channel_id) self.assertEqual(1, len(blocks[0].encoded_handshake)) self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[1].opcode) self.assertEqual(2, blocks[1].channel_id) self.assertEqual(0x7d, len(blocks[1].encoded_handshake)) self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[2].opcode) self.assertEqual(3, blocks[2].channel_id) self.assertEqual(0xffff, len(blocks[2].encoded_handshake)) self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[3].opcode) self.assertEqual(4, blocks[3].channel_id) self.assertEqual(0x10000, len(blocks[3].encoded_handshake)) self.assertEqual(len(data), parser._read_position) def test_read_add_channel_request(self): data = '\x00\x01\x01a' parser = mux._MuxFramePayloadParser(data) blocks = list(parser.read_control_blocks()) self.assertEqual(mux._MUX_OPCODE_ADD_CHANNEL_REQUEST, blocks[0].opcode) self.assertEqual(1, blocks[0].channel_id) self.assertEqual(1, len(blocks[0].encoded_handshake)) def test_read_drop_channel(self): data = '\x60\x01\x00' parser = mux._MuxFramePayloadParser(data) blocks = list(parser.read_control_blocks()) self.assertEqual(1, len(blocks)) self.assertEqual(1, blocks[0].channel_id) self.assertEqual(mux._MUX_OPCODE_DROP_CHANNEL, blocks[0].opcode) self.assertEqual(None, blocks[0].drop_code) self.assertEqual(0, len(blocks[0].drop_message)) data = '\x60\x02\x09\x03\xe8Success' parser = mux._MuxFramePayloadParser(data) blocks = list(parser.read_control_blocks()) self.assertEqual(1, len(blocks)) self.assertEqual(2, blocks[0].channel_id) self.assertEqual(mux._MUX_OPCODE_DROP_CHANNEL, blocks[0].opcode) self.assertEqual(1000, blocks[0].drop_code) self.assertEqual('Success', blocks[0].drop_message) # Reason is too short. data = '\x60\x01\x01\x00' parser = mux._MuxFramePayloadParser(data) self.assertRaises(mux.PhysicalConnectionError, lambda: list(parser.read_control_blocks())) def test_read_flow_control(self): data = '\x40\x01\x02' parser = mux._MuxFramePayloadParser(data) blocks = list(parser.read_control_blocks()) self.assertEqual(1, len(blocks)) self.assertEqual(1, blocks[0].channel_id) self.assertEqual(mux._MUX_OPCODE_FLOW_CONTROL, blocks[0].opcode) self.assertEqual(2, blocks[0].send_quota) def test_read_new_channel_slot(self): data = '\x80\x01\x02\x02\x03' parser = mux._MuxFramePayloadParser(data) # TODO(bashi): Implement self.assertRaises(mux.PhysicalConnectionError, lambda: list(parser.read_control_blocks())) def test_read_invalid_number_field_in_control_block(self): # No number field. data = '' parser = mux._MuxFramePayloadParser(data) self.assertRaises(ValueError, parser._read_number) # The last two bytes are missing. data = '\x7e' parser = mux._MuxFramePayloadParser(data) self.assertRaises(ValueError, parser._read_number) # Missing the last one byte. data = '\x7f\x00\x00\x00\x00\x00\x01\x00' parser = mux._MuxFramePayloadParser(data) self.assertRaises(ValueError, parser._read_number) # The length of number field is too large. data = '\x7f\xff\xff\xff\xff\xff\xff\xff\xff' parser = mux._MuxFramePayloadParser(data) self.assertRaises(ValueError, parser._read_number) # The msb of the first byte is set. data = '\x80' parser = mux._MuxFramePayloadParser(data) self.assertRaises(ValueError, parser._read_number) # Using 3 bytes encoding for 125. data = '\x7e\x00\x7d' parser = mux._MuxFramePayloadParser(data) self.assertRaises(ValueError, parser._read_number) # Using 9 bytes encoding for 0xffff data = '\x7f\x00\x00\x00\x00\x00\x00\xff\xff' parser = mux._MuxFramePayloadParser(data) self.assertRaises(ValueError, parser._read_number) def test_read_invalid_size_and_contents(self): # Only contain number field. data = '\x01' parser = mux._MuxFramePayloadParser(data) self.assertRaises(mux.PhysicalConnectionError, parser._read_size_and_contents) def test_create_add_channel_response(self): data = mux._create_add_channel_response(channel_id=1, encoded_handshake='FooBar', encoding=0, rejected=False) self.assertEqual('\x20\x01\x06FooBar', data) data = mux._create_add_channel_response(channel_id=2, encoded_handshake='Hello', encoding=1, rejected=True) self.assertEqual('\x31\x02\x05Hello', data) def test_create_drop_channel(self): data = mux._create_drop_channel(channel_id=1) self.assertEqual('\x60\x01\x00', data) data = mux._create_drop_channel(channel_id=1, code=2000, message='error') self.assertEqual('\x60\x01\x07\x07\xd0error', data) # reason must be empty if code is None self.assertRaises(ValueError, mux._create_drop_channel, 1, None, 'FooBar') def test_parse_request_text(self): request_text = _create_request_header() command, path, version, headers = mux._parse_request_text(request_text) self.assertEqual('GET', command) self.assertEqual('/echo', path) self.assertEqual('HTTP/1.1', version) self.assertEqual(3, len(headers)) self.assertEqual('server.example.com', headers['Host']) self.assertEqual('http://example.com', headers['Origin']) class MuxHandlerTest(unittest.TestCase): def test_add_channel(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=6) request.connection.put_bytes(flow_control) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=3, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=3, replenished_quota=6) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Hello')) request.connection.put_bytes( _create_logical_frame(channel_id=3, message='World')) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=3, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) self.assertEqual([], dispatcher.channel_events[1].messages) self.assertEqual(['Hello'], dispatcher.channel_events[2].messages) self.assertEqual(['World'], dispatcher.channel_events[3].messages) # Channel 2 messages = request.connection.get_written_messages(2) self.assertEqual(1, len(messages)) self.assertEqual('Hello', messages[0]) # Channel 3 messages = request.connection.get_written_messages(3) self.assertEqual(1, len(messages)) self.assertEqual('World', messages[0]) control_blocks = request.connection.get_written_control_blocks() # There should be 8 control blocks: # - 1 NewChannelSlot # - 2 AddChannelResponses for channel id 2 and 3 # - 6 FlowControls for channel id 1 (initialize), 'Hello', 'World', # and 3 'Goodbye's self.assertEqual(9, len(control_blocks)) def test_physical_connection_write_failure(self): # Use _FailOnWriteConnection. request = _create_mock_request(connection=_FailOnWriteConnection()) dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() # Let the worker echo back 'Hello'. It causes _FailOnWriteConnection # raising an exception. request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Hello')) # Let the worker exit. This will be unnecessary when # _LogicalConnection.write() is changed to throw an exception if # woke up by on_writer_done. request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) # All threads should be done. self.assertTrue(mux_handler.wait_until_done(timeout=2)) def test_send_blocked(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) # On receiving this 'Hello', the server tries to echo back 'Hello', # but it will be blocked since there's no send quota available for the # channel 2. request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Hello')) # Wait until the worker is blocked due to send quota shortage. time.sleep(1) # Close the channel 2. The worker should be notified of the end of # writer thread and stop waiting for send quota to be replenished. drop_channel = _create_drop_channel_frame(channel_id=2) request.connection.put_bytes(drop_channel) # Make sure the channel 1 is also closed. drop_channel = _create_drop_channel_frame(channel_id=1) request.connection.put_bytes(drop_channel) # All threads should be done. self.assertTrue(mux_handler.wait_until_done(timeout=2)) def test_add_channel_delta_encoding(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) delta = 'GET /echo HTTP/1.1\r\n\r\n' add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=1, encoded_handshake=delta) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=6) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Hello')) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) self.assertEqual(['Hello'], dispatcher.channel_events[2].messages) messages = request.connection.get_written_messages(2) self.assertEqual(1, len(messages)) self.assertEqual('Hello', messages[0]) def test_add_channel_delta_encoding_override(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) # Override Sec-WebSocket-Protocol. delta = ('GET /echo HTTP/1.1\r\n' 'Sec-WebSocket-Protocol: x-foo\r\n' '\r\n') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=1, encoded_handshake=delta) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=6) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Hello')) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) self.assertEqual(['Hello'], dispatcher.channel_events[2].messages) messages = request.connection.get_written_messages(2) self.assertEqual(1, len(messages)) self.assertEqual('Hello', messages[0]) self.assertEqual('x-foo', dispatcher.channel_events[2].request.ws_protocol) def test_add_channel_delta_after_identity(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) # Sec-WebSocket-Protocol is different from client's opening handshake # of the physical connection. # TODO(bashi): Remove Upgrade, Connection, Sec-WebSocket-Key and # Sec-WebSocket-Version. encoded_handshake = ( 'GET /echo HTTP/1.1\r\n' 'Host: server.example.com\r\n' 'Sec-WebSocket-Protocol: x-foo\r\n' 'Connection: Upgrade\r\n' 'Origin: http://example.com\r\n' '\r\n') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=6) request.connection.put_bytes(flow_control) delta = 'GET /echo HTTP/1.1\r\n\r\n' add_channel_request = _create_add_channel_request_frame( channel_id=3, encoding=1, encoded_handshake=delta) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=3, replenished_quota=6) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Hello')) request.connection.put_bytes( _create_logical_frame(channel_id=3, message='World')) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=3, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) self.assertEqual([], dispatcher.channel_events[1].messages) self.assertEqual(['Hello'], dispatcher.channel_events[2].messages) self.assertEqual(['World'], dispatcher.channel_events[3].messages) # Channel 2 messages = request.connection.get_written_messages(2) self.assertEqual(1, len(messages)) self.assertEqual('Hello', messages[0]) # Channel 3 messages = request.connection.get_written_messages(3) self.assertEqual(1, len(messages)) self.assertEqual('World', messages[0]) # Handshake base should be updated. self.assertEqual( 'x-foo', mux_handler._handshake_base._headers['Sec-WebSocket-Protocol']) def test_add_channel_delta_remove_header(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) # Override handshake delta base. encoded_handshake = ( 'GET /echo HTTP/1.1\r\n' 'Host: server.example.com\r\n' 'Sec-WebSocket-Protocol: x-foo\r\n' 'Connection: Upgrade\r\n' 'Origin: http://example.com\r\n' '\r\n') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=6) request.connection.put_bytes(flow_control) # Remove Sec-WebSocket-Protocol header. delta = ('GET /echo HTTP/1.1\r\n' 'Sec-WebSocket-Protocol:' '\r\n') add_channel_request = _create_add_channel_request_frame( channel_id=3, encoding=1, encoded_handshake=delta) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=3, replenished_quota=6) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Hello')) request.connection.put_bytes( _create_logical_frame(channel_id=3, message='World')) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=3, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) self.assertEqual([], dispatcher.channel_events[1].messages) self.assertEqual(['Hello'], dispatcher.channel_events[2].messages) self.assertEqual(['World'], dispatcher.channel_events[3].messages) # Channel 2 messages = request.connection.get_written_messages(2) self.assertEqual(1, len(messages)) self.assertEqual('Hello', messages[0]) # Channel 3 messages = request.connection.get_written_messages(3) self.assertEqual(1, len(messages)) self.assertEqual('World', messages[0]) self.assertEqual( 'x-foo', dispatcher.channel_events[2].request.ws_protocol) self.assertEqual( None, dispatcher.channel_events[3].request.ws_protocol) def test_add_channel_delta_encoding_permessage_compress(self): # Enable permessage compress extension on the implicitly opened channel. extensions = common.parse_extensions( '%s; method=deflate' % common.PERMESSAGE_COMPRESSION_EXTENSION) request = _create_mock_request( logical_channel_extensions=extensions) dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) delta = 'GET /echo HTTP/1.1\r\n\r\n' add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=1, encoded_handshake=delta) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=20) request.connection.put_bytes(flow_control) # Send compressed 'Hello' on logical channel 1 and 2. compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_hello = compress.compress('Hello') compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) compressed_hello = compressed_hello[:-4] request.connection.put_bytes( _create_logical_frame(channel_id=1, message=compressed_hello, rsv1=True)) request.connection.put_bytes( _create_logical_frame(channel_id=2, message=compressed_hello, rsv1=True)) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) self.assertEqual(['Hello'], dispatcher.channel_events[1].messages) self.assertEqual(['Hello'], dispatcher.channel_events[2].messages) # Written 'Hello's should be compressed. messages = request.connection.get_written_messages(1) self.assertEqual(1, len(messages)) self.assertEqual(compressed_hello, messages[0]) messages = request.connection.get_written_messages(2) self.assertEqual(1, len(messages)) self.assertEqual(compressed_hello, messages[0]) def test_add_channel_delta_encoding_remove_extensions(self): # Enable permessage compress extension on the implicitly opened channel. extensions = common.parse_extensions( '%s; method=deflate' % common.PERMESSAGE_COMPRESSION_EXTENSION) request = _create_mock_request( logical_channel_extensions=extensions) dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) # Remove permessage compress extension. delta = ('GET /echo HTTP/1.1\r\n' 'Sec-WebSocket-Extensions:\r\n' '\r\n') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=1, encoded_handshake=delta) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=20) request.connection.put_bytes(flow_control) # Send compressed message on logical channel 2. The message should # be rejected (since rsv1 is set). compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_hello = compress.compress('Hello') compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) compressed_hello = compressed_hello[:-4] request.connection.put_bytes( _create_logical_frame(channel_id=2, message=compressed_hello, rsv1=True)) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) drop_channel = next( b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) self.assertEqual(mux._DROP_CODE_NORMAL_CLOSURE, drop_channel.drop_code) self.assertEqual(2, drop_channel.channel_id) # UnsupportedFrameException should be raised on logical channel 2. self.assertTrue(isinstance(dispatcher.channel_events[2].exception, UnsupportedFrameException)) def test_add_channel_invalid_encoding(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=3, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) self.assertTrue(mux_handler.wait_until_done(timeout=2)) drop_channel = next( b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) self.assertEqual(mux._DROP_CODE_UNKNOWN_REQUEST_ENCODING, drop_channel.drop_code) self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR, request.connection.server_close_code) def test_add_channel_incomplete_handshake(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) incomplete_encoded_handshake = 'GET /echo HTTP/1.1' add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=incomplete_encoded_handshake) request.connection.put_bytes(add_channel_request) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) self.assertTrue(1 in dispatcher.channel_events) self.assertTrue(not 2 in dispatcher.channel_events) def test_add_channel_duplicate_channel_id(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) self.assertTrue(mux_handler.wait_until_done(timeout=2)) drop_channel = next( b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) self.assertEqual(mux._DROP_CODE_CHANNEL_ALREADY_EXISTS, drop_channel.drop_code) self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR, request.connection.server_close_code) def test_receive_drop_channel(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) drop_channel = _create_drop_channel_frame(channel_id=2) request.connection.put_bytes(drop_channel) # Terminate implicitly opened channel. request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) exception = dispatcher.channel_events[2].exception self.assertTrue(exception.__class__ == ConnectionTerminatedException) def test_receive_ping_frame(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=13) request.connection.put_bytes(flow_control) ping_frame = _create_logical_frame(channel_id=2, message='Hello World!', opcode=common.OPCODE_PING) request.connection.put_bytes(ping_frame) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) messages = request.connection.get_written_control_messages(2) self.assertEqual(common.OPCODE_PONG, messages[0]['opcode']) self.assertEqual('Hello World!', messages[0]['message']) def test_receive_fragmented_ping(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=13) request.connection.put_bytes(flow_control) # Send a ping with message 'Hello world!' in two fragmented frames. ping_frame1 = _create_logical_frame(channel_id=2, message='Hello ', fin=False, opcode=common.OPCODE_PING) request.connection.put_bytes(ping_frame1) ping_frame2 = _create_logical_frame(channel_id=2, message='World!', fin=True, opcode=common.OPCODE_CONTINUATION) request.connection.put_bytes(ping_frame2) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) messages = request.connection.get_written_control_messages(2) self.assertEqual(common.OPCODE_PONG, messages[0]['opcode']) self.assertEqual('Hello World!', messages[0]['message']) def test_receive_fragmented_ping_while_receiving_fragmented_message(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=19) request.connection.put_bytes(flow_control) # Send a fragmented frame of message 'Hello '. hello = _create_logical_frame(channel_id=2, message='Hello ', fin=False) request.connection.put_bytes(hello) # Before sending the last fragmented frame of the message, send a # fragmented ping. ping1 = _create_logical_frame(channel_id=2, message='Pi', fin=False, opcode=common.OPCODE_PING) request.connection.put_bytes(ping1) ping2 = _create_logical_frame(channel_id=2, message='ng!', fin=True, opcode=common.OPCODE_CONTINUATION) request.connection.put_bytes(ping2) # Send the last fragmented frame of the message. world = _create_logical_frame(channel_id=2, message='World!', fin=True, opcode=common.OPCODE_CONTINUATION) request.connection.put_bytes(world) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) messages = request.connection.get_written_messages(2) self.assertEqual(['Hello World!'], messages) control_messages = request.connection.get_written_control_messages(2) self.assertEqual(common.OPCODE_PONG, control_messages[0]['opcode']) self.assertEqual('Ping!', control_messages[0]['message']) def test_receive_two_ping_while_receiving_fragmented_message(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=25) request.connection.put_bytes(flow_control) # Send a fragmented frame of message 'Hello '. hello = _create_logical_frame(channel_id=2, message='Hello ', fin=False) request.connection.put_bytes(hello) # Before sending the last fragmented frame of the message, send a # fragmented ping and a non-fragmented ping. ping1 = _create_logical_frame(channel_id=2, message='Pi', fin=False, opcode=common.OPCODE_PING) request.connection.put_bytes(ping1) ping2 = _create_logical_frame(channel_id=2, message='ng!', fin=True, opcode=common.OPCODE_CONTINUATION) request.connection.put_bytes(ping2) ping3 = _create_logical_frame(channel_id=2, message='Pong!', fin=True, opcode=common.OPCODE_PING) request.connection.put_bytes(ping3) # Send the last fragmented frame of the message. world = _create_logical_frame(channel_id=2, message='World!', fin=True, opcode=common.OPCODE_CONTINUATION) request.connection.put_bytes(world) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) messages = request.connection.get_written_messages(2) self.assertEqual(['Hello World!'], messages) control_messages = request.connection.get_written_control_messages(2) self.assertEqual(common.OPCODE_PONG, control_messages[0]['opcode']) self.assertEqual('Ping!', control_messages[0]['message']) self.assertEqual(common.OPCODE_PONG, control_messages[1]['opcode']) self.assertEqual('Pong!', control_messages[1]['message']) def test_receive_message_while_receiving_fragmented_ping(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=19) request.connection.put_bytes(flow_control) # Send a fragmented ping. ping1 = _create_logical_frame(channel_id=2, message='Pi', fin=False, opcode=common.OPCODE_PING) request.connection.put_bytes(ping1) # Before sending the last fragmented ping, send a message. # The logical channel (2) should be dropped. message = _create_logical_frame(channel_id=2, message='Hello world!', fin=True) request.connection.put_bytes(message) # Send the last fragmented frame of the message. ping2 = _create_logical_frame(channel_id=2, message='ng!', fin=True, opcode=common.OPCODE_CONTINUATION) request.connection.put_bytes(ping2) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) drop_channel = next( b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) self.assertEqual(2, drop_channel.channel_id) # No message should be sent on channel 2. self.assertRaises(KeyError, request.connection.get_written_messages, 2) self.assertRaises(KeyError, request.connection.get_written_control_messages, 2) def test_send_ping(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/ping') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=6) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) messages = request.connection.get_written_control_messages(2) self.assertEqual(common.OPCODE_PING, messages[0]['opcode']) self.assertEqual('Ping!', messages[0]['message']) def test_send_fragmented_ping(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/ping') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) # Replenish 3 bytes. This isn't enough to send the whole ping frame # because the frame will have 5 bytes message('Ping!'). The frame # should be fragmented. flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=3) request.connection.put_bytes(flow_control) # Wait until the worker is blocked due to send quota shortage. time.sleep(1) # Replenish remaining 2 + 1 bytes (including extra cost). flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=3) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) messages = request.connection.get_written_control_messages(2) self.assertEqual(common.OPCODE_PING, messages[0]['opcode']) self.assertEqual('Ping!', messages[0]['message']) def test_send_fragmented_ping_while_sending_fragmented_message(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header( path='/ping_while_hello_world') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) # Application will send: # - text message 'Hello ' with fin=0 # - ping with 'Ping!' message # - text message 'World!' with fin=1 # Replenish (6 + 1) + (2 + 1) bytes so that the ping will be # fragmented on the logical channel. flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=10) request.connection.put_bytes(flow_control) time.sleep(1) # Replenish remaining 3 + 6 bytes. flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=9) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) messages = request.connection.get_written_messages(2) self.assertEqual(['Hello World!'], messages) control_messages = request.connection.get_written_control_messages(2) self.assertEqual(common.OPCODE_PING, control_messages[0]['opcode']) self.assertEqual('Ping!', control_messages[0]['message']) def test_send_fragmented_two_ping_while_sending_fragmented_message(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header( path='/two_ping_while_hello_world') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) # Application will send: # - text message 'Hello ' with fin=0 # - ping with 'Ping!' message # - ping with 'Pong!' message # - text message 'World!' with fin=1 # Replenish (6 + 1) + (2 + 1) bytes so that the first ping will be # fragmented on the logical channel. flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=10) request.connection.put_bytes(flow_control) time.sleep(1) # Replenish remaining 3 + (5 + 1) + 6 bytes. The second ping won't # be fragmented on the logical channel. flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=15) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) messages = request.connection.get_written_messages(2) self.assertEqual(['Hello World!'], messages) control_messages = request.connection.get_written_control_messages(2) self.assertEqual(common.OPCODE_PING, control_messages[0]['opcode']) self.assertEqual('Ping!', control_messages[0]['message']) self.assertEqual(common.OPCODE_PING, control_messages[1]['opcode']) self.assertEqual('Pong!', control_messages[1]['message']) def test_send_drop_channel(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() # DropChannel for channel id 1 which doesn't have reason. frame = create_binary_frame('\x00\x60\x01\x00', mask=True) request.connection.put_bytes(frame) self.assertTrue(mux_handler.wait_until_done(timeout=2)) drop_channel = next( b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) self.assertEqual(mux._DROP_CODE_ACKNOWLEDGED, drop_channel.drop_code) self.assertEqual(1, drop_channel.channel_id) def test_two_flow_control(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) # Replenish 5 bytes. flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=5) request.connection.put_bytes(flow_control) # Send 10 bytes. The server will try echo back 10 bytes. request.connection.put_bytes( _create_logical_frame(channel_id=2, message='HelloWorld')) # Replenish 5 + 1 (per-message extra cost) bytes. flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=6) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) messages = request.connection.get_written_messages(2) self.assertEqual(['HelloWorld'], messages) received_flow_controls = [ b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_FLOW_CONTROL and b.channel_id == 2] # Replenishment for 'HelloWorld' + 1 self.assertEqual(11, received_flow_controls[0].send_quota) # Replenishment for 'Goodbye' + 1 self.assertEqual(8, received_flow_controls[1].send_quota) def test_no_send_quota_on_server(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='HelloWorld')) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) # Just wait for 1 sec so that the server attempts to echo back # 'HelloWorld'. self.assertFalse(mux_handler.wait_until_done(timeout=1)) # No message should be sent on channel 2. self.assertRaises(KeyError, request.connection.get_written_messages, 2) def test_no_send_quota_on_server_for_permessage_extra_cost(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=6) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Hello')) # Replenish only len('World') bytes. flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=5) request.connection.put_bytes(flow_control) # Server should not callback for this message. request.connection.put_bytes( _create_logical_frame(channel_id=2, message='World')) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) # Just wait for 1 sec so that the server attempts to echo back # 'World'. self.assertFalse(mux_handler.wait_until_done(timeout=1)) # Only one message should be sent on channel 2. messages = request.connection.get_written_messages(2) self.assertEqual(['Hello'], messages) def test_quota_violation_by_client(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, 0) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='HelloWorld')) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) control_blocks = request.connection.get_written_control_blocks() self.assertEqual(5, len(control_blocks)) drop_channel = next( b for b in control_blocks if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) self.assertEqual(mux._DROP_CODE_SEND_QUOTA_VIOLATION, drop_channel.drop_code) def test_consume_quota_empty_message(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() # Client has 1 byte quota. mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, 1) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=2) request.connection.put_bytes(flow_control) # Send an empty message. Pywebsocket always replenishes 1 byte quota # for empty message request.connection.put_bytes( _create_logical_frame(channel_id=2, message='')) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) # This message violates quota on channel id 2. request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) self.assertEqual(1, len(dispatcher.channel_events[2].messages)) self.assertEqual('', dispatcher.channel_events[2].messages[0]) received_flow_controls = [ b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_FLOW_CONTROL and b.channel_id == 2] self.assertEqual(1, len(received_flow_controls)) self.assertEqual(1, received_flow_controls[0].send_quota) drop_channel = next( b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) self.assertEqual(2, drop_channel.channel_id) self.assertEqual(mux._DROP_CODE_SEND_QUOTA_VIOLATION, drop_channel.drop_code) def test_consume_quota_fragmented_message(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() # Client has len('Hello') + len('Goodbye') + 2 bytes quota. mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, 14) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=6) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='He', fin=False, opcode=common.OPCODE_TEXT)) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='llo', fin=True, opcode=common.OPCODE_CONTINUATION)) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) messages = request.connection.get_written_messages(2) self.assertEqual(['Hello'], messages) def test_fragmented_control_message(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/ping') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) # Replenish total 6 bytes in 3 FlowControls. flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=1) request.connection.put_bytes(flow_control) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=2) request.connection.put_bytes(flow_control) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=3) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) messages = request.connection.get_written_control_messages(2) self.assertEqual(common.OPCODE_PING, messages[0]['opcode']) self.assertEqual('Ping!', messages[0]['message']) def test_channel_slot_violation_by_client(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(slots=1, send_quota=mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=6) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Hello')) # This request should be rejected. encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=3, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=3, replenished_quota=6) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=3, message='Hello')) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) self.assertEqual([], dispatcher.channel_events[1].messages) self.assertEqual(['Hello'], dispatcher.channel_events[2].messages) self.assertFalse(dispatcher.channel_events.has_key(3)) drop_channel = next( b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) self.assertEqual(3, drop_channel.channel_id) self.assertEqual(mux._DROP_CODE_NEW_CHANNEL_SLOT_VIOLATION, drop_channel.drop_code) def test_quota_overflow_by_client(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(slots=1, send_quota=mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) # Replenish 0x7FFFFFFFFFFFFFFF bytes twice. flow_control = _create_flow_control_frame( channel_id=2, replenished_quota=0x7FFFFFFFFFFFFFFF) request.connection.put_bytes(flow_control) request.connection.put_bytes(flow_control) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) drop_channel = next( b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) self.assertEqual(2, drop_channel.channel_id) self.assertEqual(mux._DROP_CODE_SEND_QUOTA_OVERFLOW, drop_channel.drop_code) def test_invalid_encapsulated_message(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() first_byte = (mux._MUX_OPCODE_ADD_CHANNEL_REQUEST << 5) block = (chr(first_byte) + mux._encode_channel_id(1) + mux._encode_number(0)) payload = mux._encode_channel_id(mux._CONTROL_CHANNEL_ID) + block text_frame = create_binary_frame(payload, opcode=common.OPCODE_TEXT, mask=True) request.connection.put_bytes(text_frame) self.assertTrue(mux_handler.wait_until_done(timeout=2)) drop_channel = next( b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) self.assertEqual(mux._DROP_CODE_INVALID_ENCAPSULATING_MESSAGE, drop_channel.drop_code) self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR, request.connection.server_close_code) def test_channel_id_truncated(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() # The last byte of the channel id is missing. frame = create_binary_frame('\x80', mask=True) request.connection.put_bytes(frame) self.assertTrue(mux_handler.wait_until_done(timeout=2)) drop_channel = next( b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) self.assertEqual(mux._DROP_CODE_CHANNEL_ID_TRUNCATED, drop_channel.drop_code) self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR, request.connection.server_close_code) def test_inner_frame_truncated(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() # Just contain channel id 1. frame = create_binary_frame('\x01', mask=True) request.connection.put_bytes(frame) self.assertTrue(mux_handler.wait_until_done(timeout=2)) drop_channel = next( b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) self.assertEqual(mux._DROP_CODE_ENCAPSULATED_FRAME_IS_TRUNCATED, drop_channel.drop_code) self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR, request.connection.server_close_code) def test_unknown_mux_opcode(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() # Undefined opcode 5 frame = create_binary_frame('\x00\xa0', mask=True) request.connection.put_bytes(frame) self.assertTrue(mux_handler.wait_until_done(timeout=2)) drop_channel = next( b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) self.assertEqual(mux._DROP_CODE_UNKNOWN_MUX_OPCODE, drop_channel.drop_code) self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR, request.connection.server_close_code) def test_invalid_mux_control_block(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() # DropChannel contains 1 byte reason frame = create_binary_frame('\x00\x60\x00\x01\x00', mask=True) request.connection.put_bytes(frame) self.assertTrue(mux_handler.wait_until_done(timeout=2)) drop_channel = next( b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL) self.assertEqual(mux._DROP_CODE_INVALID_MUX_CONTROL_BLOCK, drop_channel.drop_code) self.assertEqual(common.STATUS_INTERNAL_ENDPOINT_ERROR, request.connection.server_close_code) def test_permessage_compress(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) # Enable permessage compress extension on logical channel 2. extensions = '%s; method=deflate' % ( common.PERMESSAGE_COMPRESSION_EXTENSION) encoded_handshake = _create_request_header(path='/echo', extensions=extensions) add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) flow_control = _create_flow_control_frame(channel_id=2, replenished_quota=20) request.connection.put_bytes(flow_control) # Send compressed 'Hello' twice. compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_hello1 = compress.compress('Hello') compressed_hello1 += compress.flush(zlib.Z_SYNC_FLUSH) compressed_hello1 = compressed_hello1[:-4] request.connection.put_bytes( _create_logical_frame(channel_id=2, message=compressed_hello1, rsv1=True)) compressed_hello2 = compress.compress('Hello') compressed_hello2 += compress.flush(zlib.Z_SYNC_FLUSH) compressed_hello2 = compressed_hello2[:-4] request.connection.put_bytes( _create_logical_frame(channel_id=2, message=compressed_hello2, rsv1=True)) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) request.connection.put_bytes( _create_logical_frame(channel_id=2, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) self.assertEqual(['Hello', 'Hello'], dispatcher.channel_events[2].messages) # Written 'Hello's should be compressed. messages = request.connection.get_written_messages(2) self.assertEqual(2, len(messages)) self.assertEqual(compressed_hello1, messages[0]) self.assertEqual(compressed_hello2, messages[1]) def test_permessage_compress_fragmented_message(self): extensions = common.parse_extensions( '%s; method=deflate' % common.PERMESSAGE_COMPRESSION_EXTENSION) request = _create_mock_request( logical_channel_extensions=extensions) dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) # Send compressed 'HelloHelloHello' as fragmented message. compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_hello = compress.compress('HelloHelloHello') compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) compressed_hello = compressed_hello[:-4] m = len(compressed_hello) / 2 request.connection.put_bytes( _create_logical_frame(channel_id=1, message=compressed_hello[:m], fin=False, rsv1=True, opcode=common.OPCODE_TEXT)) request.connection.put_bytes( _create_logical_frame(channel_id=1, message=compressed_hello[m:], fin=True, rsv1=False, opcode=common.OPCODE_CONTINUATION)) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) self.assertEqual(['HelloHelloHello'], dispatcher.channel_events[1].messages) messages = request.connection.get_written_messages(1) self.assertEqual(1, len(messages)) self.assertEqual(compressed_hello, messages[0]) def test_receive_bad_fragmented_message(self): request = _create_mock_request() dispatcher = _MuxMockDispatcher() mux_handler = mux._MuxHandler(request, dispatcher) mux_handler.start() mux_handler.add_channel_slots(mux._INITIAL_NUMBER_OF_CHANNEL_SLOTS, mux._INITIAL_QUOTA_FOR_CLIENT) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=2, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) # Send a frame with fin=False, and then send a frame with # opcode=TEXT (not CONTINUATION). Logical channel 2 should be dropped. frame1 = _create_logical_frame(channel_id=2, message='Hello ', fin=False, opcode=common.OPCODE_TEXT) request.connection.put_bytes(frame1) frame2 = _create_logical_frame(channel_id=2, message='World!', fin=True, opcode=common.OPCODE_TEXT) request.connection.put_bytes(frame2) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=3, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) # Send a frame with opcode=CONTINUATION without a preceding frame # the fin of which is not set. Logical channel 3 should be dropped. frame3 = _create_logical_frame(channel_id=3, message='Hello', fin=True, opcode=common.OPCODE_CONTINUATION) request.connection.put_bytes(frame3) encoded_handshake = _create_request_header(path='/echo') add_channel_request = _create_add_channel_request_frame( channel_id=4, encoding=0, encoded_handshake=encoded_handshake) request.connection.put_bytes(add_channel_request) # Send a frame with opcode=PING and fin=False, and then send a frame # with opcode=TEXT (not CONTINUATION). Logical channel 4 should be # dropped. frame4 = _create_logical_frame(channel_id=4, message='Ping', fin=False, opcode=common.OPCODE_PING) request.connection.put_bytes(frame4) frame5 = _create_logical_frame(channel_id=4, message='Hello', fin=True, opcode=common.OPCODE_TEXT) request.connection.put_bytes(frame5) request.connection.put_bytes( _create_logical_frame(channel_id=1, message='Goodbye')) self.assertTrue(mux_handler.wait_until_done(timeout=2)) drop_channels = [ b for b in request.connection.get_written_control_blocks() if b.opcode == mux._MUX_OPCODE_DROP_CHANNEL] self.assertEqual(3, len(drop_channels)) for d in drop_channels: self.assertEqual(mux._DROP_CODE_BAD_FRAGMENTATION, d.drop_code) if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/test_handshake_hybi08.py0000750023300700116100000003063212035177527021576 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for handshake module.""" import unittest import set_sys_path # Update sys.path to locate mod_pywebsocket module. from mod_pywebsocket import common from mod_pywebsocket.handshake._base import AbortedByUserException from mod_pywebsocket.handshake._base import HandshakeException from mod_pywebsocket.handshake._base import VersionException from mod_pywebsocket.handshake.hybi import Handshaker from test_handshake_hybi import _create_request from test_handshake_hybi import _create_handshaker from test_handshake_hybi import AbortedByUserDispatcher from test_handshake_hybi import AbortingDispatcher from test_handshake_hybi import HandshakeAbortedException from test_handshake_hybi import RequestDefinition from test_handshake_hybi import SubprotocolChoosingDispatcher import mock def _create_good_request_def(): return RequestDefinition( 'GET', '/demo', {'Host': 'server.example.com', 'Upgrade': 'websocket', 'Connection': 'Upgrade', 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 'Sec-WebSocket-Origin': 'http://example.com', 'Sec-WebSocket-Version': '8'}) _EXPECTED_RESPONSE = ( 'HTTP/1.1 101 Switching Protocols\r\n' 'Upgrade: websocket\r\n' 'Connection: Upgrade\r\n' 'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n') class HandshakerTest(unittest.TestCase): """A unittest for draft-ietf-hybi-thewebsocketprotocol-06 and later handshake processor. """ def test_do_handshake(self): request = _create_request(_create_good_request_def()) dispatcher = mock.MockDispatcher() handshaker = Handshaker(request, dispatcher) handshaker.do_handshake() self.assertTrue(dispatcher.do_extra_handshake_called) self.assertEqual( _EXPECTED_RESPONSE, request.connection.written_data()) self.assertEqual('/demo', request.ws_resource) self.assertEqual('http://example.com', request.ws_origin) self.assertEqual(None, request.ws_protocol) self.assertEqual(None, request.ws_extensions) self.assertEqual(common.VERSION_HYBI08, request.ws_version) def test_do_handshake_with_capitalized_value(self): request_def = _create_good_request_def() request_def.headers['upgrade'] = 'WEBSOCKET' request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual( _EXPECTED_RESPONSE, request.connection.written_data()) request_def = _create_good_request_def() request_def.headers['Connection'] = 'UPGRADE' request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual( _EXPECTED_RESPONSE, request.connection.written_data()) def test_do_handshake_with_multiple_connection_values(self): request_def = _create_good_request_def() request_def.headers['Connection'] = 'Upgrade, keep-alive, , ' request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual( _EXPECTED_RESPONSE, request.connection.written_data()) def test_aborting_handshake(self): handshaker = Handshaker( _create_request(_create_good_request_def()), AbortingDispatcher()) # do_extra_handshake raises an exception. Check that it's not caught by # do_handshake. self.assertRaises(HandshakeAbortedException, handshaker.do_handshake) def test_do_handshake_with_protocol(self): request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Protocol'] = 'chat, superchat' request = _create_request(request_def) handshaker = Handshaker(request, SubprotocolChoosingDispatcher(0)) handshaker.do_handshake() EXPECTED_RESPONSE = ( 'HTTP/1.1 101 Switching Protocols\r\n' 'Upgrade: websocket\r\n' 'Connection: Upgrade\r\n' 'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n' 'Sec-WebSocket-Protocol: chat\r\n\r\n') self.assertEqual(EXPECTED_RESPONSE, request.connection.written_data()) self.assertEqual('chat', request.ws_protocol) def test_do_handshake_protocol_not_in_request_but_in_response(self): request_def = _create_good_request_def() request = _create_request(request_def) handshaker = Handshaker( request, SubprotocolChoosingDispatcher(-1, 'foobar')) # No request has been made but ws_protocol is set. HandshakeException # must be raised. self.assertRaises(HandshakeException, handshaker.do_handshake) def test_do_handshake_with_protocol_no_protocol_selection(self): request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Protocol'] = 'chat, superchat' request = _create_request(request_def) handshaker = _create_handshaker(request) # ws_protocol is not set. HandshakeException must be raised. self.assertRaises(HandshakeException, handshaker.do_handshake) def test_do_handshake_with_extensions(self): request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Extensions'] = ( 'deflate-stream, unknown') EXPECTED_RESPONSE = ( 'HTTP/1.1 101 Switching Protocols\r\n' 'Upgrade: websocket\r\n' 'Connection: Upgrade\r\n' 'Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n' 'Sec-WebSocket-Extensions: deflate-stream\r\n\r\n') request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual(EXPECTED_RESPONSE, request.connection.written_data()) self.assertEqual(1, len(request.ws_extensions)) extension = request.ws_extensions[0] self.assertEqual('deflate-stream', extension.name()) self.assertEqual(0, len(extension.get_parameter_names())) def test_do_handshake_with_quoted_extensions(self): request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Extensions'] = ( 'deflate-stream, , ' 'unknown; e = "mc^2"; ma="\r\n \\\rf "; pv=nrt') request = _create_request(request_def) handshaker = _create_handshaker(request) self.assertRaises(HandshakeException, handshaker.do_handshake) def test_do_handshake_with_optional_headers(self): request_def = _create_good_request_def() request_def.headers['EmptyValue'] = '' request_def.headers['AKey'] = 'AValue' request = _create_request(request_def) handshaker = _create_handshaker(request) handshaker.do_handshake() self.assertEqual( 'AValue', request.headers_in['AKey']) self.assertEqual( '', request.headers_in['EmptyValue']) def test_abort_extra_handshake(self): handshaker = Handshaker( _create_request(_create_good_request_def()), AbortedByUserDispatcher()) # do_extra_handshake raises an AbortedByUserException. Check that it's # not caught by do_handshake. self.assertRaises(AbortedByUserException, handshaker.do_handshake) def test_bad_requests(self): bad_cases = [ ('HTTP request', RequestDefinition( 'GET', '/demo', {'Host': 'www.google.com', 'User-Agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5;' ' en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3' ' GTB6 GTBA', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,' '*/*;q=0.8', 'Accept-Language': 'en-us,en;q=0.5', 'Accept-Encoding': 'gzip,deflate', 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', 'Keep-Alive': '300', 'Connection': 'keep-alive'}), None, True)] request_def = _create_good_request_def() request_def.method = 'POST' bad_cases.append(('Wrong method', request_def, None, True)) request_def = _create_good_request_def() del request_def.headers['Host'] bad_cases.append(('Missing Host', request_def, None, True)) request_def = _create_good_request_def() del request_def.headers['Upgrade'] bad_cases.append(('Missing Upgrade', request_def, None, True)) request_def = _create_good_request_def() request_def.headers['Upgrade'] = 'nonwebsocket' bad_cases.append(('Wrong Upgrade', request_def, None, True)) request_def = _create_good_request_def() del request_def.headers['Connection'] bad_cases.append(('Missing Connection', request_def, None, True)) request_def = _create_good_request_def() request_def.headers['Connection'] = 'Downgrade' bad_cases.append(('Wrong Connection', request_def, None, True)) request_def = _create_good_request_def() del request_def.headers['Sec-WebSocket-Key'] bad_cases.append(('Missing Sec-WebSocket-Key', request_def, 400, True)) request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Key'] = ( 'dGhlIHNhbXBsZSBub25jZQ==garbage') bad_cases.append(('Wrong Sec-WebSocket-Key (with garbage on the tail)', request_def, 400, True)) request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Key'] = 'YQ==' # BASE64 of 'a' bad_cases.append( ('Wrong Sec-WebSocket-Key (decoded value is not 16 octets long)', request_def, 400, True)) request_def = _create_good_request_def() del request_def.headers['Sec-WebSocket-Version'] bad_cases.append(('Missing Sec-WebSocket-Version', request_def, None, True)) request_def = _create_good_request_def() request_def.headers['Sec-WebSocket-Version'] = '3' bad_cases.append(('Wrong Sec-WebSocket-Version', request_def, None, False)) for (case_name, request_def, expected_status, expect_handshake_exception) in bad_cases: request = _create_request(request_def) handshaker = Handshaker(request, mock.MockDispatcher()) try: handshaker.do_handshake() self.fail('No exception thrown for \'%s\' case' % case_name) except HandshakeException, e: self.assertTrue(expect_handshake_exception) self.assertEqual(expected_status, e.status) except VersionException, e: self.assertFalse(expect_handshake_exception) if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/mux_client_for_testing.py0000640023300700116100000006234012044167306022171 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2012, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """WebSocket client utility for testing mux extension. This code should be independent from mod_pywebsocket. See the comment of client_for_testing.py. NOTE: This code is far from robust like client_for_testing.py. """ import Queue import base64 import collections import email import email.parser import logging import math import os import random import socket import struct import threading from mod_pywebsocket import util from test import client_for_testing _CONTROL_CHANNEL_ID = 0 _DEFAULT_CHANNEL_ID = 1 _MUX_OPCODE_ADD_CHANNEL_REQUEST = 0 _MUX_OPCODE_ADD_CHANNEL_RESPONSE = 1 _MUX_OPCODE_FLOW_CONTROL = 2 _MUX_OPCODE_DROP_CHANNEL = 3 _MUX_OPCODE_NEW_CHANNEL_SLOT = 4 class _ControlBlock: def __init__(self, opcode): self.opcode = opcode def _parse_handshake_response(response): status_line, header_lines = response.split('\r\n', 1) words = status_line.split(' ') if len(words) < 3: raise ValueError('Bad Status-Line syntax %r' % status_line) [version, response_code] = words[:2] if version != 'HTTP/1.1': raise ValueError('Bad response version %r' % version) if response_code != '101': raise ValueError('Bad response code %r ' % response_code) headers = email.parser.Parser().parsestr(header_lines) return headers def _parse_channel_id(data, offset=0): length = len(data) remaining = length - offset if remaining <= 0: raise Exception('No channel id found') channel_id = ord(data[offset]) channel_id_length = 1 if channel_id & 0xe0 == 0xe0: if remaining < 4: raise Exception('Invalid channel id format') channel_id = struct.unpack('!L', data[offset:offset+4])[0] & 0x1fffffff channel_id_length = 4 elif channel_id & 0xc0 == 0xc0: if remaining < 3: raise Exception('Invalid channel id format') channel_id = (((channel_id & 0x1f) << 16) + struct.unpack('!H', data[offset+1:offset+3])[0]) channel_id_length = 3 elif channel_id & 0x80 == 0x80: if remaining < 2: raise Exception('Invalid channel id format') channel_id = struct.unpack('!H', data[offset:offset+2])[0] & 0x3fff channel_id_length = 2 return channel_id, channel_id_length def _parse_number(data, offset=0): first_byte = ord(data[offset]) if (first_byte & 0x80) != 0: raise Exception('The MSB of number field must be unset') first_byte = first_byte & 0x7f if first_byte == 127: if offset + 9 > len(data): raise Exception('Invalid number') return struct.unpack('!Q', data[offset+1:offset+9])[0], 9 if first_byte == 126: if offset + 3 > len(data): raise Exception('Invalid number') return struct.unpack('!H', data[offset+1:offset+3])[0], 3 return first_byte, 1 def _parse_size_and_contents(data, offset=0): size, advance = _parse_number(data, offset) start_position = offset + advance end_position = start_position + size if len(data) < end_position: raise Exception('Invalid size of control block (%d < %d)' % ( len(data), end_position)) return data[start_position:end_position], size + advance def _parse_control_blocks(data): blocks = [] length = len(data) pos = 0 while pos < length: first_byte = ord(data[pos]) pos += 1 opcode = (first_byte >> 5) & 0x7 block = _ControlBlock(opcode) # TODO(bashi): Support more opcode if opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE: block.encode = first_byte & 3 block.rejected = (first_byte >> 4) & 1 channel_id, advance = _parse_channel_id(data, pos) block.channel_id = channel_id pos += advance encoded_handshake, advance = _parse_size_and_contents(data, pos) block.encoded_handshake = encoded_handshake pos += advance blocks.append(block) elif opcode == _MUX_OPCODE_DROP_CHANNEL: block.mux_error = (first_byte >> 4) & 1 channel_id, advance = _parse_channel_id(data, pos) block.channel_id = channel_id pos += advance reason, advance = _parse_size_and_contents(data, pos) if len(reason) == 0: block.drop_code = None block.drop_message = '' elif len(reason) >= 2: block.drop_code = struct.unpack('!H', reason[:2])[0] block.drop_message = reason[2:] else: raise Exception('Invalid DropChannel') pos += advance blocks.append(block) elif opcode == _MUX_OPCODE_FLOW_CONTROL: channel_id, advance = _parse_channel_id(data, pos) block.channel_id = channel_id pos += advance send_quota, advance = _parse_number(data, pos) block.send_quota = send_quota pos += advance blocks.append(block) elif opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT: fallback = first_byte & 1 slots, advance = _parse_number(data, pos) pos += advance send_quota, advance = _parse_number(data, pos) pos += advance if fallback == 1 and (slots != 0 or send_quota != 0): raise Exception('slots and send_quota must be zero if F bit ' 'is set') block.fallback = fallback block.slots = slots block.send_quota = send_quota blocks.append(block) else: raise Exception( 'Unsupported mux opcode %d received' % opcode) return blocks def _encode_channel_id(channel_id): if channel_id < 0: raise ValueError('Channel id %d must not be negative' % channel_id) if channel_id < 2 ** 7: return chr(channel_id) if channel_id < 2 ** 14: return struct.pack('!H', 0x8000 + channel_id) if channel_id < 2 ** 21: first = chr(0xc0 + (channel_id >> 16)) return first + struct.pack('!H', channel_id & 0xffff) if channel_id < 2 ** 29: return struct.pack('!L', 0xe0000000 + channel_id) raise ValueError('Channel id %d is too large' % channel_id) def _encode_number(number): if number <= 125: return chr(number) elif number < (1 << 16): return chr(0x7e) + struct.pack('!H', number) elif number < (1 << 63): return chr(0x7f) + struct.pack('!Q', number) else: raise Exception('Invalid number') def _create_add_channel_request(channel_id, encoded_handshake, encoding=0): length = len(encoded_handshake) handshake_length = _encode_number(length) first_byte = (_MUX_OPCODE_ADD_CHANNEL_REQUEST << 5) | encoding return (chr(first_byte) + _encode_channel_id(channel_id) + handshake_length + encoded_handshake) def _create_flow_control(channel_id, replenished_quota): first_byte = (_MUX_OPCODE_FLOW_CONTROL << 5) return (chr(first_byte) + _encode_channel_id(channel_id) + _encode_number(replenished_quota)) class _MuxReaderThread(threading.Thread): """Mux reader thread. Reads frames and passes them to the mux client. This thread accesses private functions/variables of the mux client. """ def __init__(self, mux): threading.Thread.__init__(self) self.setDaemon(True) self._mux = mux self._stop_requested = False def _receive_message(self): first_opcode = None pending_payload = [] while not self._stop_requested: fin, rsv1, rsv2, rsv3, opcode, payload_length = ( client_for_testing.read_frame_header(self._mux._socket)) if not first_opcode: if opcode == client_for_testing.OPCODE_TEXT: raise Exception('Received a text message on physical ' 'connection') if opcode == client_for_testing.OPCODE_CONTINUATION: raise Exception('Received an intermediate frame but ' 'fragmentation was not started') if (opcode == client_for_testing.OPCODE_BINARY or opcode == client_for_testing.OPCODE_PONG or opcode == client_for_testing.OPCODE_PONG or opcode == client_for_testing.OPCODE_CLOSE): first_opcode = opcode else: raise Exception('Received an undefined opcode frame: %d' % opcode) elif opcode != client_for_testing.OPCODE_CONTINUATION: raise Exception('Received a new opcode before ' 'terminating fragmentation') payload = client_for_testing.receive_bytes( self._mux._socket, payload_length) if self._mux._incoming_frame_filter is not None: payload = self._mux._incoming_frame_filter.filter(payload) pending_payload.append(payload) if fin: break if self._stop_requested: return None, None message = ''.join(pending_payload) return first_opcode, message def request_stop(self): self._stop_requested = True def run(self): try: while not self._stop_requested: # opcode is OPCODE_BINARY or control opcodes when a message # is succesfully received. opcode, message = self._receive_message() if not opcode: return if opcode == client_for_testing.OPCODE_BINARY: channel_id, advance = _parse_channel_id(message) self._mux._dispatch_frame(channel_id, message[advance:]) else: self._mux._process_control_message(opcode, message) finally: self._mux._notify_reader_done() class _InnerFrame(object): def __init__(self, fin, rsv1, rsv2, rsv3, opcode, payload): self.fin = fin self.rsv1 = rsv1 self.rsv2 = rsv2 self.rsv3 = rsv3 self.opcode = opcode self.payload = payload class _LogicalChannelData(object): def __init__(self): self.queue = Queue.Queue() self.send_quota = 0 self.receive_quota = 0 class MuxClient(object): """WebSocket mux client. Note that this class is NOT thread-safe. Do not access an instance of this class from multiple threads at a same time. """ def __init__(self, options): self._logger = util.get_class_logger(self) self._options = options self._options.enable_mux() self._stream = None self._socket = None self._handshake = client_for_testing.WebSocketHandshake(self._options) self._incoming_frame_filter = None self._outgoing_frame_filter = None self._is_active = False self._read_thread = None self._control_blocks_condition = threading.Condition() self._control_blocks = [] self._channel_slots = collections.deque() self._logical_channels_condition = threading.Condition(); self._logical_channels = {} self._timeout = 2 self._physical_connection_close_event = None self._physical_connection_close_message = None def _parse_inner_frame(self, data): if len(data) == 0: raise Exception('Invalid encapsulated frame received') first_byte = ord(data[0]) fin = (first_byte << 7) & 1 rsv1 = (first_byte << 6) & 1 rsv2 = (first_byte << 5) & 1 rsv3 = (first_byte << 4) & 1 opcode = first_byte & 0xf if self._outgoing_frame_filter: payload = self._outgoing_frame_filter.filter( data[1:]) else: payload = data[1:] return _InnerFrame(fin, rsv1, rsv2, rsv3, opcode, payload) def _process_mux_control_blocks(self): for block in self._control_blocks: if block.opcode == _MUX_OPCODE_ADD_CHANNEL_RESPONSE: # AddChannelResponse will be handled in add_channel(). continue elif block.opcode == _MUX_OPCODE_FLOW_CONTROL: try: self._logical_channels_condition.acquire() if not block.channel_id in self._logical_channels: raise Exception('Invalid flow control received for ' 'channel id %d' % block.channel_id) self._logical_channels[block.channel_id].send_quota += ( block.send_quota) self._logical_channels_condition.notify() finally: self._logical_channels_condition.release() elif block.opcode == _MUX_OPCODE_NEW_CHANNEL_SLOT: self._channel_slots.extend([block.send_quota] * block.slots) def _dispatch_frame(self, channel_id, payload): if channel_id == _CONTROL_CHANNEL_ID: try: self._control_blocks_condition.acquire() self._control_blocks += _parse_control_blocks(payload) self._process_mux_control_blocks() self._control_blocks_condition.notify() finally: self._control_blocks_condition.release() else: try: self._logical_channels_condition.acquire() if not channel_id in self._logical_channels: raise Exception('Received logical frame on channel id ' '%d, which is not established' % channel_id) inner_frame = self._parse_inner_frame(payload) self._logical_channels[channel_id].receive_quota -= ( len(inner_frame.payload)) if self._logical_channels[channel_id].receive_quota < 0: raise Exception('The server violates quota on ' 'channel id %d' % channel_id) finally: self._logical_channels_condition.release() self._logical_channels[channel_id].queue.put(inner_frame) def _process_control_message(self, opcode, message): # Ping/Pong are not supported. if opcode == client_for_testing.OPCODE_CLOSE: self._physical_connection_close_message = message if self._is_active: self._stream.send_close( code=client_for_testing.STATUS_NORMAL_CLOSURE, reason='') self._read_thread.request_stop() if self._physical_connection_close_event: self._physical_connection_close_event.set() def _notify_reader_done(self): self._logger.debug('Read thread terminated.') self.close_socket() def _assert_channel_slot_available(self): try: self._control_blocks_condition.acquire() if len(self._channel_slots) == 0: # Wait once self._control_blocks_condition.wait(timeout=self._timeout) finally: self._control_blocks_condition.release() if len(self._channel_slots) == 0: raise Exception('Failed to receive NewChannelSlot') def _assert_send_quota_available(self, channel_id): try: self._logical_channels_condition.acquire() if self._logical_channels[channel_id].send_quota == 0: # Wait once self._logical_channels_condition.wait(timeout=self._timeout) finally: self._logical_channels_condition.release() if self._logical_channels[channel_id].send_quota == 0: raise Exception('Failed to receive FlowControl for channel id %d' % channel_id) def connect(self): self._socket = socket.socket() self._socket.settimeout(self._options.socket_timeout) self._socket.connect((self._options.server_host, self._options.server_port)) if self._options.use_tls: self._socket = _TLSSocket(self._socket) self._handshake.handshake(self._socket) self._stream = client_for_testing.WebSocketStream( self._socket, self._handshake) self._logical_channels[_DEFAULT_CHANNEL_ID] = _LogicalChannelData() self._read_thread = _MuxReaderThread(self) self._read_thread.start() self._assert_channel_slot_available() self._assert_send_quota_available(_DEFAULT_CHANNEL_ID) self._is_active = True self._logger.info('Connection established') def add_channel(self, channel_id, options): if not self._is_active: raise Exception('Mux client is not active') if channel_id in self._logical_channels: raise Exception('Channel id %d already exists' % channel_id) try: send_quota = self._channel_slots.popleft() except IndexError, e: raise Exception('No channel slots: %r' % e) # Create AddChannel request request_line = 'GET %s HTTP/1.1\r\n' % options.resource fields = [] if options.server_port == client_for_testing.DEFAULT_PORT: fields.append('Host: %s\r\n' % options.server_host.lower()) else: fields.append('Host: %s:%d\r\n' % (options.server_host.lower(), options.server_port)) fields.append('Origin: %s\r\n' % options.origin.lower()) fields.append('Connection: Upgrade\r\n') if len(options.extensions) > 0: fields.append('Sec-WebSocket-Extensions: %s\r\n' % ', '.join(options.extensions)) handshake = request_line + ''.join(fields) + '\r\n' add_channel_request = _create_add_channel_request( channel_id, handshake) payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + add_channel_request self._stream.send_binary(payload) # Wait AddChannelResponse self._logger.debug('Waiting AddChannelResponse for the request...') response = None try: self._control_blocks_condition.acquire() while True: for block in self._control_blocks: if block.opcode != _MUX_OPCODE_ADD_CHANNEL_RESPONSE: continue if block.channel_id == channel_id: response = block self._control_blocks.remove(response) break if response: break self._control_blocks_condition.wait(self._timeout) if not self._is_active: raise Exception('AddChannelRequest timed out') finally: self._control_blocks_condition.release() # Validate AddChannelResponse if response.rejected: raise Exception('The server rejected AddChannelRequest') fields = _parse_handshake_response(response.encoded_handshake) # Should we reject when Upgrade, Connection, or Sec-WebSocket-Accept # headers exist? self._logical_channels_condition.acquire() self._logical_channels[channel_id] = _LogicalChannelData() self._logical_channels[channel_id].send_quota = send_quota self._logical_channels_condition.release() self._logger.debug('Logical channel %d established' % channel_id) def _check_logical_channel_is_opened(self, channel_id): if not self._is_active: raise Exception('Mux client is not active') if not channel_id in self._logical_channels: raise Exception('Logical channel %d is not established.') def drop_channel(self, channel_id): # TODO(bashi): Implement pass def send_flow_control(self, channel_id, replenished_quota): self._check_logical_channel_is_opened(channel_id) flow_control = _create_flow_control(channel_id, replenished_quota) payload = _encode_channel_id(_CONTROL_CHANNEL_ID) + flow_control # Replenish receive quota try: self._logical_channels_condition.acquire() self._logical_channels[channel_id].receive_quota += ( replenished_quota) finally: self._logical_channels_condition.release() self._stream.send_binary(payload) def send_message(self, channel_id, message, end=True, binary=False): self._check_logical_channel_is_opened(channel_id) if binary: first_byte = (end << 7) | client_for_testing.OPCODE_BINARY else: first_byte = (end << 7) | client_for_testing.OPCODE_TEXT message = message.encode('utf-8') try: self._logical_channels_condition.acquire() if self._logical_channels[channel_id].send_quota < len(message): raise Exception('Send quota violation: %d < %d' % ( self._logical_channels[channel_id].send_quota, len(message))) self._logical_channels[channel_id].send_quota -= len(message) finally: self._logical_channels_condition.release() payload = _encode_channel_id(channel_id) + chr(first_byte) + message self._stream.send_binary(payload) def assert_receive(self, channel_id, payload, binary=False): self._check_logical_channel_is_opened(channel_id) try: inner_frame = self._logical_channels[channel_id].queue.get( timeout=self._timeout) except Queue.Empty, e: raise Exception('Cannot receive message from channel id %d' % channel_id) if binary: opcode = client_for_testing.OPCODE_BINARY else: opcode = client_for_testing.OPCODE_TEXT if inner_frame.opcode != opcode: raise Exception('Unexpected opcode received (%r != %r)' % (expected_opcode, inner_frame.opcode)) if inner_frame.payload != payload: raise Exception('Unexpected payload received') def send_close(self, channel_id, code=None, reason=''): self._check_logical_channel_is_opened(channel_id) if code is not None: body = struct.pack('!H', code) + reason.encode('utf-8') else: body = '' first_byte = (1 << 7) | client_for_testing.OPCODE_CLOSE payload = _encode_channel_id(channel_id) + chr(first_byte) + body self._stream.send_binary(payload) def assert_receive_close(self, channel_id): self._check_logical_channel_is_opened(channel_id) try: inner_frame = self._logical_channels[channel_id].queue.get( timeout=self._timeout) except Queue.Empty, e: raise Exception('Cannot receive message from channel id %d' % channel_id) if inner_frame.opcode != client_for_testing.OPCODE_CLOSE: raise Exception('Didn\'t receive close frame') def send_physical_connection_close(self, code=None, reason=''): self._physical_connection_close_event = threading.Event() self._stream.send_close(code, reason) # This method can be used only after calling # send_physical_connection_close(). def assert_physical_connection_receive_close( self, code=client_for_testing.STATUS_NORMAL_CLOSURE, reason=''): self._physical_connection_close_event.wait(timeout=self._timeout) if (not self._physical_connection_close_event.isSet() or not self._physical_connection_close_message): raise Exception('Didn\'t receive closing handshake') def close_socket(self): self._is_active = False self._socket.close() pywebsocket-0.7.9/src/test/test_util.py0000750023300700116100000001247412115602721017432 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for util module.""" import os import sys import unittest import set_sys_path # Update sys.path to locate mod_pywebsocket module. from mod_pywebsocket import util _TEST_DATA_DIR = os.path.join(os.path.split(__file__)[0], 'testdata') class UtilTest(unittest.TestCase): """A unittest for util module.""" def test_get_stack_trace(self): self.assertEqual('None\n', util.get_stack_trace()) try: a = 1 / 0 # Intentionally raise exception. except Exception: trace = util.get_stack_trace() self.failUnless(trace.startswith('Traceback')) self.failUnless(trace.find('ZeroDivisionError') != -1) def test_prepend_message_to_exception(self): exc = Exception('World') self.assertEqual('World', str(exc)) util.prepend_message_to_exception('Hello ', exc) self.assertEqual('Hello World', str(exc)) def test_get_script_interp(self): cygwin_path = 'c:\\cygwin\\bin' cygwin_perl = os.path.join(cygwin_path, 'perl') self.assertEqual(None, util.get_script_interp( os.path.join(_TEST_DATA_DIR, 'README'))) self.assertEqual(None, util.get_script_interp( os.path.join(_TEST_DATA_DIR, 'README'), cygwin_path)) self.assertEqual('/usr/bin/perl -wT', util.get_script_interp( os.path.join(_TEST_DATA_DIR, 'hello.pl'))) self.assertEqual(cygwin_perl + ' -wT', util.get_script_interp( os.path.join(_TEST_DATA_DIR, 'hello.pl'), cygwin_path)) def test_hexify(self): self.assertEqual('61 7a 41 5a 30 39 20 09 0d 0a 00 ff', util.hexify('azAZ09 \t\r\n\x00\xff')) class RepeatedXorMaskerTest(unittest.TestCase): """A unittest for RepeatedXorMasker class.""" def test_mask(self): # Sample input e6,97,a5 is U+65e5 in UTF-8 masker = util.RepeatedXorMasker('\xff\xff\xff\xff') result = masker.mask('\xe6\x97\xa5') self.assertEqual('\x19\x68\x5a', result) masker = util.RepeatedXorMasker('\x00\x00\x00\x00') result = masker.mask('\xe6\x97\xa5') self.assertEqual('\xe6\x97\xa5', result) masker = util.RepeatedXorMasker('\xe6\x97\xa5\x20') result = masker.mask('\xe6\x97\xa5') self.assertEqual('\x00\x00\x00', result) def test_mask_twice(self): masker = util.RepeatedXorMasker('\x00\x7f\xff\x20') # mask[0], mask[1], ... will be used. result = masker.mask('\x00\x00\x00\x00\x00') self.assertEqual('\x00\x7f\xff\x20\x00', result) # mask[2], mask[0], ... will be used for the next call. result = masker.mask('\x00\x00\x00\x00\x00') self.assertEqual('\x7f\xff\x20\x00\x7f', result) def test_mask_large_data(self): masker = util.RepeatedXorMasker('mASk') original = ''.join([chr(i % 256) for i in xrange(1000)]) result = masker.mask(original) expected = ''.join( [chr((i % 256) ^ ord('mASk'[i % 4])) for i in xrange(1000)]) self.assertEqual(expected, result) masker = util.RepeatedXorMasker('MaSk') first_part = 'The WebSocket Protocol enables two-way communication.' result = masker.mask(first_part) self.assertEqual( '\x19\t6K\x1a\x0418"\x028\x0e9A\x03\x19"\x15<\x08"\rs\x0e#' '\x001\x07(\x12s\x1f:\x0e~\x1c,\x18s\x08"\x0c>\x1e#\x080\n9' '\x08<\x05c', result) second_part = 'It has two parts: a handshake and the data transfer.' result = masker.mask(second_part) self.assertEqual( "('K%\x00 K9\x16[s\nm\t2\x05)\x12;\n&\x04s\n#" "\x05s\x1f%\x04s\x0f,\x152K9\x132\x05>\x076\x19c", result) if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/test_memorizingfile.py0000750023300700116100000001023411644773271021504 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for memorizingfile module.""" import StringIO import unittest import set_sys_path # Update sys.path to locate mod_pywebsocket module. from mod_pywebsocket import memorizingfile class UtilTest(unittest.TestCase): """A unittest for memorizingfile module.""" def check(self, memorizing_file, num_read, expected_list): for unused in range(num_read): memorizing_file.readline() actual_list = memorizing_file.get_memorized_lines() self.assertEqual(len(expected_list), len(actual_list)) for expected, actual in zip(expected_list, actual_list): self.assertEqual(expected, actual) def check_with_size(self, memorizing_file, read_size, expected_list): read_list = [] read_line = '' while True: line = memorizing_file.readline(read_size) line_length = len(line) self.assertTrue(line_length <= read_size) if line_length == 0: if read_line != '': read_list.append(read_line) break read_line += line if line[line_length - 1] == '\n': read_list.append(read_line) read_line = '' actual_list = memorizing_file.get_memorized_lines() self.assertEqual(len(expected_list), len(actual_list)) self.assertEqual(len(expected_list), len(read_list)) for expected, actual, read in zip(expected_list, actual_list, read_list): self.assertEqual(expected, actual) self.assertEqual(expected, read) def test_get_memorized_lines(self): memorizing_file = memorizingfile.MemorizingFile(StringIO.StringIO( 'Hello\nWorld\nWelcome')) self.check(memorizing_file, 3, ['Hello\n', 'World\n', 'Welcome']) def test_get_memorized_lines_limit_memorized_lines(self): memorizing_file = memorizingfile.MemorizingFile(StringIO.StringIO( 'Hello\nWorld\nWelcome'), 2) self.check(memorizing_file, 3, ['Hello\n', 'World\n']) def test_get_memorized_lines_empty_file(self): memorizing_file = memorizingfile.MemorizingFile(StringIO.StringIO( '')) self.check(memorizing_file, 10, []) def test_get_memorized_lines_with_size(self): for size in range(1, 10): memorizing_file = memorizingfile.MemorizingFile(StringIO.StringIO( 'Hello\nWorld\nWelcome')) self.check_with_size(memorizing_file, size, ['Hello\n', 'World\n', 'Welcome']) if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/client_for_testing.py0000640023300700116100000011561412103670745021305 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2012, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """WebSocket client utility for testing. This module contains helper methods for performing handshake, frame sending/receiving as a WebSocket client. This is code for testing mod_pywebsocket. Keep this code independent from mod_pywebsocket. Don't import e.g. Stream class for generating frame for testing. Using util.hexify, etc. that are not related to protocol processing is allowed. Note: This code is far from robust, e.g., we cut corners in handshake. """ import base64 import errno import logging import os import random import re import socket import struct from mod_pywebsocket import util DEFAULT_PORT = 80 DEFAULT_SECURE_PORT = 443 # Opcodes introduced in IETF HyBi 01 for the new framing format OPCODE_CONTINUATION = 0x0 OPCODE_CLOSE = 0x8 OPCODE_PING = 0x9 OPCODE_PONG = 0xa OPCODE_TEXT = 0x1 OPCODE_BINARY = 0x2 # Strings used for handshake _UPGRADE_HEADER = 'Upgrade: websocket\r\n' _UPGRADE_HEADER_HIXIE75 = 'Upgrade: WebSocket\r\n' _CONNECTION_HEADER = 'Connection: Upgrade\r\n' WEBSOCKET_ACCEPT_UUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' # Status codes STATUS_NORMAL_CLOSURE = 1000 STATUS_GOING_AWAY = 1001 STATUS_PROTOCOL_ERROR = 1002 STATUS_UNSUPPORTED_DATA = 1003 STATUS_NO_STATUS_RECEIVED = 1005 STATUS_ABNORMAL_CLOSURE = 1006 STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007 STATUS_POLICY_VIOLATION = 1008 STATUS_MESSAGE_TOO_BIG = 1009 STATUS_MANDATORY_EXT = 1010 STATUS_INTERNAL_ENDPOINT_ERROR = 1011 STATUS_TLS_HANDSHAKE = 1015 # Extension tokens _DEFLATE_STREAM_EXTENSION = 'deflate-stream' _DEFLATE_FRAME_EXTENSION = 'deflate-frame' # TODO(bashi): Update after mux implementation finished. _MUX_EXTENSION = 'mux_DO_NOT_USE' def _method_line(resource): return 'GET %s HTTP/1.1\r\n' % resource def _sec_origin_header(origin): return 'Sec-WebSocket-Origin: %s\r\n' % origin.lower() def _origin_header(origin): # 4.1 13. concatenation of the string "Origin:", a U+0020 SPACE character, # and the /origin/ value, converted to ASCII lowercase, to /fields/. return 'Origin: %s\r\n' % origin.lower() def _format_host_header(host, port, secure): # 4.1 9. Let /hostport/ be an empty string. # 4.1 10. Append the /host/ value, converted to ASCII lowercase, to # /hostport/ hostport = host.lower() # 4.1 11. If /secure/ is false, and /port/ is not 80, or if /secure/ # is true, and /port/ is not 443, then append a U+003A COLON character # (:) followed by the value of /port/, expressed as a base-ten integer, # to /hostport/ if ((not secure and port != DEFAULT_PORT) or (secure and port != DEFAULT_SECURE_PORT)): hostport += ':' + str(port) # 4.1 12. concatenation of the string "Host:", a U+0020 SPACE # character, and /hostport/, to /fields/. return 'Host: %s\r\n' % hostport # TODO(tyoshino): Define a base class and move these shared methods to that. def receive_bytes(socket, length): received_bytes = [] remaining = length while remaining > 0: new_received_bytes = socket.recv(remaining) if not new_received_bytes: raise Exception( 'Connection closed before receiving requested length ' '(requested %d bytes but received only %d bytes)' % (length, length - remaining)) received_bytes.append(new_received_bytes) remaining -= len(new_received_bytes) return ''.join(received_bytes) # TODO(tyoshino): Now the WebSocketHandshake class diverts these methods. We # should move to HTTP parser as specified in RFC 6455. For HyBi 00 and # Hixie 75, pack these methods as some parser class. def _read_fields(socket): # 4.1 32. let /fields/ be a list of name-value pairs, initially empty. fields = {} while True: # 4.1 33. let /name/ and /value/ be empty byte arrays name = '' value = '' # 4.1 34. read /name/ name = _read_name(socket) if name is None: break # 4.1 35. read spaces # TODO(tyoshino): Skip only one space as described in the spec. ch = _skip_spaces(socket) # 4.1 36. read /value/ value = _read_value(socket, ch) # 4.1 37. read a byte from the server ch = receive_bytes(socket, 1) if ch != '\n': # 0x0A raise Exception( 'Expected LF but found %r while reading value %r for header ' '%r' % (ch, name, value)) # 4.1 38. append an entry to the /fields/ list that has the name # given by the string obtained by interpreting the /name/ byte # array as a UTF-8 stream and the value given by the string # obtained by interpreting the /value/ byte array as a UTF-8 byte # stream. fields.setdefault(name, []).append(value) # 4.1 39. return to the "Field" step above return fields def _read_name(socket): # 4.1 33. let /name/ be empty byte arrays name = '' while True: # 4.1 34. read a byte from the server ch = receive_bytes(socket, 1) if ch == '\r': # 0x0D return None elif ch == '\n': # 0x0A raise Exception( 'Unexpected LF when reading header name %r' % name) elif ch == ':': # 0x3A return name elif ch >= 'A' and ch <= 'Z': # range 0x31 to 0x5A ch = chr(ord(ch) + 0x20) name += ch else: name += ch def _skip_spaces(socket): # 4.1 35. read a byte from the server while True: ch = receive_bytes(socket, 1) if ch == ' ': # 0x20 continue return ch def _read_value(socket, ch): # 4.1 33. let /value/ be empty byte arrays value = '' # 4.1 36. read a byte from server. while True: if ch == '\r': # 0x0D return value elif ch == '\n': # 0x0A raise Exception( 'Unexpected LF when reading header value %r' % value) else: value += ch ch = receive_bytes(socket, 1) def read_frame_header(socket): received = receive_bytes(socket, 2) first_byte = ord(received[0]) fin = (first_byte >> 7) & 1 rsv1 = (first_byte >> 6) & 1 rsv2 = (first_byte >> 5) & 1 rsv3 = (first_byte >> 4) & 1 opcode = first_byte & 0xf second_byte = ord(received[1]) mask = (second_byte >> 7) & 1 payload_length = second_byte & 0x7f if mask != 0: raise Exception( 'Mask bit must be 0 for frames coming from server') if payload_length == 127: extended_payload_length = receive_bytes(socket, 8) payload_length = struct.unpack( '!Q', extended_payload_length)[0] if payload_length > 0x7FFFFFFFFFFFFFFF: raise Exception('Extended payload length >= 2^63') elif payload_length == 126: extended_payload_length = receive_bytes(socket, 2) payload_length = struct.unpack( '!H', extended_payload_length)[0] return fin, rsv1, rsv2, rsv3, opcode, payload_length class _TLSSocket(object): """Wrapper for a TLS connection.""" def __init__(self, raw_socket): self._ssl = socket.ssl(raw_socket) def send(self, bytes): return self._ssl.write(bytes) def recv(self, size=-1): return self._ssl.read(size) def close(self): # Nothing to do. pass class HttpStatusException(Exception): """This exception will be raised when unexpected http status code was received as a result of handshake. """ def __init__(self, name, status): super(HttpStatusException, self).__init__(name) self.status = status class WebSocketHandshake(object): """Opening handshake processor for the WebSocket protocol (RFC 6455).""" def __init__(self, options): self._logger = util.get_class_logger(self) self._options = options def handshake(self, socket): """Handshake WebSocket. Raises: Exception: handshake failed. """ self._socket = socket request_line = _method_line(self._options.resource) self._logger.debug('Opening handshake Request-Line: %r', request_line) self._socket.sendall(request_line) fields = [] fields.append(_UPGRADE_HEADER) fields.append(_CONNECTION_HEADER) fields.append(_format_host_header( self._options.server_host, self._options.server_port, self._options.use_tls)) if self._options.version is 8: fields.append(_sec_origin_header(self._options.origin)) else: fields.append(_origin_header(self._options.origin)) original_key = os.urandom(16) key = base64.b64encode(original_key) self._logger.debug( 'Sec-WebSocket-Key: %s (%s)', key, util.hexify(original_key)) fields.append('Sec-WebSocket-Key: %s\r\n' % key) fields.append('Sec-WebSocket-Version: %d\r\n' % self._options.version) # Setting up extensions. if len(self._options.extensions) > 0: fields.append('Sec-WebSocket-Extensions: %s\r\n' % ', '.join(self._options.extensions)) self._logger.debug('Opening handshake request headers: %r', fields) for field in fields: self._socket.sendall(field) self._socket.sendall('\r\n') self._logger.info('Sent opening handshake request') field = '' while True: ch = receive_bytes(self._socket, 1) field += ch if ch == '\n': break self._logger.debug('Opening handshake Response-Line: %r', field) if len(field) < 7 or not field.endswith('\r\n'): raise Exception('Wrong status line: %r' % field) m = re.match('[^ ]* ([^ ]*) .*', field) if m is None: raise Exception( 'No HTTP status code found in status line: %r' % field) code = m.group(1) if not re.match('[0-9][0-9][0-9]', code): raise Exception( 'HTTP status code %r is not three digit in status line: %r' % (code, field)) if code != '101': raise HttpStatusException( 'Expected HTTP status code 101 but found %r in status line: ' '%r' % (code, field), int(code)) fields = _read_fields(self._socket) ch = receive_bytes(self._socket, 1) if ch != '\n': # 0x0A raise Exception('Expected LF but found: %r' % ch) self._logger.debug('Opening handshake response headers: %r', fields) # Check /fields/ if len(fields['upgrade']) != 1: raise Exception( 'Multiple Upgrade headers found: %s' % fields['upgrade']) if len(fields['connection']) != 1: raise Exception( 'Multiple Connection headers found: %s' % fields['connection']) if fields['upgrade'][0] != 'websocket': raise Exception( 'Unexpected Upgrade header value: %s' % fields['upgrade'][0]) if fields['connection'][0].lower() != 'upgrade': raise Exception( 'Unexpected Connection header value: %s' % fields['connection'][0]) if len(fields['sec-websocket-accept']) != 1: raise Exception( 'Multiple Sec-WebSocket-Accept headers found: %s' % fields['sec-websocket-accept']) accept = fields['sec-websocket-accept'][0] # Validate try: decoded_accept = base64.b64decode(accept) except TypeError, e: raise HandshakeException( 'Illegal value for header Sec-WebSocket-Accept: ' + accept) if len(decoded_accept) != 20: raise HandshakeException( 'Decoded value of Sec-WebSocket-Accept is not 20-byte long') self._logger.debug('Actual Sec-WebSocket-Accept: %r (%s)', accept, util.hexify(decoded_accept)) original_expected_accept = util.sha1_hash( key + WEBSOCKET_ACCEPT_UUID).digest() expected_accept = base64.b64encode(original_expected_accept) self._logger.debug('Expected Sec-WebSocket-Accept: %r (%s)', expected_accept, util.hexify(original_expected_accept)) if accept != expected_accept: raise Exception( 'Invalid Sec-WebSocket-Accept header: %r (expected) != %r ' '(actual)' % (accept, expected_accept)) server_extensions_header = fields.get('sec-websocket-extensions') if (server_extensions_header is None or len(server_extensions_header) != 1): accepted_extensions = [] else: accepted_extensions = server_extensions_header[0].split(',') # TODO(tyoshino): Follow the ABNF in the spec. accepted_extensions = [s.strip() for s in accepted_extensions] # Scan accepted extension list to check if there is any unrecognized # extensions or extensions we didn't request in it. Then, for # extensions we request, parse them and store parameters. They will be # used later by each extension. deflate_stream_accepted = False deflate_frame_accepted = False mux_accepted = False for extension in accepted_extensions: if extension == '': continue if extension == _DEFLATE_STREAM_EXTENSION: if self._options.use_deflate_stream: deflate_stream_accepted = True continue if extension == _DEFLATE_FRAME_EXTENSION: if self._options.use_deflate_frame: deflate_frame_accepted = True continue if extension == _MUX_EXTENSION: if self._options.use_mux: mux_accepted = True continue raise Exception( 'Received unrecognized extension: %s' % extension) # Let all extensions check the response for extension request. if self._options.use_deflate_stream and not deflate_stream_accepted: raise Exception('%s extension not accepted' % _DEFLATE_STREAM_EXTENSION) if (self._options.use_deflate_frame and not deflate_frame_accepted): raise Exception('%s extension not accepted' % _DEFLATE_FRAME_EXTENSION) if self._options.use_mux and not mux_accepted: raise Exception('%s extension not accepted' % _MUX_EXTENSION) class WebSocketHybi00Handshake(object): """Opening handshake processor for the WebSocket protocol version HyBi 00. """ def __init__(self, options, draft_field): self._logger = util.get_class_logger(self) self._options = options self._draft_field = draft_field def handshake(self, socket): """Handshake WebSocket. Raises: Exception: handshake failed. """ self._socket = socket # 4.1 5. send request line. request_line = _method_line(self._options.resource) self._logger.debug('Opening handshake Request-Line: %r', request_line) self._socket.sendall(request_line) # 4.1 6. Let /fields/ be an empty list of strings. fields = [] # 4.1 7. Add the string "Upgrade: WebSocket" to /fields/. fields.append(_UPGRADE_HEADER_HIXIE75) # 4.1 8. Add the string "Connection: Upgrade" to /fields/. fields.append(_CONNECTION_HEADER) # 4.1 9-12. Add Host: field to /fields/. fields.append(_format_host_header( self._options.server_host, self._options.server_port, self._options.use_tls)) # 4.1 13. Add Origin: field to /fields/. fields.append(_origin_header(self._options.origin)) # TODO: 4.1 14 Add Sec-WebSocket-Protocol: field to /fields/. # TODO: 4.1 15 Add cookie headers to /fields/. # 4.1 16-23. Add Sec-WebSocket-Key to /fields/. self._number1, key1 = self._generate_sec_websocket_key() self._logger.debug('Number1: %d', self._number1) fields.append('Sec-WebSocket-Key1: %s\r\n' % key1) self._number2, key2 = self._generate_sec_websocket_key() self._logger.debug('Number2: %d', self._number1) fields.append('Sec-WebSocket-Key2: %s\r\n' % key2) fields.append('Sec-WebSocket-Draft: %s\r\n' % self._draft_field) # 4.1 24. For each string in /fields/, in a random order: send the # string, encoded as UTF-8, followed by a UTF-8 encoded U+000D CARRIAGE # RETURN U+000A LINE FEED character pair (CRLF). random.shuffle(fields) self._logger.debug('Opening handshake request headers: %r', fields) for field in fields: self._socket.sendall(field) # 4.1 25. send a UTF-8-encoded U+000D CARRIAGE RETURN U+000A LINE FEED # character pair (CRLF). self._socket.sendall('\r\n') # 4.1 26. let /key3/ be a string consisting of eight random bytes (or # equivalently, a random 64 bit integer encoded in a big-endian order). self._key3 = self._generate_key3() # 4.1 27. send /key3/ to the server. self._socket.sendall(self._key3) self._logger.debug( 'Key3: %r (%s)', self._key3, util.hexify(self._key3)) self._logger.info('Sent opening handshake request') # 4.1 28. Read bytes from the server until either the connection # closes, or a 0x0A byte is read. let /field/ be these bytes, including # the 0x0A bytes. field = '' while True: ch = receive_bytes(self._socket, 1) field += ch if ch == '\n': break self._logger.debug('Opening handshake Response-Line: %r', field) # if /field/ is not at least seven bytes long, or if the last # two bytes aren't 0x0D and 0x0A respectively, or if it does not # contain at least two 0x20 bytes, then fail the WebSocket connection # and abort these steps. if len(field) < 7 or not field.endswith('\r\n'): raise Exception('Wrong status line: %r' % field) m = re.match('[^ ]* ([^ ]*) .*', field) if m is None: raise Exception('No code found in status line: %r' % field) # 4.1 29. let /code/ be the substring of /field/ that starts from the # byte after the first 0x20 byte, and ends with the byte before the # second 0x20 byte. code = m.group(1) # 4.1 30. if /code/ is not three bytes long, or if any of the bytes in # /code/ are not in the range 0x30 to 0x90, then fail the WebSocket # connection and abort these steps. if not re.match('[0-9][0-9][0-9]', code): raise Exception( 'HTTP status code %r is not three digit in status line: %r' % (code, field)) # 4.1 31. if /code/, interpreted as UTF-8, is "101", then move to the # next step. if code != '101': raise HttpStatusException( 'Expected HTTP status code 101 but found %r in status line: ' '%r' % (code, field), int(code)) # 4.1 32-39. read fields into /fields/ fields = _read_fields(self._socket) self._logger.debug('Opening handshake response headers: %r', fields) # 4.1 40. _Fields processing_ # read a byte from server ch = receive_bytes(self._socket, 1) if ch != '\n': # 0x0A raise Exception('Expected LF but found %r' % ch) # 4.1 41. check /fields/ if len(fields['upgrade']) != 1: raise Exception( 'Multiple Upgrade headers found: %s' % fields['upgrade']) if len(fields['connection']) != 1: raise Exception( 'Multiple Connection headers found: %s' % fields['connection']) if len(fields['sec-websocket-origin']) != 1: raise Exception( 'Multiple Sec-WebSocket-Origin headers found: %s' % fields['sec-sebsocket-origin']) if len(fields['sec-websocket-location']) != 1: raise Exception( 'Multiple Sec-WebSocket-Location headers found: %s' % fields['sec-sebsocket-location']) # TODO(ukai): protocol # if the entry's name is "upgrade" # if the value is not exactly equal to the string "WebSocket", # then fail the WebSocket connection and abort these steps. if fields['upgrade'][0] != 'WebSocket': raise Exception( 'Unexpected Upgrade header value: %s' % fields['upgrade'][0]) # if the entry's name is "connection" # if the value, converted to ASCII lowercase, is not exactly equal # to the string "upgrade", then fail the WebSocket connection and # abort these steps. if fields['connection'][0].lower() != 'upgrade': raise Exception( 'Unexpected Connection header value: %s' % fields['connection'][0]) # TODO(ukai): check origin, location, cookie, .. # 4.1 42. let /challenge/ be the concatenation of /number_1/, # expressed as a big endian 32 bit integer, /number_2/, expressed # as big endian 32 bit integer, and the eight bytes of /key_3/ in the # order they were sent on the wire. challenge = struct.pack('!I', self._number1) challenge += struct.pack('!I', self._number2) challenge += self._key3 self._logger.debug( 'Challenge: %r (%s)', challenge, util.hexify(challenge)) # 4.1 43. let /expected/ be the MD5 fingerprint of /challenge/ as a # big-endian 128 bit string. expected = util.md5_hash(challenge).digest() self._logger.debug( 'Expected challenge response: %r (%s)', expected, util.hexify(expected)) # 4.1 44. read sixteen bytes from the server. # let /reply/ be those bytes. reply = receive_bytes(self._socket, 16) self._logger.debug( 'Actual challenge response: %r (%s)', reply, util.hexify(reply)) # 4.1 45. if /reply/ does not exactly equal /expected/, then fail # the WebSocket connection and abort these steps. if expected != reply: raise Exception( 'Bad challenge response: %r (expected) != %r (actual)' % (expected, reply)) # 4.1 46. The *WebSocket connection is established*. def _generate_sec_websocket_key(self): # 4.1 16. let /spaces_n/ be a random integer from 1 to 12 inclusive. spaces = random.randint(1, 12) # 4.1 17. let /max_n/ be the largest integer not greater than # 4,294,967,295 divided by /spaces_n/. maxnum = 4294967295 / spaces # 4.1 18. let /number_n/ be a random integer from 0 to /max_n/ # inclusive. number = random.randint(0, maxnum) # 4.1 19. let /product_n/ be the result of multiplying /number_n/ and # /spaces_n/ together. product = number * spaces # 4.1 20. let /key_n/ be a string consisting of /product_n/, expressed # in base ten using the numerals in the range U+0030 DIGIT ZERO (0) to # U+0039 DIGIT NINE (9). key = str(product) # 4.1 21. insert between one and twelve random characters from the # range U+0021 to U+002F and U+003A to U+007E into /key_n/ at random # positions. available_chars = range(0x21, 0x2f + 1) + range(0x3a, 0x7e + 1) n = random.randint(1, 12) for _ in xrange(n): ch = random.choice(available_chars) pos = random.randint(0, len(key)) key = key[0:pos] + chr(ch) + key[pos:] # 4.1 22. insert /spaces_n/ U+0020 SPACE characters into /key_n/ at # random positions other than start or end of the string. for _ in xrange(spaces): pos = random.randint(1, len(key) - 1) key = key[0:pos] + ' ' + key[pos:] return number, key def _generate_key3(self): # 4.1 26. let /key3/ be a string consisting of eight random bytes (or # equivalently, a random 64 bit integer encoded in a big-endian order). return ''.join([chr(random.randint(0, 255)) for _ in xrange(8)]) class WebSocketHixie75Handshake(object): """WebSocket handshake processor for IETF Hixie 75.""" _EXPECTED_RESPONSE = ( 'HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + _UPGRADE_HEADER_HIXIE75 + _CONNECTION_HEADER) def __init__(self, options): self._logger = util.get_class_logger(self) self._options = options def _skip_headers(self): terminator = '\r\n\r\n' pos = 0 while pos < len(terminator): received = receive_bytes(self._socket, 1) if received == terminator[pos]: pos += 1 elif received == terminator[0]: pos = 1 else: pos = 0 def handshake(self, socket): self._socket = socket request_line = _method_line(self._options.resource) self._logger.debug('Opening handshake Request-Line: %r', request_line) self._socket.sendall(request_line) headers = _UPGRADE_HEADER_HIXIE75 + _CONNECTION_HEADER headers += _format_host_header( self._options.server_host, self._options.server_port, self._options.use_tls) headers += _origin_header(self._options.origin) self._logger.debug('Opening handshake request headers: %r', headers) self._socket.sendall(headers) self._socket.sendall('\r\n') self._logger.info('Sent opening handshake request') for expected_char in WebSocketHixie75Handshake._EXPECTED_RESPONSE: received = receive_bytes(self._socket, 1) if expected_char != received: raise Exception('Handshake failure') # We cut corners and skip other headers. self._skip_headers() class WebSocketStream(object): """Frame processor for the WebSocket protocol (RFC 6455).""" def __init__(self, socket, handshake): self._handshake = handshake if self._handshake._options.use_deflate_stream: self._socket = util.DeflateSocket(socket) else: self._socket = socket # Filters applied to application data part of data frames. self._outgoing_frame_filter = None self._incoming_frame_filter = None if self._handshake._options.use_deflate_frame: self._outgoing_frame_filter = ( util._RFC1979Deflater(None, False)) self._incoming_frame_filter = util._RFC1979Inflater() self._fragmented = False def _mask_hybi(self, s): # TODO(tyoshino): os.urandom does open/read/close for every call. If # performance matters, change this to some library call that generates # cryptographically secure pseudo random number sequence. masking_nonce = os.urandom(4) result = [masking_nonce] count = 0 for c in s: result.append(chr(ord(c) ^ ord(masking_nonce[count]))) count = (count + 1) % len(masking_nonce) return ''.join(result) def send_frame_of_arbitrary_bytes(self, header, body): self._socket.sendall(header + self._mask_hybi(body)) def send_data(self, payload, frame_type, end=True, mask=True): if self._outgoing_frame_filter is not None: payload = self._outgoing_frame_filter.filter(payload) if self._fragmented: opcode = OPCODE_CONTINUATION else: opcode = frame_type if end: self._fragmented = False fin = 1 else: self._fragmented = True fin = 0 rsv1 = 0 if self._handshake._options.use_deflate_frame: rsv1 = 1 if mask: mask_bit = 1 << 7 else: mask_bit = 0 header = chr(fin << 7 | rsv1 << 6 | opcode) payload_length = len(payload) if payload_length <= 125: header += chr(mask_bit | payload_length) elif payload_length < 1 << 16: header += chr(mask_bit | 126) + struct.pack('!H', payload_length) elif payload_length < 1 << 63: header += chr(mask_bit | 127) + struct.pack('!Q', payload_length) else: raise Exception('Too long payload (%d byte)' % payload_length) if mask: payload = self._mask_hybi(payload) self._socket.sendall(header + payload) def send_binary(self, payload, end=True, mask=True): self.send_data(payload, OPCODE_BINARY, end, mask) def send_text(self, payload, end=True, mask=True): self.send_data(payload.encode('utf-8'), OPCODE_TEXT, end, mask) def _assert_receive_data(self, payload, opcode, fin, rsv1, rsv2, rsv3): (actual_fin, actual_rsv1, actual_rsv2, actual_rsv3, actual_opcode, payload_length) = read_frame_header(self._socket) if actual_opcode != opcode: raise Exception( 'Unexpected opcode: %d (expected) vs %d (actual)' % (opcode, actual_opcode)) if actual_fin != fin: raise Exception( 'Unexpected fin: %d (expected) vs %d (actual)' % (fin, actual_fin)) if rsv1 is None: rsv1 = 0 if self._handshake._options.use_deflate_frame: rsv1 = 1 if rsv2 is None: rsv2 = 0 if rsv3 is None: rsv3 = 0 if actual_rsv1 != rsv1: raise Exception( 'Unexpected rsv1: %r (expected) vs %r (actual)' % (rsv1, actual_rsv1)) if actual_rsv2 != rsv2: raise Exception( 'Unexpected rsv2: %r (expected) vs %r (actual)' % (rsv2, actual_rsv2)) if actual_rsv3 != rsv3: raise Exception( 'Unexpected rsv3: %r (expected) vs %r (actual)' % (rsv3, actual_rsv3)) received = receive_bytes(self._socket, payload_length) if self._incoming_frame_filter is not None: received = self._incoming_frame_filter.filter(received) if len(received) != len(payload): raise Exception( 'Unexpected payload length: %d (expected) vs %d (actual)' % (len(payload), len(received))) if payload != received: raise Exception( 'Unexpected payload: %r (expected) vs %r (actual)' % (payload, received)) def assert_receive_binary(self, payload, opcode=OPCODE_BINARY, fin=1, rsv1=None, rsv2=None, rsv3=None): self._assert_receive_data(payload, opcode, fin, rsv1, rsv2, rsv3) def assert_receive_text(self, payload, opcode=OPCODE_TEXT, fin=1, rsv1=None, rsv2=None, rsv3=None): self._assert_receive_data(payload.encode('utf-8'), opcode, fin, rsv1, rsv2, rsv3) def _build_close_frame(self, code, reason, mask): frame = chr(1 << 7 | OPCODE_CLOSE) if code is not None: body = struct.pack('!H', code) + reason.encode('utf-8') else: body = '' if mask: frame += chr(1 << 7 | len(body)) + self._mask_hybi(body) else: frame += chr(len(body)) + body return frame def send_close(self, code, reason): self._socket.sendall( self._build_close_frame(code, reason, True)) def assert_receive_close(self, code, reason): expected_frame = self._build_close_frame(code, reason, False) actual_frame = receive_bytes(self._socket, len(expected_frame)) if actual_frame != expected_frame: raise Exception( 'Unexpected close frame: %r (expected) vs %r (actual)' % (expected_frame, actual_frame)) class WebSocketStreamHixie75(object): """Frame processor for the WebSocket protocol version Hixie 75 and HyBi 00. """ _CLOSE_FRAME = '\xff\x00' def __init__(self, socket, unused_handshake): self._socket = socket def send_frame_of_arbitrary_bytes(self, header, body): self._socket.sendall(header + body) def send_data(self, payload, unused_frame_typem, unused_end, unused_mask): frame = ''.join(['\x00', payload, '\xff']) self._socket.sendall(frame) def send_binary(self, unused_payload, unused_end, unused_mask): pass def send_text(self, payload, unused_end, unused_mask): encoded_payload = payload.encode('utf-8') frame = ''.join(['\x00', encoded_payload, '\xff']) self._socket.sendall(frame) def assert_receive_binary(self, payload, opcode=OPCODE_BINARY, fin=1, rsv1=0, rsv2=0, rsv3=0): raise Exception('Binary frame is not supported in hixie75') def assert_receive_text(self, payload): received = receive_bytes(self._socket, 1) if received != '\x00': raise Exception( 'Unexpected frame type: %d (expected) vs %d (actual)' % (0, ord(received))) received = receive_bytes(self._socket, len(payload) + 1) if received[-1] != '\xff': raise Exception( 'Termination expected: 0xff (expected) vs %r (actual)' % received) if received[0:-1] != payload: raise Exception( 'Unexpected payload: %r (expected) vs %r (actual)' % (payload, received[0:-1])) def send_close(self, code, reason): self._socket.sendall(self._CLOSE_FRAME) def assert_receive_close(self, unused_code, unused_reason): closing = receive_bytes(self._socket, len(self._CLOSE_FRAME)) if closing != self._CLOSE_FRAME: raise Exception('Didn\'t receive closing handshake') class ClientOptions(object): """Holds option values to configure the Client object.""" def __init__(self): self.version = 13 self.server_host = '' self.origin = '' self.resource = '' self.server_port = -1 self.socket_timeout = 1000 self.use_tls = False self.extensions = [] # Enable deflate-stream. self.use_deflate_stream = False # Enable deflate-application-data. self.use_deflate_frame = False # Enable mux self.use_mux = False def enable_deflate_stream(self): self.use_deflate_stream = True self.extensions.append(_DEFLATE_STREAM_EXTENSION) def enable_deflate_frame(self): self.use_deflate_frame = True self.extensions.append(_DEFLATE_FRAME_EXTENSION) def enable_mux(self): self.use_mux = True self.extensions.append(_MUX_EXTENSION) class Client(object): """WebSocket client.""" def __init__(self, options, handshake, stream_class): self._logger = util.get_class_logger(self) self._options = options self._socket = None self._handshake = handshake self._stream_class = stream_class def connect(self): self._socket = socket.socket() self._socket.settimeout(self._options.socket_timeout) self._socket.connect((self._options.server_host, self._options.server_port)) if self._options.use_tls: self._socket = _TLSSocket(self._socket) self._handshake.handshake(self._socket) self._stream = self._stream_class(self._socket, self._handshake) self._logger.info('Connection established') def send_frame_of_arbitrary_bytes(self, header, body): self._stream.send_frame_of_arbitrary_bytes(header, body) def send_message(self, message, end=True, binary=False, raw=False, mask=True): if binary: self._stream.send_binary(message, end, mask) elif raw: self._stream.send_data(message, OPCODE_TEXT, end, mask) else: self._stream.send_text(message, end, mask) def assert_receive(self, payload, binary=False): if binary: self._stream.assert_receive_binary(payload) else: self._stream.assert_receive_text(payload) def send_close(self, code=STATUS_NORMAL_CLOSURE, reason=''): self._stream.send_close(code, reason) def assert_receive_close(self, code=STATUS_NORMAL_CLOSURE, reason=''): self._stream.assert_receive_close(code, reason) def close_socket(self): self._socket.close() def assert_connection_closed(self): try: read_data = receive_bytes(self._socket, 1) except Exception, e: if str(e).find( 'Connection closed before receiving requested length ') == 0: return try: error_number, message = e for error_name in ['ECONNRESET', 'WSAECONNRESET']: if (error_name in dir(errno) and error_number == getattr(errno, error_name)): return except: raise e raise e raise Exception('Connection is not closed (Read: %r)' % read_data) def create_client(options): return Client( options, WebSocketHandshake(options), WebSocketStream) def create_client_hybi00(options): return Client( options, WebSocketHybi00Handshake(options, '0'), WebSocketStreamHixie75) def create_client_hixie75(options): return Client( options, WebSocketHixie75Handshake(options), WebSocketStreamHixie75) # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/endtoend_with_external_server.py0000750023300700116100000000475511633311317023546 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Test for end-to-end with external server. This test is not run by run_all.py because it requires some preparations. If you would like to run this test correctly, launch Apache with mod_python and mod_pywebsocket manually. In addition, you should pass allow_draft75 option and example path as handler_scan option and Apache's DocumentRoot. """ import optparse import sys import test.test_endtoend import unittest _DEFAULT_WEB_SOCKET_PORT = 80 class EndToEndTestWithExternalServer(test.test_endtoend.EndToEndTest): pass if __name__ == '__main__': parser = optparse.OptionParser() parser.add_option('-p', '--port', dest='port', type='int', default=_DEFAULT_WEB_SOCKET_PORT, help='external test server port.') (options, args) = parser.parse_args() test.test_endtoend._use_external_server = True test.test_endtoend._external_server_port = options.port unittest.main(argv=[sys.argv[0]]) # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/test_http_header_util.py0000750023300700116100000000645411644773271022021 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for http_header_util module.""" import unittest from mod_pywebsocket import http_header_util class UnitTest(unittest.TestCase): """A unittest for http_header_util module.""" def test_parse_relative_uri(self): host, port, resource = http_header_util.parse_uri('/ws/test') self.assertEqual(None, host) self.assertEqual(None, port) self.assertEqual('/ws/test', resource) def test_parse_absolute_uri(self): host, port, resource = http_header_util.parse_uri( 'ws://localhost:10080/ws/test') self.assertEqual('localhost', host) self.assertEqual(10080, port) self.assertEqual('/ws/test', resource) host, port, resource = http_header_util.parse_uri( 'ws://example.com/ws/test') self.assertEqual('example.com', host) self.assertEqual(80, port) self.assertEqual('/ws/test', resource) host, port, resource = http_header_util.parse_uri( 'wss://example.com/') self.assertEqual('example.com', host) self.assertEqual(443, port) self.assertEqual('/', resource) host, port, resource = http_header_util.parse_uri( 'ws://example.com:8080') self.assertEqual('example.com', host) self.assertEqual(8080, port) self.assertEqual('/', resource) def test_parse_invalid_uri(self): host, port, resource = http_header_util.parse_uri('ws:///') self.assertEqual(None, resource) host, port, resource = http_header_util.parse_uri('ws://localhost:') self.assertEqual(None, resource) host, port, resource = http_header_util.parse_uri('ws://localhost:/ws') self.assertEqual(None, resource) if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/test_msgutil.py0000750023300700116100000014627412045715720020155 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2012, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for msgutil module.""" import array import Queue import random import struct import unittest import zlib import set_sys_path # Update sys.path to locate mod_pywebsocket module. from mod_pywebsocket import common from mod_pywebsocket.extensions import DeflateFrameExtensionProcessor from mod_pywebsocket.extensions import PerFrameCompressionExtensionProcessor from mod_pywebsocket.extensions import PerMessageCompressionExtensionProcessor from mod_pywebsocket import msgutil from mod_pywebsocket.stream import InvalidUTF8Exception from mod_pywebsocket.stream import Stream from mod_pywebsocket.stream import StreamHixie75 from mod_pywebsocket.stream import StreamOptions from mod_pywebsocket import util from test import mock # We use one fixed nonce for testing instead of cryptographically secure PRNG. _MASKING_NONCE = 'ABCD' def _mask_hybi(frame): frame_key = map(ord, _MASKING_NONCE) frame_key_len = len(frame_key) result = array.array('B') result.fromstring(frame) count = 0 for i in xrange(len(result)): result[i] ^= frame_key[count] count = (count + 1) % frame_key_len return _MASKING_NONCE + result.tostring() def _install_extension_processor(processor, request, stream_options): response = processor.get_extension_response() if response is not None: processor.setup_stream_options(stream_options) request.ws_extension_processors.append(processor) def _create_request_from_rawdata( read_data, deflate_stream=False, deflate_frame_request=None, perframe_compression_request=None, permessage_compression_request=None): req = mock.MockRequest(connection=mock.MockConn(''.join(read_data))) req.ws_version = common.VERSION_HYBI_LATEST stream_options = StreamOptions() stream_options.deflate_stream = deflate_stream req.ws_extension_processors = [] if deflate_frame_request is not None: processor = DeflateFrameExtensionProcessor(deflate_frame_request) _install_extension_processor(processor, req, stream_options) elif perframe_compression_request is not None: processor = PerFrameCompressionExtensionProcessor( perframe_compression_request) _install_extension_processor(processor, req, stream_options) elif permessage_compression_request is not None: processor = PerMessageCompressionExtensionProcessor( permessage_compression_request) _install_extension_processor(processor, req, stream_options) req.ws_stream = Stream(req, stream_options) return req def _create_request(*frames): """Creates MockRequest using data given as frames. frames will be returned on calling request.connection.read() where request is MockRequest returned by this function. """ read_data = [] for (header, body) in frames: read_data.append(header + _mask_hybi(body)) return _create_request_from_rawdata(read_data) def _create_blocking_request(): """Creates MockRequest. Data written to a MockRequest can be read out by calling request.connection.written_data(). """ req = mock.MockRequest(connection=mock.MockBlockingConn()) req.ws_version = common.VERSION_HYBI_LATEST stream_options = StreamOptions() req.ws_stream = Stream(req, stream_options) return req def _create_request_hixie75(read_data=''): req = mock.MockRequest(connection=mock.MockConn(read_data)) req.ws_stream = StreamHixie75(req) return req def _create_blocking_request_hixie75(): req = mock.MockRequest(connection=mock.MockBlockingConn()) req.ws_stream = StreamHixie75(req) return req class BasicMessageTest(unittest.TestCase): """Basic tests for Stream.""" def test_send_message(self): request = _create_request() msgutil.send_message(request, 'Hello') self.assertEqual('\x81\x05Hello', request.connection.written_data()) payload = 'a' * 125 request = _create_request() msgutil.send_message(request, payload) self.assertEqual('\x81\x7d' + payload, request.connection.written_data()) def test_send_medium_message(self): payload = 'a' * 126 request = _create_request() msgutil.send_message(request, payload) self.assertEqual('\x81\x7e\x00\x7e' + payload, request.connection.written_data()) payload = 'a' * ((1 << 16) - 1) request = _create_request() msgutil.send_message(request, payload) self.assertEqual('\x81\x7e\xff\xff' + payload, request.connection.written_data()) def test_send_large_message(self): payload = 'a' * (1 << 16) request = _create_request() msgutil.send_message(request, payload) self.assertEqual('\x81\x7f\x00\x00\x00\x00\x00\x01\x00\x00' + payload, request.connection.written_data()) def test_send_message_unicode(self): request = _create_request() msgutil.send_message(request, u'\u65e5') # U+65e5 is encoded as e6,97,a5 in UTF-8 self.assertEqual('\x81\x03\xe6\x97\xa5', request.connection.written_data()) def test_send_message_fragments(self): request = _create_request() msgutil.send_message(request, 'Hello', False) msgutil.send_message(request, ' ', False) msgutil.send_message(request, 'World', False) msgutil.send_message(request, '!', True) self.assertEqual('\x01\x05Hello\x00\x01 \x00\x05World\x80\x01!', request.connection.written_data()) def test_send_fragments_immediate_zero_termination(self): request = _create_request() msgutil.send_message(request, 'Hello World!', False) msgutil.send_message(request, '', True) self.assertEqual('\x01\x0cHello World!\x80\x00', request.connection.written_data()) def test_receive_message(self): request = _create_request( ('\x81\x85', 'Hello'), ('\x81\x86', 'World!')) self.assertEqual('Hello', msgutil.receive_message(request)) self.assertEqual('World!', msgutil.receive_message(request)) payload = 'a' * 125 request = _create_request(('\x81\xfd', payload)) self.assertEqual(payload, msgutil.receive_message(request)) def test_receive_medium_message(self): payload = 'a' * 126 request = _create_request(('\x81\xfe\x00\x7e', payload)) self.assertEqual(payload, msgutil.receive_message(request)) payload = 'a' * ((1 << 16) - 1) request = _create_request(('\x81\xfe\xff\xff', payload)) self.assertEqual(payload, msgutil.receive_message(request)) def test_receive_large_message(self): payload = 'a' * (1 << 16) request = _create_request( ('\x81\xff\x00\x00\x00\x00\x00\x01\x00\x00', payload)) self.assertEqual(payload, msgutil.receive_message(request)) def test_receive_length_not_encoded_using_minimal_number_of_bytes(self): # Log warning on receiving bad payload length field that doesn't use # minimal number of bytes but continue processing. payload = 'a' # 1 byte can be represented without extended payload length field. request = _create_request( ('\x81\xff\x00\x00\x00\x00\x00\x00\x00\x01', payload)) self.assertEqual(payload, msgutil.receive_message(request)) def test_receive_message_unicode(self): request = _create_request(('\x81\x83', '\xe6\x9c\xac')) # U+672c is encoded as e6,9c,ac in UTF-8 self.assertEqual(u'\u672c', msgutil.receive_message(request)) def test_receive_message_erroneous_unicode(self): # \x80 and \x81 are invalid as UTF-8. request = _create_request(('\x81\x82', '\x80\x81')) # Invalid characters should raise InvalidUTF8Exception self.assertRaises(InvalidUTF8Exception, msgutil.receive_message, request) def test_receive_fragments(self): request = _create_request( ('\x01\x85', 'Hello'), ('\x00\x81', ' '), ('\x00\x85', 'World'), ('\x80\x81', '!')) self.assertEqual('Hello World!', msgutil.receive_message(request)) def test_receive_fragments_unicode(self): # UTF-8 encodes U+6f22 into e6bca2 and U+5b57 into e5ad97. request = _create_request( ('\x01\x82', '\xe6\xbc'), ('\x00\x82', '\xa2\xe5'), ('\x80\x82', '\xad\x97')) self.assertEqual(u'\u6f22\u5b57', msgutil.receive_message(request)) def test_receive_fragments_immediate_zero_termination(self): request = _create_request( ('\x01\x8c', 'Hello World!'), ('\x80\x80', '')) self.assertEqual('Hello World!', msgutil.receive_message(request)) def test_receive_fragments_duplicate_start(self): request = _create_request( ('\x01\x85', 'Hello'), ('\x01\x85', 'World')) self.assertRaises(msgutil.InvalidFrameException, msgutil.receive_message, request) def test_receive_fragments_intermediate_but_not_started(self): request = _create_request(('\x00\x85', 'Hello')) self.assertRaises(msgutil.InvalidFrameException, msgutil.receive_message, request) def test_receive_fragments_end_but_not_started(self): request = _create_request(('\x80\x85', 'Hello')) self.assertRaises(msgutil.InvalidFrameException, msgutil.receive_message, request) def test_receive_message_discard(self): request = _create_request( ('\x8f\x86', 'IGNORE'), ('\x81\x85', 'Hello'), ('\x8f\x89', 'DISREGARD'), ('\x81\x86', 'World!')) self.assertRaises(msgutil.UnsupportedFrameException, msgutil.receive_message, request) self.assertEqual('Hello', msgutil.receive_message(request)) self.assertRaises(msgutil.UnsupportedFrameException, msgutil.receive_message, request) self.assertEqual('World!', msgutil.receive_message(request)) def test_receive_close(self): request = _create_request( ('\x88\x8a', struct.pack('!H', 1000) + 'Good bye')) self.assertEqual(None, msgutil.receive_message(request)) self.assertEqual(1000, request.ws_close_code) self.assertEqual('Good bye', request.ws_close_reason) def test_send_longest_close(self): reason = 'a' * 123 request = _create_request( ('\x88\xfd', struct.pack('!H', common.STATUS_NORMAL_CLOSURE) + reason)) request.ws_stream.close_connection(common.STATUS_NORMAL_CLOSURE, reason) self.assertEqual(request.ws_close_code, common.STATUS_NORMAL_CLOSURE) self.assertEqual(request.ws_close_reason, reason) def test_send_close_too_long(self): request = _create_request() self.assertRaises(msgutil.BadOperationException, Stream.close_connection, request.ws_stream, common.STATUS_NORMAL_CLOSURE, 'a' * 124) def test_send_close_inconsistent_code_and_reason(self): request = _create_request() # reason parameter must not be specified when code is None. self.assertRaises(msgutil.BadOperationException, Stream.close_connection, request.ws_stream, None, 'a') def test_send_ping(self): request = _create_request() msgutil.send_ping(request, 'Hello World!') self.assertEqual('\x89\x0cHello World!', request.connection.written_data()) def test_send_longest_ping(self): request = _create_request() msgutil.send_ping(request, 'a' * 125) self.assertEqual('\x89\x7d' + 'a' * 125, request.connection.written_data()) def test_send_ping_too_long(self): request = _create_request() self.assertRaises(msgutil.BadOperationException, msgutil.send_ping, request, 'a' * 126) def test_receive_ping(self): """Tests receiving a ping control frame.""" def handler(request, message): request.called = True # Stream automatically respond to ping with pong without any action # by application layer. request = _create_request( ('\x89\x85', 'Hello'), ('\x81\x85', 'World')) self.assertEqual('World', msgutil.receive_message(request)) self.assertEqual('\x8a\x05Hello', request.connection.written_data()) request = _create_request( ('\x89\x85', 'Hello'), ('\x81\x85', 'World')) request.on_ping_handler = handler self.assertEqual('World', msgutil.receive_message(request)) self.assertTrue(request.called) def test_receive_longest_ping(self): request = _create_request( ('\x89\xfd', 'a' * 125), ('\x81\x85', 'World')) self.assertEqual('World', msgutil.receive_message(request)) self.assertEqual('\x8a\x7d' + 'a' * 125, request.connection.written_data()) def test_receive_ping_too_long(self): request = _create_request(('\x89\xfe\x00\x7e', 'a' * 126)) self.assertRaises(msgutil.InvalidFrameException, msgutil.receive_message, request) def test_receive_pong(self): """Tests receiving a pong control frame.""" def handler(request, message): request.called = True request = _create_request( ('\x8a\x85', 'Hello'), ('\x81\x85', 'World')) request.on_pong_handler = handler msgutil.send_ping(request, 'Hello') self.assertEqual('\x89\x05Hello', request.connection.written_data()) # Valid pong is received, but receive_message won't return for it. self.assertEqual('World', msgutil.receive_message(request)) # Check that nothing was written after receive_message call. self.assertEqual('\x89\x05Hello', request.connection.written_data()) self.assertTrue(request.called) def test_receive_unsolicited_pong(self): # Unsolicited pong is allowed from HyBi 07. request = _create_request( ('\x8a\x85', 'Hello'), ('\x81\x85', 'World')) msgutil.receive_message(request) request = _create_request( ('\x8a\x85', 'Hello'), ('\x81\x85', 'World')) msgutil.send_ping(request, 'Jumbo') # Body mismatch. msgutil.receive_message(request) def test_ping_cannot_be_fragmented(self): request = _create_request(('\x09\x85', 'Hello')) self.assertRaises(msgutil.InvalidFrameException, msgutil.receive_message, request) def test_ping_with_too_long_payload(self): request = _create_request(('\x89\xfe\x01\x00', 'a' * 256)) self.assertRaises(msgutil.InvalidFrameException, msgutil.receive_message, request) class DeflateStreamTest(unittest.TestCase): """Tests for checking deflate-stream extension.""" def test_send_message(self): compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) request = _create_request_from_rawdata('', deflate_stream=True) msgutil.send_message(request, 'Hello') expected = compress.compress('\x81\x05Hello') expected += compress.flush(zlib.Z_SYNC_FLUSH) self.assertEqual(expected, request.connection.written_data()) def test_receive_message(self): compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) data = compress.compress('\x81\x85' + _mask_hybi('Hello')) data += compress.flush(zlib.Z_SYNC_FLUSH) data += compress.compress('\x81\x89' + _mask_hybi('WebSocket')) data += compress.flush(zlib.Z_FINISH) compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) data += compress.compress('\x81\x85' + _mask_hybi('World')) data += compress.flush(zlib.Z_SYNC_FLUSH) # Close frame data += compress.compress( '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye')) data += compress.flush(zlib.Z_SYNC_FLUSH) request = _create_request_from_rawdata(data, deflate_stream=True) self.assertEqual('Hello', msgutil.receive_message(request)) self.assertEqual('WebSocket', msgutil.receive_message(request)) self.assertEqual('World', msgutil.receive_message(request)) self.assertFalse(request.drain_received_data_called) self.assertEqual(None, msgutil.receive_message(request)) self.assertTrue(request.drain_received_data_called) class DeflateFrameTest(unittest.TestCase): """Tests for checking deflate-frame extension.""" def test_send_message(self): compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) request = _create_request_from_rawdata( '', deflate_frame_request=extension) msgutil.send_message(request, 'Hello') msgutil.send_message(request, 'World') expected = '' compressed_hello = compress.compress('Hello') compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) compressed_hello = compressed_hello[:-4] expected += '\xc1%c' % len(compressed_hello) expected += compressed_hello compressed_world = compress.compress('World') compressed_world += compress.flush(zlib.Z_SYNC_FLUSH) compressed_world = compressed_world[:-4] expected += '\xc1%c' % len(compressed_world) expected += compressed_world self.assertEqual(expected, request.connection.written_data()) def test_send_message_bfinal(self): extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) request = _create_request_from_rawdata( '', deflate_frame_request=extension) self.assertEquals(1, len(request.ws_extension_processors)) deflate_frame_processor = request.ws_extension_processors[0] deflate_frame_processor.set_bfinal(True) msgutil.send_message(request, 'Hello') msgutil.send_message(request, 'World') expected = '' compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_hello = compress.compress('Hello') compressed_hello += compress.flush(zlib.Z_FINISH) compressed_hello = compressed_hello + chr(0) expected += '\xc1%c' % len(compressed_hello) expected += compressed_hello compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_world = compress.compress('World') compressed_world += compress.flush(zlib.Z_FINISH) compressed_world = compressed_world + chr(0) expected += '\xc1%c' % len(compressed_world) expected += compressed_world self.assertEqual(expected, request.connection.written_data()) def test_send_message_comp_bit(self): compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) request = _create_request_from_rawdata( '', deflate_frame_request=extension) self.assertEquals(1, len(request.ws_extension_processors)) deflate_frame_processor = request.ws_extension_processors[0] msgutil.send_message(request, 'Hello') deflate_frame_processor.disable_outgoing_compression() msgutil.send_message(request, 'Hello') deflate_frame_processor.enable_outgoing_compression() msgutil.send_message(request, 'Hello') expected = '' compressed_hello = compress.compress('Hello') compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) compressed_hello = compressed_hello[:-4] expected += '\xc1%c' % len(compressed_hello) expected += compressed_hello expected += '\x81\x05Hello' compressed_2nd_hello = compress.compress('Hello') compressed_2nd_hello += compress.flush(zlib.Z_SYNC_FLUSH) compressed_2nd_hello = compressed_2nd_hello[:-4] expected += '\xc1%c' % len(compressed_2nd_hello) expected += compressed_2nd_hello self.assertEqual(expected, request.connection.written_data()) def test_send_message_no_context_takeover_parameter(self): compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) extension.add_parameter('no_context_takeover', None) request = _create_request_from_rawdata( '', deflate_frame_request=extension) for i in xrange(3): msgutil.send_message(request, 'Hello') compressed_message = compress.compress('Hello') compressed_message += compress.flush(zlib.Z_SYNC_FLUSH) compressed_message = compressed_message[:-4] expected = '\xc1%c' % len(compressed_message) expected += compressed_message self.assertEqual( expected + expected + expected, request.connection.written_data()) def test_bad_request_parameters(self): """Tests that if there's anything wrong with deflate-frame extension request, deflate-frame is rejected. """ extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) # max_window_bits less than 8 is illegal. extension.add_parameter('max_window_bits', '7') processor = DeflateFrameExtensionProcessor(extension) self.assertEqual(None, processor.get_extension_response()) extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) # max_window_bits greater than 15 is illegal. extension.add_parameter('max_window_bits', '16') processor = DeflateFrameExtensionProcessor(extension) self.assertEqual(None, processor.get_extension_response()) extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) # Non integer max_window_bits is illegal. extension.add_parameter('max_window_bits', 'foobar') processor = DeflateFrameExtensionProcessor(extension) self.assertEqual(None, processor.get_extension_response()) extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) # no_context_takeover must not have any value. extension.add_parameter('no_context_takeover', 'foobar') processor = DeflateFrameExtensionProcessor(extension) self.assertEqual(None, processor.get_extension_response()) def test_response_parameters(self): extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) processor = DeflateFrameExtensionProcessor(extension) processor.set_response_window_bits(8) response = processor.get_extension_response() self.assertTrue(response.has_parameter('max_window_bits')) self.assertEqual('8', response.get_parameter_value('max_window_bits')) extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) processor = DeflateFrameExtensionProcessor(extension) processor.set_response_no_context_takeover(True) response = processor.get_extension_response() self.assertTrue(response.has_parameter('no_context_takeover')) self.assertTrue( response.get_parameter_value('no_context_takeover') is None) def test_receive_message(self): compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) data = '' compressed_hello = compress.compress('Hello') compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) compressed_hello = compressed_hello[:-4] data += '\xc1%c' % (len(compressed_hello) | 0x80) data += _mask_hybi(compressed_hello) compressed_websocket = compress.compress('WebSocket') compressed_websocket += compress.flush(zlib.Z_FINISH) compressed_websocket += '\x00' data += '\xc1%c' % (len(compressed_websocket) | 0x80) data += _mask_hybi(compressed_websocket) compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_world = compress.compress('World') compressed_world += compress.flush(zlib.Z_SYNC_FLUSH) compressed_world = compressed_world[:-4] data += '\xc1%c' % (len(compressed_world) | 0x80) data += _mask_hybi(compressed_world) # Close frame data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye') extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) request = _create_request_from_rawdata( data, deflate_frame_request=extension) self.assertEqual('Hello', msgutil.receive_message(request)) self.assertEqual('WebSocket', msgutil.receive_message(request)) self.assertEqual('World', msgutil.receive_message(request)) self.assertEqual(None, msgutil.receive_message(request)) def test_receive_message_client_using_smaller_window(self): """Test that frames coming from a client which is using smaller window size that the server are correctly received. """ # Using the smallest window bits of 8 for generating input frames. compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -8) data = '' # Use a frame whose content is bigger than the clients' DEFLATE window # size before compression. The content mainly consists of 'a' but # repetition of 'b' is put at the head and tail so that if the window # size is big, the head is back-referenced but if small, not. payload = 'b' * 64 + 'a' * 1024 + 'b' * 64 compressed_hello = compress.compress(payload) compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) compressed_hello = compressed_hello[:-4] data += '\xc1%c' % (len(compressed_hello) | 0x80) data += _mask_hybi(compressed_hello) # Close frame data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye') extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) request = _create_request_from_rawdata( data, deflate_frame_request=extension) self.assertEqual(payload, msgutil.receive_message(request)) self.assertEqual(None, msgutil.receive_message(request)) def test_receive_message_comp_bit(self): compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) data = '' compressed_hello = compress.compress('Hello') compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) compressed_hello = compressed_hello[:-4] data += '\xc1%c' % (len(compressed_hello) | 0x80) data += _mask_hybi(compressed_hello) data += '\x81\x85' + _mask_hybi('Hello') compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_2nd_hello = compress.compress('Hello') compressed_2nd_hello += compress.flush(zlib.Z_SYNC_FLUSH) compressed_2nd_hello = compressed_2nd_hello[:-4] data += '\xc1%c' % (len(compressed_2nd_hello) | 0x80) data += _mask_hybi(compressed_2nd_hello) extension = common.ExtensionParameter(common.DEFLATE_FRAME_EXTENSION) request = _create_request_from_rawdata( data, deflate_frame_request=extension) for i in xrange(3): self.assertEqual('Hello', msgutil.receive_message(request)) class PermessageCompressTest(unittest.TestCase): """Tests for checking permessage-compression extension.""" def test_deflate_response_parameters(self): extension = common.ExtensionParameter( common.PERMESSAGE_COMPRESSION_EXTENSION) extension.add_parameter('method', 'deflate') processor = PerMessageCompressionExtensionProcessor(extension) response = processor.get_extension_response() self.assertEqual('deflate', response.get_parameter_value('method')) extension = common.ExtensionParameter( common.PERMESSAGE_COMPRESSION_EXTENSION) extension.add_parameter('method', 'deflate') processor = PerMessageCompressionExtensionProcessor(extension) def _compression_processor_hook(compression_processor): compression_processor.set_c2s_max_window_bits(8) compression_processor.set_c2s_no_context_takeover(True) processor.set_compression_processor_hook( _compression_processor_hook) response = processor.get_extension_response() self.assertEqual( 'deflate; c2s_max_window_bits=8; c2s_no_context_takeover', response.get_parameter_value('method')) def test_send_message_deflate_empty(self): """Test that an empty message is compressed correctly.""" compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) extension = common.ExtensionParameter( common.PERMESSAGE_COMPRESSION_EXTENSION) extension.add_parameter('method', 'deflate') request = _create_request_from_rawdata( '', permessage_compression_request=extension) msgutil.send_message(request, '') # Payload in binary: 0b00000010 0b00000000 # From LSB, # - 1 bit of BFINAL (0) # - 2 bits of BTYPE (01 that means fixed Huffman) # - 7 bits of the first code (0000000 that is the code for the # end-of-block) # - 1 bit of BFINAL (0) # - 2 bits of BTYPE (no compression) # - 3 bits of padding self.assertEqual('\xc1\x02\x02\x00', request.connection.written_data()) def test_send_message_deflate_null_character(self): """Test that a simple payload (one null) is framed correctly.""" compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) extension = common.ExtensionParameter( common.PERMESSAGE_COMPRESSION_EXTENSION) extension.add_parameter('method', 'deflate') request = _create_request_from_rawdata( '', permessage_compression_request=extension) msgutil.send_message(request, '\x00') # Payload in binary: 0b01100010 0b00000000 0b00000000 # From LSB, # - 1 bit of BFINAL (0) # - 2 bits of BTYPE (01 that means fixed Huffman) # - 8 bits of the first code (00110000 that is the code for the literal # alphabet 0x00) # - 7 bits of the second code (0000000 that is the code for the # end-of-block) # - 1 bit of BFINAL (0) # - 2 bits of BTYPE (no compression) # - 2 bits of padding self.assertEqual('\xc1\x03\x62\x00\x00', request.connection.written_data()) def test_send_message_deflate(self): compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) extension = common.ExtensionParameter( common.PERMESSAGE_COMPRESSION_EXTENSION) extension.add_parameter('method', 'deflate') request = _create_request_from_rawdata( '', permessage_compression_request=extension) msgutil.send_message(request, 'Hello') msgutil.send_message(request, 'World') expected = '' compressed_hello = compress.compress('Hello') compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) compressed_hello = compressed_hello[:-4] expected += '\xc1%c' % len(compressed_hello) expected += compressed_hello compressed_world = compress.compress('World') compressed_world += compress.flush(zlib.Z_SYNC_FLUSH) compressed_world = compressed_world[:-4] expected += '\xc1%c' % len(compressed_world) expected += compressed_world self.assertEqual(expected, request.connection.written_data()) def test_send_message_deflate_fragmented(self): compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) extension = common.ExtensionParameter( common.PERMESSAGE_COMPRESSION_EXTENSION) extension.add_parameter('method', 'deflate') request = _create_request_from_rawdata( '', permessage_compression_request=extension) msgutil.send_message(request, 'Hello', end=False) msgutil.send_message(request, 'World', end=True) expected = '' # The first frame will be an empty frame and FIN=0 and RSV1=1. expected += '\x41\x00' compressed_message = compress.compress('HelloWorld') compressed_message += compress.flush(zlib.Z_SYNC_FLUSH) compressed_message = compressed_message[:-4] expected += '\x80%c' % len(compressed_message) expected += compressed_message self.assertEqual(expected, request.connection.written_data()) def test_send_message_deflate_fragmented_bfinal(self): extension = common.ExtensionParameter( common.PERMESSAGE_COMPRESSION_EXTENSION) extension.add_parameter('method', 'deflate') request = _create_request_from_rawdata( '', permessage_compression_request=extension) self.assertEquals(1, len(request.ws_extension_processors)) compression_processor = ( request.ws_extension_processors[0].get_compression_processor()) compression_processor.set_bfinal(True) msgutil.send_message(request, 'Hello', end=False) msgutil.send_message(request, 'World', end=True) expected = '' compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_hello = compress.compress('Hello') compressed_hello += compress.flush(zlib.Z_FINISH) compressed_hello = compressed_hello + chr(0) expected += '\x41%c' % len(compressed_hello) expected += compressed_hello compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_world = compress.compress('World') compressed_world += compress.flush(zlib.Z_FINISH) compressed_world = compressed_world + chr(0) expected += '\x80%c' % len(compressed_world) expected += compressed_world self.assertEqual(expected, request.connection.written_data()) def test_receive_message_deflate(self): compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) data = '' compressed_hello = compress.compress('HelloWebSocket') compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) compressed_hello = compressed_hello[:-4] split_position = len(compressed_hello) / 2 data += '\x41%c' % (split_position | 0x80) data += _mask_hybi(compressed_hello[:split_position]) data += '\x80%c' % ((len(compressed_hello) - split_position) | 0x80) data += _mask_hybi(compressed_hello[split_position:]) compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_world = compress.compress('World') compressed_world += compress.flush(zlib.Z_SYNC_FLUSH) compressed_world = compressed_world[:-4] data += '\xc1%c' % (len(compressed_world) | 0x80) data += _mask_hybi(compressed_world) # Close frame data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye') extension = common.ExtensionParameter( common.PERMESSAGE_COMPRESSION_EXTENSION) extension.add_parameter('method', 'deflate') request = _create_request_from_rawdata( data, permessage_compression_request=extension) self.assertEqual('HelloWebSocket', msgutil.receive_message(request)) self.assertEqual('World', msgutil.receive_message(request)) self.assertEqual(None, msgutil.receive_message(request)) def test_receive_message_deflate_random_section(self): """Test that a compressed message fragmented into lots of chunks is correctly received. """ random.seed(a=0) payload = ''.join( [chr(random.randint(0, 255)) for i in xrange(1000)]) compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_payload = compress.compress(payload) compressed_payload += compress.flush(zlib.Z_SYNC_FLUSH) compressed_payload = compressed_payload[:-4] # Fragment the compressed payload into lots of frames. bytes_chunked = 0 data = '' frame_count = 0 chunk_sizes = [] while bytes_chunked < len(compressed_payload): # Make sure that # - the length of chunks are equal or less than 125 so that we can # use 1 octet length header format for all frames. # - at least 10 chunks are created. chunk_size = random.randint( 1, min(125, len(compressed_payload) / 10, len(compressed_payload) - bytes_chunked)) chunk_sizes.append(chunk_size) chunk = compressed_payload[ bytes_chunked:bytes_chunked + chunk_size] bytes_chunked += chunk_size first_octet = 0x00 if len(data) == 0: first_octet = first_octet | 0x42 if bytes_chunked == len(compressed_payload): first_octet = first_octet | 0x80 data += '%c%c' % (first_octet, chunk_size | 0x80) data += _mask_hybi(chunk) frame_count += 1 print "Chunk sizes: %r" % chunk_sizes self.assertTrue(len(chunk_sizes) > 10) # Close frame data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye') extension = common.ExtensionParameter( common.PERMESSAGE_COMPRESSION_EXTENSION) extension.add_parameter('method', 'deflate') request = _create_request_from_rawdata( data, permessage_compression_request=extension) self.assertEqual(payload, msgutil.receive_message(request)) self.assertEqual(None, msgutil.receive_message(request)) def test_receive_message_deflate_mixed_btype(self): """Test that a message compressed using lots of DEFLATE blocks with various flush mode is correctly received. """ random.seed(a=0) payload = ''.join( [chr(random.randint(0, 255)) for i in xrange(1000)]) compress = None # Fragment the compressed payload into lots of frames. bytes_chunked = 0 compressed_payload = '' chunk_sizes = [] methods = [] sync_used = False finish_used = False while bytes_chunked < len(payload): # Make sure at least 10 chunks are created. chunk_size = random.randint( 1, min(100, len(payload) - bytes_chunked)) chunk_sizes.append(chunk_size) chunk = payload[bytes_chunked:bytes_chunked + chunk_size] bytes_chunked += chunk_size if compress is None: compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) if bytes_chunked == len(payload): compressed_payload += compress.compress(chunk) compressed_payload += compress.flush(zlib.Z_SYNC_FLUSH) compressed_payload = compressed_payload[:-4] else: method = random.randint(0, 1) methods.append(method) if method == 0: compressed_payload += compress.compress(chunk) compressed_payload += compress.flush(zlib.Z_SYNC_FLUSH) sync_used = True else: compressed_payload += compress.compress(chunk) compressed_payload += compress.flush(zlib.Z_FINISH) compress = None finish_used = True print "Chunk sizes: %r" % chunk_sizes self.assertTrue(len(chunk_sizes) > 10) print "Methods: %r" % methods self.assertTrue(sync_used) self.assertTrue(finish_used) self.assertTrue(125 < len(compressed_payload)) self.assertTrue(len(compressed_payload) < 65536) data = '\xc2\xfe' + struct.pack('!H', len(compressed_payload)) data += _mask_hybi(compressed_payload) # Close frame data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye') extension = common.ExtensionParameter( common.PERMESSAGE_COMPRESSION_EXTENSION) extension.add_parameter('method', 'deflate') request = _create_request_from_rawdata( data, permessage_compression_request=extension) self.assertEqual(payload, msgutil.receive_message(request)) self.assertEqual(None, msgutil.receive_message(request)) class PerframeCompressTest(unittest.TestCase): """Tests for checking perframe-compression extension.""" def test_send_message_deflate(self): compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) extension = common.ExtensionParameter( common.PERFRAME_COMPRESSION_EXTENSION) extension.add_parameter('method', 'deflate') request = _create_request_from_rawdata( '', perframe_compression_request=extension) msgutil.send_message(request, 'Hello') msgutil.send_message(request, 'World') expected = '' compressed_hello = compress.compress('Hello') compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) compressed_hello = compressed_hello[:-4] expected += '\xc1%c' % len(compressed_hello) expected += compressed_hello compressed_world = compress.compress('World') compressed_world += compress.flush(zlib.Z_SYNC_FLUSH) compressed_world = compressed_world[:-4] expected += '\xc1%c' % len(compressed_world) expected += compressed_world self.assertEqual(expected, request.connection.written_data()) def test_receive_message_various_btype(self): compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) data = '' compressed_hello = compress.compress('Hello') compressed_hello += compress.flush(zlib.Z_SYNC_FLUSH) compressed_hello = compressed_hello[:-4] data += '\xc1%c' % (len(compressed_hello) | 0x80) data += _mask_hybi(compressed_hello) compressed_websocket = compress.compress('WebSocket') compressed_websocket += compress.flush(zlib.Z_FINISH) compressed_websocket += '\x00' data += '\xc1%c' % (len(compressed_websocket) | 0x80) data += _mask_hybi(compressed_websocket) compress = zlib.compressobj( zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS) compressed_world = compress.compress('World') compressed_world += compress.flush(zlib.Z_SYNC_FLUSH) compressed_world = compressed_world[:-4] data += '\xc1%c' % (len(compressed_world) | 0x80) data += _mask_hybi(compressed_world) # Close frame data += '\x88\x8a' + _mask_hybi(struct.pack('!H', 1000) + 'Good bye') extension = common.ExtensionParameter( common.PERFRAME_COMPRESSION_EXTENSION) extension.add_parameter('method', 'deflate') request = _create_request_from_rawdata( data, perframe_compression_request=extension) self.assertEqual('Hello', msgutil.receive_message(request)) self.assertEqual('WebSocket', msgutil.receive_message(request)) self.assertEqual('World', msgutil.receive_message(request)) self.assertEqual(None, msgutil.receive_message(request)) class MessageTestHixie75(unittest.TestCase): """Tests for draft-hixie-thewebsocketprotocol-76 stream class.""" def test_send_message(self): request = _create_request_hixie75() msgutil.send_message(request, 'Hello') self.assertEqual('\x00Hello\xff', request.connection.written_data()) def test_send_message_unicode(self): request = _create_request_hixie75() msgutil.send_message(request, u'\u65e5') # U+65e5 is encoded as e6,97,a5 in UTF-8 self.assertEqual('\x00\xe6\x97\xa5\xff', request.connection.written_data()) def test_receive_message(self): request = _create_request_hixie75('\x00Hello\xff\x00World!\xff') self.assertEqual('Hello', msgutil.receive_message(request)) self.assertEqual('World!', msgutil.receive_message(request)) def test_receive_message_unicode(self): request = _create_request_hixie75('\x00\xe6\x9c\xac\xff') # U+672c is encoded as e6,9c,ac in UTF-8 self.assertEqual(u'\u672c', msgutil.receive_message(request)) def test_receive_message_erroneous_unicode(self): # \x80 and \x81 are invalid as UTF-8. request = _create_request_hixie75('\x00\x80\x81\xff') # Invalid characters should be replaced with # U+fffd REPLACEMENT CHARACTER self.assertEqual(u'\ufffd\ufffd', msgutil.receive_message(request)) def test_receive_message_discard(self): request = _create_request_hixie75('\x80\x06IGNORE\x00Hello\xff' '\x01DISREGARD\xff\x00World!\xff') self.assertEqual('Hello', msgutil.receive_message(request)) self.assertEqual('World!', msgutil.receive_message(request)) class MessageReceiverTest(unittest.TestCase): """Tests the Stream class using MessageReceiver.""" def test_queue(self): request = _create_blocking_request() receiver = msgutil.MessageReceiver(request) self.assertEqual(None, receiver.receive_nowait()) request.connection.put_bytes('\x81\x86' + _mask_hybi('Hello!')) self.assertEqual('Hello!', receiver.receive()) def test_onmessage(self): onmessage_queue = Queue.Queue() def onmessage_handler(message): onmessage_queue.put(message) request = _create_blocking_request() receiver = msgutil.MessageReceiver(request, onmessage_handler) request.connection.put_bytes('\x81\x86' + _mask_hybi('Hello!')) self.assertEqual('Hello!', onmessage_queue.get()) class MessageReceiverHixie75Test(unittest.TestCase): """Tests the StreamHixie75 class using MessageReceiver.""" def test_queue(self): request = _create_blocking_request_hixie75() receiver = msgutil.MessageReceiver(request) self.assertEqual(None, receiver.receive_nowait()) request.connection.put_bytes('\x00Hello!\xff') self.assertEqual('Hello!', receiver.receive()) def test_onmessage(self): onmessage_queue = Queue.Queue() def onmessage_handler(message): onmessage_queue.put(message) request = _create_blocking_request_hixie75() receiver = msgutil.MessageReceiver(request, onmessage_handler) request.connection.put_bytes('\x00Hello!\xff') self.assertEqual('Hello!', onmessage_queue.get()) class MessageSenderTest(unittest.TestCase): """Tests the Stream class using MessageSender.""" def test_send(self): request = _create_blocking_request() sender = msgutil.MessageSender(request) sender.send('World') self.assertEqual('\x81\x05World', request.connection.written_data()) def test_send_nowait(self): # Use a queue to check the bytes written by MessageSender. # request.connection.written_data() cannot be used here because # MessageSender runs in a separate thread. send_queue = Queue.Queue() def write(bytes): send_queue.put(bytes) request = _create_blocking_request() request.connection.write = write sender = msgutil.MessageSender(request) sender.send_nowait('Hello') sender.send_nowait('World') self.assertEqual('\x81\x05Hello', send_queue.get()) self.assertEqual('\x81\x05World', send_queue.get()) class MessageSenderHixie75Test(unittest.TestCase): """Tests the StreamHixie75 class using MessageSender.""" def test_send(self): request = _create_blocking_request_hixie75() sender = msgutil.MessageSender(request) sender.send('World') self.assertEqual('\x00World\xff', request.connection.written_data()) def test_send_nowait(self): # Use a queue to check the bytes written by MessageSender. # request.connection.written_data() cannot be used here because # MessageSender runs in a separate thread. send_queue = Queue.Queue() def write(bytes): send_queue.put(bytes) request = _create_blocking_request_hixie75() request.connection.write = write sender = msgutil.MessageSender(request) sender.send_nowait('Hello') sender.send_nowait('World') self.assertEqual('\x00Hello\xff', send_queue.get()) self.assertEqual('\x00World\xff', send_queue.get()) if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/test/test_handshake.py0000750023300700116100000002034412120040110020356 0ustar tyoshinoeng#!/usr/bin/env python # # Copyright 2012, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """Tests for handshake._base module.""" import unittest import set_sys_path # Update sys.path to locate mod_pywebsocket module. from mod_pywebsocket.common import ExtensionParameter from mod_pywebsocket.common import ExtensionParsingException from mod_pywebsocket.common import format_extensions from mod_pywebsocket.common import parse_extensions from mod_pywebsocket.handshake._base import HandshakeException from mod_pywebsocket.handshake._base import validate_subprotocol class ValidateSubprotocolTest(unittest.TestCase): """A unittest for validate_subprotocol method.""" def test_validate_subprotocol(self): # Should succeed. validate_subprotocol('sample') validate_subprotocol('Sample') validate_subprotocol('sample\x7eprotocol') # Should fail. self.assertRaises(HandshakeException, validate_subprotocol, '') self.assertRaises(HandshakeException, validate_subprotocol, 'sample\x09protocol') self.assertRaises(HandshakeException, validate_subprotocol, 'sample\x19protocol') self.assertRaises(HandshakeException, validate_subprotocol, 'sample\x20protocol') self.assertRaises(HandshakeException, validate_subprotocol, 'sample\x7fprotocol') self.assertRaises(HandshakeException, validate_subprotocol, # "Japan" in Japanese u'\u65e5\u672c') _TEST_TOKEN_EXTENSION_DATA = [ ('foo', [('foo', [])]), ('foo; bar', [('foo', [('bar', None)])]), ('foo; bar=baz', [('foo', [('bar', 'baz')])]), ('foo; bar=baz; car=cdr', [('foo', [('bar', 'baz'), ('car', 'cdr')])]), ('foo; bar=baz, car; cdr', [('foo', [('bar', 'baz')]), ('car', [('cdr', None)])]), ('a, b, c, d', [('a', []), ('b', []), ('c', []), ('d', [])]), ] _TEST_QUOTED_EXTENSION_DATA = [ ('foo; bar=""', [('foo', [('bar', '')])]), ('foo; bar=" baz "', [('foo', [('bar', ' baz ')])]), ('foo; bar=",baz;"', [('foo', [('bar', ',baz;')])]), ('foo; bar="\\\r\\\nbaz"', [('foo', [('bar', '\r\nbaz')])]), ('foo; bar="\\"baz"', [('foo', [('bar', '"baz')])]), ('foo; bar="\xbbbaz"', [('foo', [('bar', '\xbbbaz')])]), ] _TEST_REDUNDANT_TOKEN_EXTENSION_DATA = [ ('foo \t ', [('foo', [])]), ('foo; \r\n bar', [('foo', [('bar', None)])]), ('foo; bar=\r\n \r\n baz', [('foo', [('bar', 'baz')])]), ('foo ;bar = baz ', [('foo', [('bar', 'baz')])]), ('foo,bar,,baz', [('foo', []), ('bar', []), ('baz', [])]), ] _TEST_REDUNDANT_QUOTED_EXTENSION_DATA = [ ('foo; bar="\r\n \r\n baz"', [('foo', [('bar', ' baz')])]), ] class ExtensionsParserTest(unittest.TestCase): def _verify_extension_list(self, expected_list, actual_list): """Verifies that ExtensionParameter objects in actual_list have the same members as extension definitions in expected_list. Extension definition used in this test is a pair of an extension name and a parameter dictionary. """ self.assertEqual(len(expected_list), len(actual_list)) for expected, actual in zip(expected_list, actual_list): (name, parameters) = expected self.assertEqual(name, actual._name) self.assertEqual(parameters, actual._parameters) def test_parse(self): for formatted_string, definition in _TEST_TOKEN_EXTENSION_DATA: self._verify_extension_list( definition, parse_extensions(formatted_string, allow_quoted_string=False)) for formatted_string, unused_definition in _TEST_QUOTED_EXTENSION_DATA: self.assertRaises( ExtensionParsingException, parse_extensions, formatted_string, False) def test_parse_with_allow_quoted_string(self): for formatted_string, definition in _TEST_TOKEN_EXTENSION_DATA: self._verify_extension_list( definition, parse_extensions(formatted_string, allow_quoted_string=True)) for formatted_string, definition in _TEST_QUOTED_EXTENSION_DATA: self._verify_extension_list( definition, parse_extensions(formatted_string, allow_quoted_string=True)) def test_parse_redundant_data(self): for (formatted_string, definition) in _TEST_REDUNDANT_TOKEN_EXTENSION_DATA: self._verify_extension_list( definition, parse_extensions(formatted_string, allow_quoted_string=False)) for (formatted_string, definition) in _TEST_REDUNDANT_QUOTED_EXTENSION_DATA: self.assertRaises( ExtensionParsingException, parse_extensions, formatted_string, False) def test_parse_redundant_data_with_allow_quoted_string(self): for (formatted_string, definition) in _TEST_REDUNDANT_TOKEN_EXTENSION_DATA: self._verify_extension_list( definition, parse_extensions(formatted_string, allow_quoted_string=True)) for (formatted_string, definition) in _TEST_REDUNDANT_QUOTED_EXTENSION_DATA: self._verify_extension_list( definition, parse_extensions(formatted_string, allow_quoted_string=True)) def test_parse_bad_data(self): _TEST_BAD_EXTENSION_DATA = [ ('foo; ; '), ('foo; a a'), ('foo foo'), (',,,'), ('foo; bar='), ('foo; bar="hoge'), ('foo; bar="a\r"'), ('foo; bar="\\\xff"'), ('foo; bar=\ra'), ] for formatted_string in _TEST_BAD_EXTENSION_DATA: self.assertRaises( ExtensionParsingException, parse_extensions, formatted_string) class FormatExtensionsTest(unittest.TestCase): def test_format_extensions(self): for formatted_string, definitions in _TEST_TOKEN_EXTENSION_DATA: extensions = [] for definition in definitions: (name, parameters) = definition extension = ExtensionParameter(name) extension._parameters = parameters extensions.append(extension) self.assertEqual( formatted_string, format_extensions(extensions)) if __name__ == '__main__': unittest.main() # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/example/0000750023300700116100000000000012121621357015510 5ustar tyoshinoengpywebsocket-0.7.9/src/example/abort_wsh.py0000640023300700116100000000336012043651267020063 0ustar tyoshinoeng# Copyright 2012, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from mod_pywebsocket import handshake def web_socket_do_extra_handshake(request): pass def web_socket_transfer_data(request): raise handshake.AbortedByUserException( "Aborted in web_socket_transfer_data") # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/example/pywebsocket.conf0000640023300700116100000000360511647747162020742 0ustar tyoshinoeng# Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Sample configuration file for apache2 # LogLevel debug PythonPath "sys.path+['/mod_pywebsocket']" PythonOption mod_pywebsocket.handler_root /var/www PythonOption mod_pywebsocket.handler_scan /var/www/ws #PythonOption mod_pywebsocket.allow_draft75 On PythonHeaderParserHandler mod_pywebsocket.headerparserhandler pywebsocket-0.7.9/src/example/arraybuffer_benchmark.html0000640023300700116100000001025112117565415022727 0ustar tyoshinoeng ArrayBuffer benchmark pywebsocket-0.7.9/src/example/abort_handshake_wsh.py0000640023300700116100000000336512043651267022076 0ustar tyoshinoeng# Copyright 2012, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from mod_pywebsocket import handshake def web_socket_do_extra_handshake(request): raise handshake.AbortedByUserException( "Aborted in web_socket_do_extra_handshake") def web_socket_transfer_data(request): pass # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/example/util.js0000640023300700116100000000573112117565415017041 0ustar tyoshinoeng// Copyright 2013, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Utilities for example applications. function getTimeStamp() { return Date.now(); } function formatResultInKiB(size, speed, printSize) { if (printSize) { return (size / 1024) + '\t' + speed; } else { return speed.toString(); } } function calculateSpeedInKB(size, startTimeInMs) { return Math.round(size / (getTimeStamp() - startTimeInMs) * 1000) / 1000; } function fillArrayBuffer(buffer, c) { var i; var u32Content = c * 0x01010101; var u32Blocks = Math.floor(buffer.byteLength / 4); var u32View = new Uint32Array(buffer, 0, u32Blocks); // length attribute is slow on Chrome. Don't use it for loop condition. for (i = 0; i < u32Blocks; ++i) { u32View[i] = u32Content; } // Fraction var u8Blocks = buffer.byteLength - u32Blocks * 4; var u8View = new Uint8Array(buffer, u32Blocks * 4, u8Blocks); for (i = 0; i < u8Blocks; ++i) { u8View[i] = c; } } function verifyArrayBuffer(buffer, expectedChar) { var i; var expectedU32Value = expectedChar * 0x01010101; var u32Blocks = Math.floor(buffer.byteLength / 4); var u32View = new Uint32Array(buffer, 0, u32Blocks); for (i = 0; i < u32Blocks; ++i) { if (u32View[i] != expectedU32Value) { return false; } } var u8Blocks = buffer.byteLength - u32Blocks * 4; var u8View = new Uint8Array(buffer, u32Blocks * 4, u8Blocks); for (i = 0; i < u8Blocks; ++i) { if (u8View[i] != expectedChar) { return false; } } return true; } pywebsocket-0.7.9/src/example/echo_wsh.py0000640023300700116100000000422511667573355017707 0ustar tyoshinoeng# Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. _GOODBYE_MESSAGE = u'Goodbye' def web_socket_do_extra_handshake(request): # This example handler accepts any request. See origin_check_wsh.py for how # to reject access from untrusted scripts based on origin value. pass # Always accept. def web_socket_transfer_data(request): while True: line = request.ws_stream.receive_message() if line is None: return if isinstance(line, unicode): request.ws_stream.send_message(line, binary=False) if line == _GOODBYE_MESSAGE: return else: request.ws_stream.send_message(line, binary=True) # vi:sts=4 sw=4 et pywebsocket-0.7.9/src/example/bench_wsh.py0000640023300700116100000000442211522430402020016 0ustar tyoshinoeng# Copyright 2011, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """A simple load tester for WebSocket clients. A client program sends a message formatted as "