mitmproxy/netlib/websockets/protocol.py

112 lines
3.1 KiB
Python
Raw Normal View History

2016-05-28 20:17:02 +00:00
"""
Colleciton of utility functions that implement small portions of the RFC6455
WebSockets Protocol Useful for building WebSocket clients and servers.
2015-07-08 07:34:10 +00:00
2016-05-28 20:17:02 +00:00
Emphassis is on readabilty, simplicity and modularity, not performance or
completeness
2015-09-05 16:15:47 +00:00
2016-05-28 20:17:02 +00:00
This is a work in progress and does not yet contain all the utilites need to
create fully complient client/servers #
Spec: https://tools.ietf.org/html/rfc6455
2015-07-08 07:34:10 +00:00
2016-05-28 20:17:02 +00:00
The magic sha that websocket servers must know to prove they understand
RFC6455
"""
2015-07-08 07:34:10 +00:00
2015-09-05 16:15:47 +00:00
from __future__ import absolute_import
import base64
import hashlib
import os
2015-09-20 22:44:17 +00:00
2015-09-17 13:16:12 +00:00
import six
2015-09-05 16:15:47 +00:00
from ..http import Headers
2015-09-20 22:44:17 +00:00
websockets_magic = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
2015-07-08 07:34:10 +00:00
VERSION = "13"
2015-08-10 18:44:36 +00:00
2015-07-08 07:34:10 +00:00
class Masker(object):
"""
Data sent from the server must be masked to prevent malicious clients
from sending data over the wire in predictable patterns
Servers do not have to mask data they send to the client.
https://tools.ietf.org/html/rfc6455#section-5.3
"""
def __init__(self, key):
self.key = key
self.offset = 0
def mask(self, offset, data):
2015-09-20 22:44:17 +00:00
result = bytearray(data)
if six.PY2:
for i in range(len(data)):
result[i] ^= ord(self.key[offset % 4])
offset += 1
result = str(result)
else:
for i in range(len(data)):
result[i] ^= self.key[offset % 4]
offset += 1
result = bytes(result)
2015-07-08 07:34:10 +00:00
return result
def __call__(self, data):
ret = self.mask(self.offset, data)
self.offset += len(ret)
return ret
2015-08-10 18:44:36 +00:00
2015-07-08 07:34:10 +00:00
class WebsocketsProtocol(object):
def __init__(self):
pass
@classmethod
def client_handshake_headers(self, key=None, version=VERSION):
"""
Create the headers for a valid HTTP upgrade request. If Key is not
specified, it is generated, and can be found in sec-websocket-key in
the returned header set.
2015-09-05 16:15:47 +00:00
Returns an instance of Headers
2015-07-08 07:34:10 +00:00
"""
if not key:
key = base64.b64encode(os.urandom(16)).decode('ascii')
2015-09-25 16:24:18 +00:00
return Headers(
sec_websocket_key=key,
sec_websocket_version=version,
connection="Upgrade",
upgrade="websocket",
)
2015-07-08 07:34:10 +00:00
@classmethod
def server_handshake_headers(self, key):
"""
The server response is a valid HTTP 101 response.
"""
2015-09-25 16:24:18 +00:00
return Headers(
sec_websocket_accept=self.create_server_nonce(key),
connection="Upgrade",
upgrade="websocket"
)
2015-07-08 07:34:10 +00:00
@classmethod
def check_client_handshake(self, headers):
if headers.get("upgrade") != "websocket":
2015-07-08 07:34:10 +00:00
return
2015-09-25 16:24:18 +00:00
return headers.get("sec-websocket-key")
2015-07-08 07:34:10 +00:00
@classmethod
def check_server_handshake(self, headers):
if headers.get("upgrade") != "websocket":
2015-07-08 07:34:10 +00:00
return
2015-09-25 16:24:18 +00:00
return headers.get("sec-websocket-accept")
2015-07-08 07:34:10 +00:00
@classmethod
def create_server_nonce(self, client_nonce):
return base64.b64encode(hashlib.sha1(client_nonce + websockets_magic).digest())