2015-04-24 03:09:21 +00:00
|
|
|
import cStringIO
|
2015-04-11 18:35:15 +00:00
|
|
|
import os
|
2015-04-10 02:35:40 +00:00
|
|
|
|
2015-04-24 03:09:21 +00:00
|
|
|
from nose.tools import raises
|
2015-04-17 02:29:20 +00:00
|
|
|
|
2015-04-24 03:09:21 +00:00
|
|
|
from netlib import tcp, test, websockets, http
|
2015-04-24 03:23:00 +00:00
|
|
|
import tutils
|
2015-04-23 20:47:09 +00:00
|
|
|
|
|
|
|
|
2015-04-19 21:38:09 +00:00
|
|
|
class WebSocketsEchoHandler(tcp.BaseHandler):
|
|
|
|
def __init__(self, connection, address, server):
|
|
|
|
super(WebSocketsEchoHandler, self).__init__(
|
|
|
|
connection, address, server
|
|
|
|
)
|
|
|
|
self.handshake_done = False
|
|
|
|
|
|
|
|
def handle(self):
|
|
|
|
while True:
|
|
|
|
if not self.handshake_done:
|
|
|
|
self.handshake()
|
|
|
|
else:
|
|
|
|
self.read_next_message()
|
|
|
|
|
|
|
|
def read_next_message(self):
|
2015-04-21 10:39:45 +00:00
|
|
|
frame = websockets.Frame.from_file(self.rfile)
|
2015-04-23 21:21:04 +00:00
|
|
|
self.on_message(frame.payload)
|
2015-04-19 21:38:09 +00:00
|
|
|
|
|
|
|
def send_message(self, message):
|
|
|
|
frame = websockets.Frame.default(message, from_client = False)
|
2015-04-20 05:18:30 +00:00
|
|
|
frame.to_file(self.wfile)
|
2015-04-21 01:39:00 +00:00
|
|
|
|
2015-04-19 21:38:09 +00:00
|
|
|
def handshake(self):
|
2015-04-21 10:39:45 +00:00
|
|
|
req = http.read_request(self.rfile)
|
2015-04-22 20:23:51 +00:00
|
|
|
key = websockets.check_client_handshake(req.headers)
|
2015-04-21 10:39:45 +00:00
|
|
|
|
|
|
|
self.wfile.write(http.response_preamble(101) + "\r\n")
|
|
|
|
headers = websockets.server_handshake_headers(key)
|
|
|
|
self.wfile.write(headers.format() + "\r\n")
|
2015-04-19 21:38:09 +00:00
|
|
|
self.wfile.flush()
|
|
|
|
self.handshake_done = True
|
|
|
|
|
|
|
|
def on_message(self, message):
|
|
|
|
if message is not None:
|
|
|
|
self.send_message(message)
|
|
|
|
|
|
|
|
|
|
|
|
class WebSocketsClient(tcp.TCPClient):
|
|
|
|
def __init__(self, address, source_address=None):
|
|
|
|
super(WebSocketsClient, self).__init__(address, source_address)
|
2015-04-21 10:39:45 +00:00
|
|
|
self.client_nonce = None
|
2015-04-19 21:38:09 +00:00
|
|
|
|
|
|
|
def connect(self):
|
|
|
|
super(WebSocketsClient, self).connect()
|
|
|
|
|
2015-04-21 10:39:45 +00:00
|
|
|
preamble = http.request_preamble("GET", "/")
|
|
|
|
self.wfile.write(preamble + "\r\n")
|
|
|
|
headers = websockets.client_handshake_headers()
|
|
|
|
self.client_nonce = headers.get_first("sec-websocket-key")
|
|
|
|
self.wfile.write(headers.format() + "\r\n")
|
2015-04-19 21:38:09 +00:00
|
|
|
self.wfile.flush()
|
|
|
|
|
2015-04-21 10:39:45 +00:00
|
|
|
resp = http.read_response(self.rfile, "get", None)
|
2015-04-22 20:23:51 +00:00
|
|
|
server_nonce = websockets.check_server_handshake(resp.headers)
|
2015-04-19 21:38:09 +00:00
|
|
|
|
2015-04-21 01:39:00 +00:00
|
|
|
if not server_nonce == websockets.create_server_nonce(self.client_nonce):
|
2015-04-19 21:38:09 +00:00
|
|
|
self.close()
|
|
|
|
|
|
|
|
def read_next_message(self):
|
2015-04-20 05:18:30 +00:00
|
|
|
return websockets.Frame.from_file(self.rfile).payload
|
2015-04-19 21:38:09 +00:00
|
|
|
|
|
|
|
def send_message(self, message):
|
|
|
|
frame = websockets.Frame.default(message, from_client = True)
|
2015-04-20 05:18:30 +00:00
|
|
|
frame.to_file(self.wfile)
|
2015-04-19 21:38:09 +00:00
|
|
|
|
2015-04-21 01:39:00 +00:00
|
|
|
|
2015-04-10 02:35:40 +00:00
|
|
|
class TestWebSockets(test.ServerTestBase):
|
2015-04-19 21:38:09 +00:00
|
|
|
handler = WebSocketsEchoHandler
|
2015-04-10 02:35:40 +00:00
|
|
|
|
2015-04-11 22:40:18 +00:00
|
|
|
def random_bytes(self, n = 100):
|
|
|
|
return os.urandom(n)
|
|
|
|
|
2015-04-11 18:35:15 +00:00
|
|
|
def echo(self, msg):
|
2015-04-19 21:38:09 +00:00
|
|
|
client = WebSocketsClient(("127.0.0.1", self.port))
|
2015-04-10 02:35:40 +00:00
|
|
|
client.connect()
|
|
|
|
client.send_message(msg)
|
|
|
|
response = client.read_next_message()
|
|
|
|
assert response == msg
|
|
|
|
|
2015-04-11 18:35:15 +00:00
|
|
|
def test_simple_echo(self):
|
|
|
|
self.echo("hello I'm the client")
|
|
|
|
|
|
|
|
def test_frame_sizes(self):
|
2015-04-17 02:29:20 +00:00
|
|
|
# length can fit in the the 7 bit payload length
|
|
|
|
small_msg = self.random_bytes(100)
|
|
|
|
# 50kb, sligthly larger than can fit in a 7 bit int
|
|
|
|
medium_msg = self.random_bytes(50000)
|
|
|
|
# 150kb, slightly larger than can fit in a 16 bit int
|
|
|
|
large_msg = self.random_bytes(150000)
|
2015-04-11 22:40:18 +00:00
|
|
|
|
|
|
|
self.echo(small_msg)
|
|
|
|
self.echo(medium_msg)
|
|
|
|
self.echo(large_msg)
|
|
|
|
|
|
|
|
def test_default_builder(self):
|
|
|
|
"""
|
|
|
|
default builder should always generate valid frames
|
2015-04-17 02:29:20 +00:00
|
|
|
"""
|
2015-04-11 22:40:18 +00:00
|
|
|
msg = self.random_bytes()
|
2015-04-19 21:38:09 +00:00
|
|
|
client_frame = websockets.Frame.default(msg, from_client = True)
|
|
|
|
server_frame = websockets.Frame.default(msg, from_client = False)
|
2015-04-21 11:13:42 +00:00
|
|
|
|
2015-04-11 22:40:18 +00:00
|
|
|
def test_serialization_bijection(self):
|
2015-04-13 20:36:09 +00:00
|
|
|
"""
|
2015-04-17 02:29:20 +00:00
|
|
|
Ensure that various frame types can be serialized/deserialized back
|
|
|
|
and forth between to_bytes() and from_bytes()
|
|
|
|
"""
|
2015-04-11 22:40:18 +00:00
|
|
|
for is_client in [True, False]:
|
2015-04-17 02:29:20 +00:00
|
|
|
for num_bytes in [100, 50000, 150000]:
|
2015-04-19 21:38:09 +00:00
|
|
|
frame = websockets.Frame.default(
|
2015-04-17 02:29:20 +00:00
|
|
|
self.random_bytes(num_bytes), is_client
|
|
|
|
)
|
2015-04-23 21:21:04 +00:00
|
|
|
frame2 = websockets.Frame.from_bytes(
|
2015-04-21 10:51:01 +00:00
|
|
|
frame.to_bytes()
|
2015-04-21 01:39:00 +00:00
|
|
|
)
|
2015-04-23 21:21:04 +00:00
|
|
|
assert frame == frame2
|
2015-04-11 22:40:18 +00:00
|
|
|
|
2015-04-20 05:18:30 +00:00
|
|
|
bytes = b'\x81\x03cba'
|
2015-04-21 10:51:01 +00:00
|
|
|
assert websockets.Frame.from_bytes(bytes).to_bytes() == bytes
|
2015-04-13 20:36:09 +00:00
|
|
|
|
2015-04-21 10:39:45 +00:00
|
|
|
def test_check_server_handshake(self):
|
2015-04-22 20:23:51 +00:00
|
|
|
headers = websockets.server_handshake_headers("key")
|
|
|
|
assert websockets.check_server_handshake(headers)
|
|
|
|
headers["Upgrade"] = ["not_websocket"]
|
|
|
|
assert not websockets.check_server_handshake(headers)
|
2015-04-21 10:39:45 +00:00
|
|
|
|
|
|
|
def test_check_client_handshake(self):
|
2015-04-22 20:23:51 +00:00
|
|
|
headers = websockets.client_handshake_headers("key")
|
|
|
|
assert websockets.check_client_handshake(headers) == "key"
|
|
|
|
headers["Upgrade"] = ["not_websocket"]
|
|
|
|
assert not websockets.check_client_handshake(headers)
|
2015-04-20 05:18:30 +00:00
|
|
|
|
|
|
|
|
2015-04-19 21:38:09 +00:00
|
|
|
class BadHandshakeHandler(WebSocketsEchoHandler):
|
2015-04-11 22:40:18 +00:00
|
|
|
def handshake(self):
|
2015-04-21 10:39:45 +00:00
|
|
|
client_hs = http.read_request(self.rfile)
|
2015-04-22 20:23:51 +00:00
|
|
|
websockets.check_client_handshake(client_hs.headers)
|
2015-04-21 10:39:45 +00:00
|
|
|
|
|
|
|
self.wfile.write(http.response_preamble(101) + "\r\n")
|
|
|
|
headers = websockets.server_handshake_headers("malformed key")
|
|
|
|
self.wfile.write(headers.format() + "\r\n")
|
2015-04-11 22:40:18 +00:00
|
|
|
self.wfile.flush()
|
|
|
|
self.handshake_done = True
|
|
|
|
|
2015-04-17 02:29:20 +00:00
|
|
|
|
2015-04-11 22:40:18 +00:00
|
|
|
class TestBadHandshake(test.ServerTestBase):
|
|
|
|
"""
|
|
|
|
Ensure that the client disconnects if the server handshake is malformed
|
2015-04-17 02:29:20 +00:00
|
|
|
"""
|
2015-04-11 22:40:18 +00:00
|
|
|
handler = BadHandshakeHandler
|
|
|
|
|
|
|
|
@raises(tcp.NetLibDisconnect)
|
|
|
|
def test(self):
|
2015-04-19 21:38:09 +00:00
|
|
|
client = WebSocketsClient(("127.0.0.1", self.port))
|
2015-04-11 22:40:18 +00:00
|
|
|
client.connect()
|
2015-04-17 02:29:20 +00:00
|
|
|
client.send_message("hello")
|
2015-04-24 03:09:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
class TestFrameHeader:
|
|
|
|
def test_roundtrip(self):
|
|
|
|
def round(*args, **kwargs):
|
|
|
|
f = websockets.FrameHeader(*args, **kwargs)
|
|
|
|
bytes = f.to_bytes()
|
|
|
|
f2 = websockets.FrameHeader.from_file(cStringIO.StringIO(bytes))
|
|
|
|
assert f == f2
|
|
|
|
round()
|
|
|
|
round(fin=1)
|
|
|
|
round(rsv1=1)
|
|
|
|
round(rsv2=1)
|
|
|
|
round(rsv3=1)
|
|
|
|
round(payload_length=1)
|
|
|
|
round(payload_length=100)
|
|
|
|
round(payload_length=1000)
|
|
|
|
round(payload_length=10000)
|
|
|
|
round(opcode=websockets.OPCODE.PING)
|
|
|
|
round(masking_key="test")
|
|
|
|
|
2015-04-29 21:04:22 +00:00
|
|
|
def test_human_readable(self):
|
2015-04-30 00:10:08 +00:00
|
|
|
f = websockets.FrameHeader(
|
|
|
|
masking_key="test",
|
|
|
|
fin=True,
|
|
|
|
payload_length=10
|
|
|
|
)
|
|
|
|
assert f.human_readable()
|
|
|
|
f = websockets.FrameHeader()
|
2015-04-29 21:04:22 +00:00
|
|
|
assert f.human_readable()
|
|
|
|
|
2015-04-24 03:09:21 +00:00
|
|
|
def test_funky(self):
|
|
|
|
f = websockets.FrameHeader(masking_key="test", mask=False)
|
|
|
|
bytes = f.to_bytes()
|
|
|
|
f2 = websockets.FrameHeader.from_file(cStringIO.StringIO(bytes))
|
|
|
|
assert not f2.mask
|
|
|
|
|
2015-04-24 03:23:00 +00:00
|
|
|
def test_violations(self):
|
|
|
|
tutils.raises("opcode", websockets.FrameHeader, opcode=17)
|
2015-04-24 03:42:31 +00:00
|
|
|
tutils.raises("masking key", websockets.FrameHeader, masking_key="x")
|
|
|
|
|
|
|
|
def test_automask(self):
|
|
|
|
f = websockets.FrameHeader(mask=True)
|
|
|
|
assert f.masking_key
|
|
|
|
|
|
|
|
f = websockets.FrameHeader(masking_key="foob")
|
|
|
|
assert f.mask
|
|
|
|
|
|
|
|
f = websockets.FrameHeader(masking_key="foob", mask=0)
|
|
|
|
assert not f.mask
|
|
|
|
assert f.masking_key
|
2015-04-24 03:23:00 +00:00
|
|
|
|
2015-04-24 03:09:21 +00:00
|
|
|
|
|
|
|
class TestFrame:
|
|
|
|
def test_roundtrip(self):
|
|
|
|
def round(*args, **kwargs):
|
|
|
|
f = websockets.Frame(*args, **kwargs)
|
|
|
|
bytes = f.to_bytes()
|
|
|
|
f2 = websockets.Frame.from_file(cStringIO.StringIO(bytes))
|
|
|
|
assert f == f2
|
|
|
|
round("test")
|
2015-04-24 03:31:14 +00:00
|
|
|
round("test", fin=1)
|
|
|
|
round("test", rsv1=1)
|
|
|
|
round("test", opcode=websockets.OPCODE.PING)
|
|
|
|
round("test", masking_key="test")
|
2015-04-24 03:23:00 +00:00
|
|
|
|
|
|
|
def test_human_readable(self):
|
|
|
|
f = websockets.Frame()
|
|
|
|
assert f.human_readable()
|
2015-04-30 22:09:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_masker():
|
|
|
|
tests = [
|
|
|
|
["a"],
|
|
|
|
["four"],
|
|
|
|
["fourf"],
|
|
|
|
["fourfive"],
|
|
|
|
["a", "aasdfasdfa", "asdf"],
|
|
|
|
["a"*50, "aasdfasdfa", "asdf"],
|
|
|
|
]
|
|
|
|
for i in tests:
|
|
|
|
m = websockets.Masker("abcd")
|
|
|
|
data = "".join([m(t) for t in i])
|
|
|
|
data2 = websockets.Masker("abcd")(data)
|
|
|
|
assert data2 == "".join(i)
|