/usr/lib/python3/dist-packages/websockets/framing.py is in python3-websockets 3.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | """
The :mod:`websockets.framing` module implements data framing as specified in
`section 5 of RFC 6455`_.
It deals with a single frame at a time. Anything that depends on the sequence
of frames is implemented in :mod:`websockets.protocol`.
.. _section 5 of RFC 6455: http://tools.ietf.org/html/rfc6455#section-5
"""
import asyncio
import collections
import io
import random
import struct
from .exceptions import PayloadTooBig, WebSocketProtocolError
__all__ = [
'OP_CONT', 'OP_TEXT', 'OP_BINARY', 'OP_CLOSE', 'OP_PING', 'OP_PONG',
'Frame', 'read_frame', 'write_frame', 'parse_close', 'serialize_close'
]
OP_CONT, OP_TEXT, OP_BINARY = range(0x00, 0x03)
OP_CLOSE, OP_PING, OP_PONG = range(0x08, 0x0b)
CLOSE_CODES = {
1000: "OK",
1001: "going away",
1002: "protocol error",
1003: "unsupported type",
# 1004: - (reserved)
# 1005: no status code (internal)
# 1006: connection closed abnormally (internal)
1007: "invalid data",
1008: "policy violation",
1009: "message too big",
1010: "extension required",
1011: "unexpected error",
# 1015: TLS failure (internal)
}
Frame = collections.namedtuple('Frame', ('fin', 'opcode', 'data'))
Frame.__doc__ = """WebSocket frame.
* ``fin`` is the FIN bit
* ``opcode`` is the opcode
* ``data`` is the payload data
Only these three fields are needed by higher level code. The MASK bit, payload
length and masking-key are handled on the fly by :func:`read_frame` and
:func:`write_frame`.
"""
@asyncio.coroutine
def read_frame(reader, mask, *, max_size=None):
"""
Read a WebSocket frame and return a :class:`Frame` object.
``reader`` is a coroutine taking an integer argument and reading exactly
this number of bytes, unless the end of file is reached.
``mask`` is a :class:`bool` telling whether the frame should be masked
i.e. whether the read happens on the server side.
If ``max_size`` is set and the payload exceeds this size in bytes,
:exc:`~websockets.exceptions.PayloadTooBig` is raised.
This function validates the frame before returning it and raises
:exc:`~websockets.exceptions.WebSocketProtocolError` if it contains
incorrect values.
"""
# Read the header
data = yield from reader(2)
head1, head2 = struct.unpack('!BB', data)
fin = bool(head1 & 0b10000000)
if head1 & 0b01110000:
raise WebSocketProtocolError("Reserved bits must be 0")
opcode = head1 & 0b00001111
if bool(head2 & 0b10000000) != mask:
raise WebSocketProtocolError("Incorrect masking")
length = head2 & 0b01111111
if length == 126:
data = yield from reader(2)
length, = struct.unpack('!H', data)
elif length == 127:
data = yield from reader(8)
length, = struct.unpack('!Q', data)
if max_size is not None and length > max_size:
raise PayloadTooBig("Payload exceeds limit "
"({} > {} bytes)".format(length, max_size))
if mask:
mask_bits = yield from reader(4)
# Read the data
data = yield from reader(length)
if mask:
data = bytes(b ^ mask_bits[i % 4] for i, b in enumerate(data))
frame = Frame(fin, opcode, data)
check_frame(frame)
return frame
def write_frame(frame, writer, mask):
"""
Write a WebSocket frame.
``frame`` is the :class:`Frame` object to write.
``writer`` is a function accepting bytes.
``mask`` is a :class:`bool` telling whether the frame should be masked
i.e. whether the write happens on the client side.
This function validates the frame before sending it and raises
:exc:`~websockets.exceptions.WebSocketProtocolError` if it contains
incorrect values.
"""
check_frame(frame)
output = io.BytesIO()
# Prepare the header
head1 = 0b10000000 if frame.fin else 0
head1 |= frame.opcode
head2 = 0b10000000 if mask else 0
length = len(frame.data)
if length < 0x7e:
output.write(struct.pack('!BB', head1, head2 | length))
elif length < 0x10000:
output.write(struct.pack('!BBH', head1, head2 | 126, length))
else:
output.write(struct.pack('!BBQ', head1, head2 | 127, length))
if mask:
mask_bits = struct.pack('!I', random.getrandbits(32))
output.write(mask_bits)
# Prepare the data
if mask:
data = bytes(b ^ mask_bits[i % 4] for i, b in enumerate(frame.data))
else:
data = frame.data
output.write(data)
# Send the frame
writer(output.getvalue())
def check_frame(frame):
"""
Raise :exc:`~websockets.exceptions.WebSocketProtocolError` if the frame
contains incorrect values.
"""
if frame.opcode in (OP_CONT, OP_TEXT, OP_BINARY):
return
elif frame.opcode in (OP_CLOSE, OP_PING, OP_PONG):
if len(frame.data) > 125:
raise WebSocketProtocolError("Control frame too long")
if not frame.fin:
raise WebSocketProtocolError("Fragmented control frame")
else:
raise WebSocketProtocolError("Invalid opcode")
def parse_close(data):
"""
Parse the data in a close frame.
Return ``(code, reason)`` when ``code`` is an :class:`int` and ``reason``
a :class:`str`.
Raise :exc:`~websockets.exceptions.WebSocketProtocolError` or
:exc:`UnicodeDecodeError` if the data is invalid.
"""
length = len(data)
if length == 0:
return 1005, ''
elif length == 1:
raise WebSocketProtocolError("Close frame too short")
else:
code, = struct.unpack('!H', data[:2])
if not (code in CLOSE_CODES or 3000 <= code < 5000):
raise WebSocketProtocolError("Invalid status code")
reason = data[2:].decode('utf-8')
return code, reason
def serialize_close(code, reason):
"""
Serialize the data for a close frame.
This is the reverse of :func:`parse_close`.
"""
return struct.pack('!H', code) + reason.encode('utf-8')
|