| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- import struct
- import socket
- from os import urandom
- from string import printable
- OPCODE_CONTINUATION = 0x0
- OPCODE_TEXT = 0x1
- OPCODE_BINARY = 0x2
- OPCODE_CLOSE = 0x8
- OPCODE_PING = 0x9
- OPCODE_PONG = 0xA
- CLOSE_NORMAL = 1000
- CLOSE_GOING_AWAY = 1001
- CLOSE_PROTOCOL_ERROR = 1002
- CLOSE_NOACCEPT_DTYPE = 1003
- CLOSE_INVALID_DATA = 1007
- CLOSE_POLICY = 1008
- CLOSE_MESSAGE_TOOBIG = 1009
- CLOSE_MISSING_EXTENSIONS = 1010
- CLOSE_UNABLE = 1011
- line_printable = [c for c in printable if c not in '\r\n\x0b\x0c']
- def printstr(s):
- return ''.join(c if c in line_printable else '.' for c in str(s))
- class Frame(object):
- """
- A Frame instance represents a web socket data frame as defined in RFC 6455.
- To encoding a frame for sending it over a socket, use Frame.pack(). To
- receive and decode a frame from a socket, use receive_frame().
- """
- def __init__(self, opcode, payload, masking_key='', mask=False, final=True,
- rsv1=False, rsv2=False, rsv3=False):
- """
- Create a new frame.
- `opcode` is one of the constants as defined above.
- `payload` is a string of bytes containing the data sendt in the frame.
- `masking_key` is an optional custom key to use for masking, or `mask`
- can be used instead to let this constructor generate a random masking
- key.
- `final` is a boolean indicating whether this frame is the last in a
- chain of fragments.
- `rsv1`, `rsv2` and `rsv3` are booleans indicating bit values for RSV1,
- RVS2 and RSV3, which are only non-zero if defined so by extensions.
- """
- if mask:
- masking_key = urandom(4)
- if len(masking_key) not in (0, 4):
- raise ValueError('invalid masking key "%s"' % masking_key)
- self.final = final
- self.rsv1 = rsv1
- self.rsv2 = rsv2
- self.rsv3 = rsv3
- self.opcode = opcode
- self.masking_key = masking_key
- self.payload = payload
- def pack(self):
- """
- Pack the frame into a string according to the following scheme:
- +-+-+-+-+-------+-+-------------+-------------------------------+
- |F|R|R|R| opcode|M| Payload len | Extended payload length |
- |I|S|S|S| (4) |A| (7) | (16/64) |
- |N|V|V|V| |S| | (if payload len==126/127) |
- | |1|2|3| |K| | |
- +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
- | Extended payload length continued, if payload len == 127 |
- + - - - - - - - - - - - - - - - +-------------------------------+
- | |Masking-key, if MASK set to 1 |
- +-------------------------------+-------------------------------+
- | Masking-key (continued) | Payload Data |
- +-------------------------------- - - - - - - - - - - - - - - - +
- : Payload Data continued ... :
- + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
- | Payload Data continued ... |
- +---------------------------------------------------------------+
- """
- header = struct.pack('!B', (self.final << 7) | (self.rsv1 << 6)
- | (self.rsv2 << 5) | (self.rsv3 << 4)
- | (self.opcode & 0xf))
- mask = bool(self.masking_key) << 7
- payload_len = len(self.payload)
- if payload_len <= 125:
- header += struct.pack('!B', mask | payload_len)
- elif payload_len < (1 << 16):
- header += struct.pack('!BH', mask | 126, payload_len)
- elif payload_len < (1 << 63):
- header += struct.pack('!BQ', mask | 127, payload_len)
- else:
- # FIXME: RFC 6455 defines an action for this...
- raise Exception('the payload length is too damn high!')
- if mask:
- return header + self.masking_key + self.mask_payload()
- return header + self.payload
- def mask_payload(self):
- return mask(self.masking_key, self.payload)
- def fragment(self, fragment_size, mask=False):
- """
- Fragment the frame into a chain of fragment frames:
- - An initial frame with non-zero opcode
- - Zero or more frames with opcode = 0 and final = False
- - A final frame with opcode = 0 and final = True
- The first and last frame may be the same frame, having a non-zero
- opcode and final = True. Thus, this function returns a list containing
- at least a single frame.
- `fragment_size` indicates the maximum payload size of each fragment.
- The payload of the original frame is split into one or more parts, and
- each part is converted to a Frame instance.
- `mask` is a boolean (default False) indicating whether the payloads
- should be masked. If True, each frame is assigned a randomly generated
- masking key.
- """
- frames = []
- for start in xrange(0, len(self.payload), fragment_size):
- payload = self.payload[start:start + fragment_size]
- frames.append(Frame(OPCODE_CONTINUATION, payload, mask=mask,
- final=False))
- frames[0].opcode = self.opcode
- frames[-1].final = True
- return frames
- def __str__(self):
- s = '<%s opcode=0x%X len=%d' \
- % (self.__class__.__name__, self.opcode, len(self.payload))
- if self.masking_key:
- s += ' masking_key=%4s' % printstr(self.masking_key)
- max_pl_disp = 30
- pl = printstr(self.payload)[:max_pl_disp]
- if len(self.payload) > max_pl_disp:
- pl += '...'
- s += ' payload=%s' % pl
- if self.rsv1:
- s += ' rsv1'
- if self.rsv2:
- s += ' rsv2'
- if self.rsv3:
- s += ' rsv3'
- return s + '>'
- class ControlFrame(Frame):
- """
- A control frame is a frame with an opcode OPCODE_CLOSE, OPCODE_PING or
- OPCODE_PONG. These frames must be handled as defined by RFC 6455, and
- """
- def fragment(self, fragment_size, mask=False):
- """
- Control frames must not be fragmented.
- """
- raise TypeError('control frames must not be fragmented')
- def pack(self):
- """
- Same as Frame.pack(), but asserts that the payload size does not exceed
- 125 bytes.
- """
- if len(self.payload) > 125:
- raise ValueError('control frames must not be larger than 125 '
- 'bytes')
- return Frame.pack(self)
- def unpack_close(self):
- """
- Unpack a close message into a status code and a reason. If no payload
- is given, the code is None and the reason is an empty string.
- """
- if self.payload:
- code = struct.unpack('!H', str(self.payload[:2]))[0]
- reason = str(self.payload[2:])
- else:
- code = None
- reason = ''
- return code, reason
- def decode_frame(reader):
- b1, b2 = struct.unpack('!BB', reader.readn(2))
- final = bool(b1 & 0x80)
- rsv1 = bool(b1 & 0x40)
- rsv2 = bool(b1 & 0x20)
- rsv3 = bool(b1 & 0x10)
- opcode = b1 & 0x0F
- masked = bool(b2 & 0x80)
- payload_len = b2 & 0x7F
- if payload_len == 126:
- payload_len = struct.unpack('!H', reader.readn(2))
- elif payload_len == 127:
- payload_len = struct.unpack('!Q', reader.readn(8))
- if masked:
- masking_key = reader.readn(4)
- payload = mask(masking_key, reader.readn(payload_len))
- else:
- masking_key = ''
- payload = reader.readn(payload_len)
- # Control frames have most significant bit 1
- cls = ControlFrame if opcode & 0x8 else Frame
- return cls(opcode, payload, masking_key=masking_key, final=final,
- rsv1=rsv1, rsv2=rsv2, rsv3=rsv3)
- def receive_frame(sock):
- return decode_frame(SocketReader(sock))
- def read_frame(data):
- reader = BufferReader(data)
- frame = decode_frame(reader)
- return frame, reader.offset
- def pop_frame(data):
- frame, size = read_frame(data)
- return frame, data[size:]
- class BufferReader(object):
- def __init__(self, data):
- self.data = data
- self.offset = 0
- def readn(self, n):
- assert len(self.data) - self.offset >= n
- self.offset += n
- return self.data[self.offset - n:self.offset]
- class SocketReader(object):
- def __init__(self, sock):
- self.sock = sock
- def readn(self, n):
- """
- Keep receiving data until exactly `n` bytes have been read.
- """
- data = ''
- while len(data) < n:
- received = self.sock.recv(n - len(data))
- if not len(received):
- raise socket.error('no data read from socket')
- data += received
- return data
- def contains_frame(data):
- """
- Read the frame length from the start of `data` and check if the data is
- long enough to contain the entire frame.
- """
- if len(data) < 2:
- return False
- b2 = struct.unpack('!B', data[1])[0]
- payload_len = b2 & 0x7F
- payload_start = 2
- if payload_len == 126:
- if len(data) > 4:
- payload_len = struct.unpack('!H', data[2:4])
- payload_start = 4
- elif payload_len == 127:
- if len(data) > 12:
- payload_len = struct.unpack('!Q', data[4:12])
- payload_start = 12
- return len(data) >= payload_len + payload_start
- def mask(key, original):
- """
- Mask an octet string using the given masking key.
- The following masking algorithm is used, as defined in RFC 6455:
- for each octet:
- j = i MOD 4
- transformed-octet-i = original-octet-i XOR masking-key-octet-j
- """
- if len(key) != 4:
- raise ValueError('invalid masking key "%s"' % key)
- key = map(ord, key)
- masked = bytearray(original)
- for i in xrange(len(masked)):
- masked[i] ^= key[i % 4]
- return masked
- def create_close_frame(code, reason):
- payload = '' if code is None else struct.pack('!H', code) + reason
- return ControlFrame(OPCODE_CLOSE, payload)
|