mirror of https://github.com/sgoudham/Enso-Bot.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
209 lines
6.3 KiB
Python
209 lines
6.3 KiB
Python
"""
|
|
The :mod:`websockets.http` module provides HTTP parsing functions. They're
|
|
merely adequate for the WebSocket handshake messages.
|
|
|
|
These functions cannot be imported from :mod:`websockets`; they must be
|
|
imported from :mod:`websockets.http`.
|
|
|
|
"""
|
|
|
|
import asyncio
|
|
import http.client
|
|
import re
|
|
import sys
|
|
|
|
from .version import version as websockets_version
|
|
|
|
|
|
__all__ = ['read_request', 'read_response', 'USER_AGENT']
|
|
|
|
MAX_HEADERS = 256
|
|
MAX_LINE = 4096
|
|
|
|
USER_AGENT = ' '.join((
|
|
'Python/{}'.format(sys.version[:3]),
|
|
'websockets/{}'.format(websockets_version),
|
|
))
|
|
|
|
|
|
# See https://tools.ietf.org/html/rfc7230#appendix-B.
|
|
|
|
# Regex for validating header names.
|
|
|
|
_token_re = re.compile(rb'^[-!#$%&\'*+.^_`|~0-9a-zA-Z]+$')
|
|
|
|
# Regex for validating header values.
|
|
|
|
# We don't attempt to support obsolete line folding.
|
|
|
|
# Include HTAB (\x09), SP (\x20), VCHAR (\x21-\x7e), obs-text (\x80-\xff).
|
|
|
|
# The ABNF is complicated because it attempts to express that optional
|
|
# whitespace is ignored. We strip whitespace and don't revalidate that.
|
|
|
|
# See also https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189
|
|
|
|
_value_re = re.compile(rb'^[\x09\x20-\x7e\x80-\xff]*$')
|
|
|
|
|
|
@asyncio.coroutine
|
|
def read_request(stream):
|
|
"""
|
|
Read an HTTP/1.1 GET request from ``stream``.
|
|
|
|
``stream`` is an :class:`~asyncio.StreamReader`.
|
|
|
|
Return ``(path, headers)`` where ``path`` is a :class:`str` and
|
|
``headers`` is a list of ``(name, value)`` tuples.
|
|
|
|
``path`` isn't URL-decoded or validated in any way.
|
|
|
|
Non-ASCII characters are represented with surrogate escapes.
|
|
|
|
Raise an exception if the request isn't well formatted.
|
|
|
|
Don't attempt to read the request body because WebSocket handshake
|
|
requests don't have one. If the request contains a body, it may be
|
|
read from ``stream`` after this coroutine returns.
|
|
|
|
"""
|
|
# https://tools.ietf.org/html/rfc7230#section-3.1.1
|
|
|
|
# Parsing is simple because fixed values are expected for method and
|
|
# version and because path isn't checked. Since WebSocket software tends
|
|
# to implement HTTP/1.1 strictly, there's little need for lenient parsing.
|
|
|
|
# Given the implementation of read_line(), request_line ends with CRLF.
|
|
request_line = yield from read_line(stream)
|
|
|
|
# This may raise "ValueError: not enough values to unpack"
|
|
method, path, version = request_line[:-2].split(b' ', 2)
|
|
|
|
if method != b'GET':
|
|
raise ValueError("Unsupported HTTP method: %r" % method)
|
|
if version != b'HTTP/1.1':
|
|
raise ValueError("Unsupported HTTP version: %r" % version)
|
|
|
|
path = path.decode('ascii', 'surrogateescape')
|
|
|
|
headers = yield from read_headers(stream)
|
|
|
|
return path, headers
|
|
|
|
|
|
@asyncio.coroutine
|
|
def read_response(stream):
|
|
"""
|
|
Read an HTTP/1.1 response from ``stream``.
|
|
|
|
``stream`` is an :class:`~asyncio.StreamReader`.
|
|
|
|
Return ``(status_code, headers)`` where ``status_code`` is a :class:`int`
|
|
and ``headers`` is a list of ``(name, value)`` tuples.
|
|
|
|
Non-ASCII characters are represented with surrogate escapes.
|
|
|
|
Raise an exception if the response isn't well formatted.
|
|
|
|
Don't attempt to read the response body, because WebSocket handshake
|
|
responses don't have one. If the response contains a body, it may be
|
|
read from ``stream`` after this coroutine returns.
|
|
|
|
"""
|
|
# https://tools.ietf.org/html/rfc7230#section-3.1.2
|
|
|
|
# As in read_request, parsing is simple because a fixed value is expected
|
|
# for version, status_code is a 3-digit number, and reason can be ignored.
|
|
|
|
# Given the implementation of read_line(), status_line ends with CRLF.
|
|
status_line = yield from read_line(stream)
|
|
|
|
# This may raise "ValueError: not enough values to unpack"
|
|
version, status_code, reason = status_line[:-2].split(b' ', 2)
|
|
|
|
if version != b'HTTP/1.1':
|
|
raise ValueError("Unsupported HTTP version: %r" % version)
|
|
# This may raise "ValueError: invalid literal for int() with base 10"
|
|
status_code = int(status_code)
|
|
if not 100 <= status_code < 1000:
|
|
raise ValueError("Unsupported HTTP status_code code: %d" % status_code)
|
|
if not _value_re.match(reason):
|
|
raise ValueError("Invalid HTTP reason phrase: %r" % reason)
|
|
|
|
headers = yield from read_headers(stream)
|
|
|
|
return status_code, headers
|
|
|
|
|
|
@asyncio.coroutine
|
|
def read_headers(stream):
|
|
"""
|
|
Read HTTP headers from ``stream``.
|
|
|
|
``stream`` is an :class:`~asyncio.StreamReader`.
|
|
|
|
Return ``(start_line, headers)`` where ``start_line`` is :class:`bytes`
|
|
and ``headers`` is a list of ``(name, value)`` tuples.
|
|
|
|
Non-ASCII characters are represented with surrogate escapes.
|
|
|
|
"""
|
|
# https://tools.ietf.org/html/rfc7230#section-3.2
|
|
|
|
# We don't attempt to support obsolete line folding.
|
|
|
|
headers = []
|
|
for _ in range(MAX_HEADERS):
|
|
line = yield from read_line(stream)
|
|
if line == b'\r\n':
|
|
break
|
|
|
|
# This may raise "ValueError: not enough values to unpack"
|
|
name, value = line[:-2].split(b':', 1)
|
|
if not _token_re.match(name):
|
|
raise ValueError("Invalid HTTP header name: %r" % name)
|
|
value = value.strip(b' \t')
|
|
if not _value_re.match(value):
|
|
raise ValueError("Invalid HTTP header value: %r" % value)
|
|
|
|
headers.append((
|
|
name.decode('ascii'), # guaranteed to be ASCII at this point
|
|
value.decode('ascii', 'surrogateescape'),
|
|
))
|
|
|
|
else:
|
|
raise ValueError("Too many HTTP headers")
|
|
|
|
return headers
|
|
|
|
|
|
@asyncio.coroutine
|
|
def read_line(stream):
|
|
"""
|
|
Read a single line from ``stream``.
|
|
|
|
``stream`` is an :class:`~asyncio.StreamReader`.
|
|
|
|
"""
|
|
# Security: this is bounded by the StreamReader's limit (default = 32kB).
|
|
line = yield from stream.readline()
|
|
# Security: this guarantees header values are small (hardcoded = 4kB)
|
|
if len(line) > MAX_LINE:
|
|
raise ValueError("Line too long")
|
|
# Not mandatory but safe - https://tools.ietf.org/html/rfc7230#section-3.5
|
|
if not line.endswith(b'\r\n'):
|
|
raise ValueError("Line without CRLF")
|
|
return line
|
|
|
|
|
|
def build_headers(raw_headers):
|
|
"""
|
|
Build a date structure for HTTP headers from a list of name - value pairs.
|
|
|
|
See also https://github.com/aaugustin/websockets/issues/210.
|
|
|
|
"""
|
|
headers = http.client.HTTPMessage()
|
|
headers._headers = raw_headers # HACK
|
|
return headers
|