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.
Enso-Bot/venv/Lib/site-packages/websockets/http.py

209 lines
6.3 KiB
Python

4 years ago
"""
The :mod:`websockets.http` module provides HTTP parsing functions. They're
merely adequate for the WebSocket handshake messages.
4 years ago
These functions cannot be imported from :mod:`websockets`; they must be
imported from :mod:`websockets.http`.
4 years ago
"""
import asyncio
import http.client
4 years ago
import re
import sys
from .version import version as websockets_version
__all__ = ['read_request', 'read_response', 'USER_AGENT']
4 years ago
MAX_HEADERS = 256
MAX_LINE = 4096
USER_AGENT = ' '.join((
'Python/{}'.format(sys.version[:3]),
'websockets/{}'.format(websockets_version),
))
4 years ago
# See https://tools.ietf.org/html/rfc7230#appendix-B.
# Regex for validating header names.
_token_re = re.compile(rb'^[-!#$%&\'*+.^_`|~0-9a-zA-Z]+$')
4 years ago
# 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]*$')
4 years ago
@asyncio.coroutine
def read_request(stream):
4 years ago
"""
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.
4 years ago
``path`` isn't URL-decoded or validated in any way.
Non-ASCII characters are represented with surrogate escapes.
4 years ago
Raise an exception if the request isn't well formatted.
4 years ago
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.
4 years ago
"""
# 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)
4 years ago
# This may raise "ValueError: not enough values to unpack"
method, path, version = request_line[:-2].split(b' ', 2)
4 years ago
if method != b'GET':
raise ValueError("Unsupported HTTP method: %r" % method)
if version != b'HTTP/1.1':
raise ValueError("Unsupported HTTP version: %r" % version)
4 years ago
path = path.decode('ascii', 'surrogateescape')
headers = yield from read_headers(stream)
4 years ago
return path, headers
@asyncio.coroutine
def read_response(stream):
4 years ago
"""
Read an HTTP/1.1 response from ``stream``.
``stream`` is an :class:`~asyncio.StreamReader`.
4 years ago
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.
4 years ago
Raise an exception if the response isn't well formatted.
4 years ago
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.
4 years ago
"""
# 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)
4 years ago
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)
4 years ago
headers = yield from read_headers(stream)
4 years ago
return status_code, headers
4 years ago
@asyncio.coroutine
def read_headers(stream):
4 years ago
"""
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.
4 years ago
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':
4 years ago
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)
4 years ago
headers.append((
name.decode('ascii'), # guaranteed to be ASCII at this point
value.decode('ascii', 'surrogateescape'),
))
4 years ago
else:
raise ValueError("Too many HTTP headers")
4 years ago
return headers
@asyncio.coroutine
def read_line(stream):
4 years ago
"""
Read a single line from ``stream``.
``stream`` is an :class:`~asyncio.StreamReader`.
4 years ago
"""
# 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)
4 years ago
if len(line) > MAX_LINE:
raise ValueError("Line too long")
4 years ago
# 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
4 years ago
def build_headers(raw_headers):
4 years ago
"""
Build a date structure for HTTP headers from a list of name - value pairs.
4 years ago
See also https://github.com/aaugustin/websockets/issues/210.
4 years ago
"""
headers = http.client.HTTPMessage()
headers._headers = raw_headers # HACK
return headers