| | #cython: language_level=3 |
| | # |
| | # Based on https://github.com/MagicStack/httptools |
| | # |
| |
|
| | from cpython cimport ( |
| | Py_buffer, |
| | PyBUF_SIMPLE, |
| | PyBuffer_Release, |
| | PyBytes_AsString, |
| | PyBytes_AsStringAndSize, |
| | PyObject_GetBuffer, |
| | ) |
| | from cpython.mem cimport PyMem_Free, PyMem_Malloc |
| | from libc.limits cimport ULLONG_MAX |
| | from libc.string cimport memcpy |
| |
|
| | from multidict import CIMultiDict as _CIMultiDict, CIMultiDictProxy as _CIMultiDictProxy |
| | from yarl import URL as _URL |
| |
|
| | from aiohttp import hdrs |
| | from aiohttp.helpers import DEBUG, set_exception |
| |
|
| | from .http_exceptions import ( |
| | BadHttpMessage, |
| | BadHttpMethod, |
| | BadStatusLine, |
| | ContentLengthError, |
| | InvalidHeader, |
| | InvalidURLError, |
| | LineTooLong, |
| | PayloadEncodingError, |
| | TransferEncodingError, |
| | ) |
| | from .http_parser import DeflateBuffer as _DeflateBuffer |
| | from .http_writer import ( |
| | HttpVersion as _HttpVersion, |
| | HttpVersion10 as _HttpVersion10, |
| | HttpVersion11 as _HttpVersion11, |
| | ) |
| | from .streams import EMPTY_PAYLOAD as _EMPTY_PAYLOAD, StreamReader as _StreamReader |
| |
|
| | cimport cython |
| |
|
| | from aiohttp cimport _cparser as cparser |
| |
|
| | include "_headers.pxi" |
| |
|
| | from aiohttp cimport _find_header |
| |
|
| | ALLOWED_UPGRADES = frozenset({"websocket"}) |
| | DEF DEFAULT_FREELIST_SIZE = 250 |
| |
|
| | cdef extern from "Python.h": |
| | int PyByteArray_Resize(object, Py_ssize_t) except -1 |
| | Py_ssize_t PyByteArray_Size(object) except -1 |
| | char* PyByteArray_AsString(object) |
| |
|
| | __all__ = ('HttpRequestParser', 'HttpResponseParser', |
| | 'RawRequestMessage', 'RawResponseMessage') |
| |
|
| | cdef object URL = _URL |
| | cdef object URL_build = URL.build |
| | cdef object CIMultiDict = _CIMultiDict |
| | cdef object CIMultiDictProxy = _CIMultiDictProxy |
| | cdef object HttpVersion = _HttpVersion |
| | cdef object HttpVersion10 = _HttpVersion10 |
| | cdef object HttpVersion11 = _HttpVersion11 |
| | cdef object SEC_WEBSOCKET_KEY1 = hdrs.SEC_WEBSOCKET_KEY1 |
| | cdef object CONTENT_ENCODING = hdrs.CONTENT_ENCODING |
| | cdef object EMPTY_PAYLOAD = _EMPTY_PAYLOAD |
| | cdef object StreamReader = _StreamReader |
| | cdef object DeflateBuffer = _DeflateBuffer |
| | cdef bytes EMPTY_BYTES = b"" |
| |
|
| | cdef inline object extend(object buf, const char* at, size_t length): |
| | cdef Py_ssize_t s |
| | cdef char* ptr |
| | s = PyByteArray_Size(buf) |
| | PyByteArray_Resize(buf, s + length) |
| | ptr = PyByteArray_AsString(buf) |
| | memcpy(ptr + s, at, length) |
| |
|
| |
|
| | DEF METHODS_COUNT = 46; |
| |
|
| | cdef list _http_method = [] |
| |
|
| | for i in range(METHODS_COUNT): |
| | _http_method.append( |
| | cparser.llhttp_method_name(<cparser.llhttp_method_t> i).decode('ascii')) |
| |
|
| |
|
| | cdef inline str http_method_str(int i): |
| | if i < METHODS_COUNT: |
| | return <str>_http_method[i] |
| | else: |
| | return "<unknown>" |
| |
|
| | cdef inline object find_header(bytes raw_header): |
| | cdef Py_ssize_t size |
| | cdef char *buf |
| | cdef int idx |
| | PyBytes_AsStringAndSize(raw_header, &buf, &size) |
| | idx = _find_header.find_header(buf, size) |
| | if idx == -1: |
| | return raw_header.decode('utf-8', 'surrogateescape') |
| | return headers[idx] |
| |
|
| |
|
| | @cython.freelist(DEFAULT_FREELIST_SIZE) |
| | cdef class RawRequestMessage: |
| | cdef readonly str method |
| | cdef readonly str path |
| | cdef readonly object version # HttpVersion |
| | cdef readonly object headers # CIMultiDict |
| | cdef readonly object raw_headers # tuple |
| | cdef readonly object should_close |
| | cdef readonly object compression |
| | cdef readonly object upgrade |
| | cdef readonly object chunked |
| | cdef readonly object url # yarl.URL |
| |
|
| | def __init__(self, method, path, version, headers, raw_headers, |
| | should_close, compression, upgrade, chunked, url): |
| | self.method = method |
| | self.path = path |
| | self.version = version |
| | self.headers = headers |
| | self.raw_headers = raw_headers |
| | self.should_close = should_close |
| | self.compression = compression |
| | self.upgrade = upgrade |
| | self.chunked = chunked |
| | self.url = url |
| |
|
| | def __repr__(self): |
| | info = [] |
| | info.append(("method", self.method)) |
| | info.append(("path", self.path)) |
| | info.append(("version", self.version)) |
| | info.append(("headers", self.headers)) |
| | info.append(("raw_headers", self.raw_headers)) |
| | info.append(("should_close", self.should_close)) |
| | info.append(("compression", self.compression)) |
| | info.append(("upgrade", self.upgrade)) |
| | info.append(("chunked", self.chunked)) |
| | info.append(("url", self.url)) |
| | sinfo = ', '.join(name + '=' + repr(val) for name, val in info) |
| | return '<RawRequestMessage(' + sinfo + ')>' |
| |
|
| | def _replace(self, **dct): |
| | cdef RawRequestMessage ret |
| | ret = _new_request_message(self.method, |
| | self.path, |
| | self.version, |
| | self.headers, |
| | self.raw_headers, |
| | self.should_close, |
| | self.compression, |
| | self.upgrade, |
| | self.chunked, |
| | self.url) |
| | if "method" in dct: |
| | ret.method = dct["method"] |
| | if "path" in dct: |
| | ret.path = dct["path"] |
| | if "version" in dct: |
| | ret.version = dct["version"] |
| | if "headers" in dct: |
| | ret.headers = dct["headers"] |
| | if "raw_headers" in dct: |
| | ret.raw_headers = dct["raw_headers"] |
| | if "should_close" in dct: |
| | ret.should_close = dct["should_close"] |
| | if "compression" in dct: |
| | ret.compression = dct["compression"] |
| | if "upgrade" in dct: |
| | ret.upgrade = dct["upgrade"] |
| | if "chunked" in dct: |
| | ret.chunked = dct["chunked"] |
| | if "url" in dct: |
| | ret.url = dct["url"] |
| | return ret |
| |
|
| | cdef _new_request_message(str method, |
| | str path, |
| | object version, |
| | object headers, |
| | object raw_headers, |
| | bint should_close, |
| | object compression, |
| | bint upgrade, |
| | bint chunked, |
| | object url): |
| | cdef RawRequestMessage ret |
| | ret = RawRequestMessage.__new__(RawRequestMessage) |
| | ret.method = method |
| | ret.path = path |
| | ret.version = version |
| | ret.headers = headers |
| | ret.raw_headers = raw_headers |
| | ret.should_close = should_close |
| | ret.compression = compression |
| | ret.upgrade = upgrade |
| | ret.chunked = chunked |
| | ret.url = url |
| | return ret |
| |
|
| |
|
| | @cython.freelist(DEFAULT_FREELIST_SIZE) |
| | cdef class RawResponseMessage: |
| | cdef readonly object version # HttpVersion |
| | cdef readonly int code |
| | cdef readonly str reason |
| | cdef readonly object headers # CIMultiDict |
| | cdef readonly object raw_headers # tuple |
| | cdef readonly object should_close |
| | cdef readonly object compression |
| | cdef readonly object upgrade |
| | cdef readonly object chunked |
| |
|
| | def __init__(self, version, code, reason, headers, raw_headers, |
| | should_close, compression, upgrade, chunked): |
| | self.version = version |
| | self.code = code |
| | self.reason = reason |
| | self.headers = headers |
| | self.raw_headers = raw_headers |
| | self.should_close = should_close |
| | self.compression = compression |
| | self.upgrade = upgrade |
| | self.chunked = chunked |
| |
|
| | def __repr__(self): |
| | info = [] |
| | info.append(("version", self.version)) |
| | info.append(("code", self.code)) |
| | info.append(("reason", self.reason)) |
| | info.append(("headers", self.headers)) |
| | info.append(("raw_headers", self.raw_headers)) |
| | info.append(("should_close", self.should_close)) |
| | info.append(("compression", self.compression)) |
| | info.append(("upgrade", self.upgrade)) |
| | info.append(("chunked", self.chunked)) |
| | sinfo = ', '.join(name + '=' + repr(val) for name, val in info) |
| | return '<RawResponseMessage(' + sinfo + ')>' |
| |
|
| |
|
| | cdef _new_response_message(object version, |
| | int code, |
| | str reason, |
| | object headers, |
| | object raw_headers, |
| | bint should_close, |
| | object compression, |
| | bint upgrade, |
| | bint chunked): |
| | cdef RawResponseMessage ret |
| | ret = RawResponseMessage.__new__(RawResponseMessage) |
| | ret.version = version |
| | ret.code = code |
| | ret.reason = reason |
| | ret.headers = headers |
| | ret.raw_headers = raw_headers |
| | ret.should_close = should_close |
| | ret.compression = compression |
| | ret.upgrade = upgrade |
| | ret.chunked = chunked |
| | return ret |
| |
|
| |
|
| | @cython.internal |
| | cdef class HttpParser: |
| |
|
| | cdef: |
| | cparser.llhttp_t* _cparser |
| | cparser.llhttp_settings_t* _csettings |
| |
|
| | bytes _raw_name |
| | object _name |
| | bytes _raw_value |
| | bint _has_value |
| |
|
| | object _protocol |
| | object _loop |
| | object _timer |
| |
|
| | size_t _max_line_size |
| | size_t _max_field_size |
| | size_t _max_headers |
| | bint _response_with_body |
| | bint _read_until_eof |
| |
|
| | bint _started |
| | object _url |
| | bytearray _buf |
| | str _path |
| | str _reason |
| | list _headers |
| | list _raw_headers |
| | bint _upgraded |
| | list _messages |
| | object _payload |
| | bint _payload_error |
| | object _payload_exception |
| | object _last_error |
| | bint _auto_decompress |
| | int _limit |
| |
|
| | str _content_encoding |
| |
|
| | Py_buffer py_buf |
| |
|
| | def __cinit__(self): |
| | self._cparser = <cparser.llhttp_t*> \ |
| | PyMem_Malloc(sizeof(cparser.llhttp_t)) |
| | if self._cparser is NULL: |
| | raise MemoryError() |
| |
|
| | self._csettings = <cparser.llhttp_settings_t*> \ |
| | PyMem_Malloc(sizeof(cparser.llhttp_settings_t)) |
| | if self._csettings is NULL: |
| | raise MemoryError() |
| |
|
| | def __dealloc__(self): |
| | PyMem_Free(self._cparser) |
| | PyMem_Free(self._csettings) |
| |
|
| | cdef _init( |
| | self, cparser.llhttp_type mode, |
| | object protocol, object loop, int limit, |
| | object timer=None, |
| | size_t max_line_size=8190, size_t max_headers=32768, |
| | size_t max_field_size=8190, payload_exception=None, |
| | bint response_with_body=True, bint read_until_eof=False, |
| | bint auto_decompress=True, |
| | ): |
| | cparser.llhttp_settings_init(self._csettings) |
| | cparser.llhttp_init(self._cparser, mode, self._csettings) |
| | self._cparser.data = <void*>self |
| | self._cparser.content_length = 0 |
| |
|
| | self._protocol = protocol |
| | self._loop = loop |
| | self._timer = timer |
| |
|
| | self._buf = bytearray() |
| | self._payload = None |
| | self._payload_error = 0 |
| | self._payload_exception = payload_exception |
| | self._messages = [] |
| |
|
| | self._raw_name = EMPTY_BYTES |
| | self._raw_value = EMPTY_BYTES |
| | self._has_value = False |
| |
|
| | self._max_line_size = max_line_size |
| | self._max_headers = max_headers |
| | self._max_field_size = max_field_size |
| | self._response_with_body = response_with_body |
| | self._read_until_eof = read_until_eof |
| | self._upgraded = False |
| | self._auto_decompress = auto_decompress |
| | self._content_encoding = None |
| |
|
| | self._csettings.on_url = cb_on_url |
| | self._csettings.on_status = cb_on_status |
| | self._csettings.on_header_field = cb_on_header_field |
| | self._csettings.on_header_value = cb_on_header_value |
| | self._csettings.on_headers_complete = cb_on_headers_complete |
| | self._csettings.on_body = cb_on_body |
| | self._csettings.on_message_begin = cb_on_message_begin |
| | self._csettings.on_message_complete = cb_on_message_complete |
| | self._csettings.on_chunk_header = cb_on_chunk_header |
| | self._csettings.on_chunk_complete = cb_on_chunk_complete |
| |
|
| | self._last_error = None |
| | self._limit = limit |
| |
|
| | cdef _process_header(self): |
| | cdef str value |
| | if self._raw_name is not EMPTY_BYTES: |
| | name = find_header(self._raw_name) |
| | value = self._raw_value.decode('utf-8', 'surrogateescape') |
| |
|
| | self._headers.append((name, value)) |
| |
|
| | if name is CONTENT_ENCODING: |
| | self._content_encoding = value |
| |
|
| | self._has_value = False |
| | self._raw_headers.append((self._raw_name, self._raw_value)) |
| | self._raw_name = EMPTY_BYTES |
| | self._raw_value = EMPTY_BYTES |
| |
|
| | cdef _on_header_field(self, char* at, size_t length): |
| | if self._has_value: |
| | self._process_header() |
| |
|
| | if self._raw_name is EMPTY_BYTES: |
| | self._raw_name = at[:length] |
| | else: |
| | self._raw_name += at[:length] |
| |
|
| | cdef _on_header_value(self, char* at, size_t length): |
| | if self._raw_value is EMPTY_BYTES: |
| | self._raw_value = at[:length] |
| | else: |
| | self._raw_value += at[:length] |
| | self._has_value = True |
| |
|
| | cdef _on_headers_complete(self): |
| | self._process_header() |
| |
|
| | should_close = not cparser.llhttp_should_keep_alive(self._cparser) |
| | upgrade = self._cparser.upgrade |
| | chunked = self._cparser.flags & cparser.F_CHUNKED |
| |
|
| | raw_headers = tuple(self._raw_headers) |
| | headers = CIMultiDictProxy(CIMultiDict(self._headers)) |
| |
|
| | if self._cparser.type == cparser.HTTP_REQUEST: |
| | allowed = upgrade and headers.get("upgrade", "").lower() in ALLOWED_UPGRADES |
| | if allowed or self._cparser.method == cparser.HTTP_CONNECT: |
| | self._upgraded = True |
| | else: |
| | if upgrade and self._cparser.status_code == 101: |
| | self._upgraded = True |
| |
|
| | # do not support old websocket spec |
| | if SEC_WEBSOCKET_KEY1 in headers: |
| | raise InvalidHeader(SEC_WEBSOCKET_KEY1) |
| |
|
| | encoding = None |
| | enc = self._content_encoding |
| | if enc is not None: |
| | self._content_encoding = None |
| | enc = enc.lower() |
| | if enc in ('gzip', 'deflate', 'br'): |
| | encoding = enc |
| |
|
| | if self._cparser.type == cparser.HTTP_REQUEST: |
| | method = http_method_str(self._cparser.method) |
| | msg = _new_request_message( |
| | method, self._path, |
| | self.http_version(), headers, raw_headers, |
| | should_close, encoding, upgrade, chunked, self._url) |
| | else: |
| | msg = _new_response_message( |
| | self.http_version(), self._cparser.status_code, self._reason, |
| | headers, raw_headers, should_close, encoding, |
| | upgrade, chunked) |
| |
|
| | if ( |
| | ULLONG_MAX > self._cparser.content_length > 0 or chunked or |
| | self._cparser.method == cparser.HTTP_CONNECT or |
| | (self._cparser.status_code >= 199 and |
| | self._cparser.content_length == 0 and |
| | self._read_until_eof) |
| | ): |
| | payload = StreamReader( |
| | self._protocol, timer=self._timer, loop=self._loop, |
| | limit=self._limit) |
| | else: |
| | payload = EMPTY_PAYLOAD |
| |
|
| | self._payload = payload |
| | if encoding is not None and self._auto_decompress: |
| | self._payload = DeflateBuffer(payload, encoding) |
| |
|
| | if not self._response_with_body: |
| | payload = EMPTY_PAYLOAD |
| |
|
| | self._messages.append((msg, payload)) |
| |
|
| | cdef _on_message_complete(self): |
| | self._payload.feed_eof() |
| | self._payload = None |
| |
|
| | cdef _on_chunk_header(self): |
| | self._payload.begin_http_chunk_receiving() |
| |
|
| | cdef _on_chunk_complete(self): |
| | self._payload.end_http_chunk_receiving() |
| |
|
| | cdef object _on_status_complete(self): |
| | pass |
| |
|
| | cdef inline http_version(self): |
| | cdef cparser.llhttp_t* parser = self._cparser |
| |
|
| | if parser.http_major == 1: |
| | if parser.http_minor == 0: |
| | return HttpVersion10 |
| | elif parser.http_minor == 1: |
| | return HttpVersion11 |
| |
|
| | return HttpVersion(parser.http_major, parser.http_minor) |
| |
|
| | ### Public API ### |
| |
|
| | def feed_eof(self): |
| | cdef bytes desc |
| |
|
| | if self._payload is not None: |
| | if self._cparser.flags & cparser.F_CHUNKED: |
| | raise TransferEncodingError( |
| | "Not enough data for satisfy transfer length header.") |
| | elif self._cparser.flags & cparser.F_CONTENT_LENGTH: |
| | raise ContentLengthError( |
| | "Not enough data for satisfy content length header.") |
| | elif cparser.llhttp_get_errno(self._cparser) != cparser.HPE_OK: |
| | desc = cparser.llhttp_get_error_reason(self._cparser) |
| | raise PayloadEncodingError(desc.decode('latin-1')) |
| | else: |
| | self._payload.feed_eof() |
| | elif self._started: |
| | self._on_headers_complete() |
| | if self._messages: |
| | return self._messages[-1][0] |
| |
|
| | def feed_data(self, data): |
| | cdef: |
| | size_t data_len |
| | size_t nb |
| | cdef cparser.llhttp_errno_t errno |
| |
|
| | PyObject_GetBuffer(data, &self.py_buf, PyBUF_SIMPLE) |
| | data_len = <size_t>self.py_buf.len |
| |
|
| | errno = cparser.llhttp_execute( |
| | self._cparser, |
| | <char*>self.py_buf.buf, |
| | data_len) |
| |
|
| | if errno is cparser.HPE_PAUSED_UPGRADE: |
| | cparser.llhttp_resume_after_upgrade(self._cparser) |
| |
|
| | nb = cparser.llhttp_get_error_pos(self._cparser) - <char*>self.py_buf.buf |
| |
|
| | PyBuffer_Release(&self.py_buf) |
| |
|
| | if errno not in (cparser.HPE_OK, cparser.HPE_PAUSED_UPGRADE): |
| | if self._payload_error == 0: |
| | if self._last_error is not None: |
| | ex = self._last_error |
| | self._last_error = None |
| | else: |
| | after = cparser.llhttp_get_error_pos(self._cparser) |
| | before = data[:after - <char*>self.py_buf.buf] |
| | after_b = after.split(b"\r\n", 1)[0] |
| | before = before.rsplit(b"\r\n", 1)[-1] |
| | data = before + after_b |
| | pointer = " " * (len(repr(before))-1) + "^" |
| | ex = parser_error_from_errno(self._cparser, data, pointer) |
| | self._payload = None |
| | raise ex |
| |
|
| | if self._messages: |
| | messages = self._messages |
| | self._messages = [] |
| | else: |
| | messages = () |
| |
|
| | if self._upgraded: |
| | return messages, True, data[nb:] |
| | else: |
| | return messages, False, b"" |
| |
|
| | def set_upgraded(self, val): |
| | self._upgraded = val |
| |
|
| |
|
| | cdef class HttpRequestParser(HttpParser): |
| |
|
| | def __init__( |
| | self, protocol, loop, int limit, timer=None, |
| | size_t max_line_size=8190, size_t max_headers=32768, |
| | size_t max_field_size=8190, payload_exception=None, |
| | bint response_with_body=True, bint read_until_eof=False, |
| | bint auto_decompress=True, |
| | ): |
| | self._init(cparser.HTTP_REQUEST, protocol, loop, limit, timer, |
| | max_line_size, max_headers, max_field_size, |
| | payload_exception, response_with_body, read_until_eof, |
| | auto_decompress) |
| |
|
| | cdef object _on_status_complete(self): |
| | cdef int idx1, idx2 |
| | if not self._buf: |
| | return |
| | self._path = self._buf.decode('utf-8', 'surrogateescape') |
| | try: |
| | idx3 = len(self._path) |
| | if self._cparser.method == cparser.HTTP_CONNECT: |
| | # authority-form, |
| | # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3 |
| | self._url = URL.build(authority=self._path, encoded=True) |
| | elif idx3 > 1 and self._path[0] == '/': |
| | # origin-form, |
| | # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1 |
| | idx1 = self._path.find("?") |
| | if idx1 == -1: |
| | query = "" |
| | idx2 = self._path.find("#") |
| | if idx2 == -1: |
| | path = self._path |
| | fragment = "" |
| | else: |
| | path = self._path[0: idx2] |
| | fragment = self._path[idx2+1:] |
| |
|
| | else: |
| | path = self._path[0:idx1] |
| | idx1 += 1 |
| | idx2 = self._path.find("#", idx1+1) |
| | if idx2 == -1: |
| | query = self._path[idx1:] |
| | fragment = "" |
| | else: |
| | query = self._path[idx1: idx2] |
| | fragment = self._path[idx2+1:] |
| |
|
| | self._url = URL.build( |
| | path=path, |
| | query_string=query, |
| | fragment=fragment, |
| | encoded=True, |
| | ) |
| | else: |
| | # absolute-form for proxy maybe, |
| | # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.2 |
| | self._url = URL(self._path, encoded=True) |
| | finally: |
| | PyByteArray_Resize(self._buf, 0) |
| |
|
| |
|
| | cdef class HttpResponseParser(HttpParser): |
| |
|
| | def __init__( |
| | self, protocol, loop, int limit, timer=None, |
| | size_t max_line_size=8190, size_t max_headers=32768, |
| | size_t max_field_size=8190, payload_exception=None, |
| | bint response_with_body=True, bint read_until_eof=False, |
| | bint auto_decompress=True |
| | ): |
| | self._init(cparser.HTTP_RESPONSE, protocol, loop, limit, timer, |
| | max_line_size, max_headers, max_field_size, |
| | payload_exception, response_with_body, read_until_eof, |
| | auto_decompress) |
| | # Use strict parsing on dev mode, so users are warned about broken servers. |
| | if not DEBUG: |
| | cparser.llhttp_set_lenient_headers(self._cparser, 1) |
| | cparser.llhttp_set_lenient_optional_cr_before_lf(self._cparser, 1) |
| | cparser.llhttp_set_lenient_spaces_after_chunk_size(self._cparser, 1) |
| |
|
| | cdef object _on_status_complete(self): |
| | if self._buf: |
| | self._reason = self._buf.decode('utf-8', 'surrogateescape') |
| | PyByteArray_Resize(self._buf, 0) |
| | else: |
| | self._reason = self._reason or '' |
| |
|
| | cdef int cb_on_message_begin(cparser.llhttp_t* parser) except -1: |
| | cdef HttpParser pyparser = <HttpParser>parser.data |
| |
|
| | pyparser._started = True |
| | pyparser._headers = [] |
| | pyparser._raw_headers = [] |
| | PyByteArray_Resize(pyparser._buf, 0) |
| | pyparser._path = None |
| | pyparser._reason = None |
| | return 0 |
| |
|
| |
|
| | cdef int cb_on_url(cparser.llhttp_t* parser, |
| | const char *at, size_t length) except -1: |
| | cdef HttpParser pyparser = <HttpParser>parser.data |
| | try: |
| | if length > pyparser._max_line_size: |
| | raise LineTooLong( |
| | 'Status line is too long', pyparser._max_line_size, length) |
| | extend(pyparser._buf, at, length) |
| | except BaseException as ex: |
| | pyparser._last_error = ex |
| | return -1 |
| | else: |
| | return 0 |
| |
|
| |
|
| | cdef int cb_on_status(cparser.llhttp_t* parser, |
| | const char *at, size_t length) except -1: |
| | cdef HttpParser pyparser = <HttpParser>parser.data |
| | cdef str reason |
| | try: |
| | if length > pyparser._max_line_size: |
| | raise LineTooLong( |
| | 'Status line is too long', pyparser._max_line_size, length) |
| | extend(pyparser._buf, at, length) |
| | except BaseException as ex: |
| | pyparser._last_error = ex |
| | return -1 |
| | else: |
| | return 0 |
| |
|
| |
|
| | cdef int cb_on_header_field(cparser.llhttp_t* parser, |
| | const char *at, size_t length) except -1: |
| | cdef HttpParser pyparser = <HttpParser>parser.data |
| | cdef Py_ssize_t size |
| | try: |
| | pyparser._on_status_complete() |
| | size = len(pyparser._raw_name) + length |
| | if size > pyparser._max_field_size: |
| | raise LineTooLong( |
| | 'Header name is too long', pyparser._max_field_size, size) |
| | pyparser._on_header_field(at, length) |
| | except BaseException as ex: |
| | pyparser._last_error = ex |
| | return -1 |
| | else: |
| | return 0 |
| |
|
| |
|
| | cdef int cb_on_header_value(cparser.llhttp_t* parser, |
| | const char *at, size_t length) except -1: |
| | cdef HttpParser pyparser = <HttpParser>parser.data |
| | cdef Py_ssize_t size |
| | try: |
| | size = len(pyparser._raw_value) + length |
| | if size > pyparser._max_field_size: |
| | raise LineTooLong( |
| | 'Header value is too long', pyparser._max_field_size, size) |
| | pyparser._on_header_value(at, length) |
| | except BaseException as ex: |
| | pyparser._last_error = ex |
| | return -1 |
| | else: |
| | return 0 |
| |
|
| |
|
| | cdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1: |
| | cdef HttpParser pyparser = <HttpParser>parser.data |
| | try: |
| | pyparser._on_status_complete() |
| | pyparser._on_headers_complete() |
| | except BaseException as exc: |
| | pyparser._last_error = exc |
| | return -1 |
| | else: |
| | if pyparser._upgraded or pyparser._cparser.method == cparser.HTTP_CONNECT: |
| | return 2 |
| | else: |
| | return 0 |
| |
|
| |
|
| | cdef int cb_on_body(cparser.llhttp_t* parser, |
| | const char *at, size_t length) except -1: |
| | cdef HttpParser pyparser = <HttpParser>parser.data |
| | cdef bytes body = at[:length] |
| | try: |
| | pyparser._payload.feed_data(body, length) |
| | except BaseException as underlying_exc: |
| | reraised_exc = underlying_exc |
| | if pyparser._payload_exception is not None: |
| | reraised_exc = pyparser._payload_exception(str(underlying_exc)) |
| |
|
| | set_exception(pyparser._payload, reraised_exc, underlying_exc) |
| |
|
| | pyparser._payload_error = 1 |
| | return -1 |
| | else: |
| | return 0 |
| |
|
| |
|
| | cdef int cb_on_message_complete(cparser.llhttp_t* parser) except -1: |
| | cdef HttpParser pyparser = <HttpParser>parser.data |
| | try: |
| | pyparser._started = False |
| | pyparser._on_message_complete() |
| | except BaseException as exc: |
| | pyparser._last_error = exc |
| | return -1 |
| | else: |
| | return 0 |
| |
|
| |
|
| | cdef int cb_on_chunk_header(cparser.llhttp_t* parser) except -1: |
| | cdef HttpParser pyparser = <HttpParser>parser.data |
| | try: |
| | pyparser._on_chunk_header() |
| | except BaseException as exc: |
| | pyparser._last_error = exc |
| | return -1 |
| | else: |
| | return 0 |
| |
|
| |
|
| | cdef int cb_on_chunk_complete(cparser.llhttp_t* parser) except -1: |
| | cdef HttpParser pyparser = <HttpParser>parser.data |
| | try: |
| | pyparser._on_chunk_complete() |
| | except BaseException as exc: |
| | pyparser._last_error = exc |
| | return -1 |
| | else: |
| | return 0 |
| |
|
| |
|
| | cdef parser_error_from_errno(cparser.llhttp_t* parser, data, pointer): |
| | cdef cparser.llhttp_errno_t errno = cparser.llhttp_get_errno(parser) |
| | cdef bytes desc = cparser.llhttp_get_error_reason(parser) |
| |
|
| | err_msg = "{}:\n\n {!r}\n {}".format(desc.decode("latin-1"), data, pointer) |
| |
|
| | if errno in {cparser.HPE_CB_MESSAGE_BEGIN, |
| | cparser.HPE_CB_HEADERS_COMPLETE, |
| | cparser.HPE_CB_MESSAGE_COMPLETE, |
| | cparser.HPE_CB_CHUNK_HEADER, |
| | cparser.HPE_CB_CHUNK_COMPLETE, |
| | cparser.HPE_INVALID_CONSTANT, |
| | cparser.HPE_INVALID_HEADER_TOKEN, |
| | cparser.HPE_INVALID_CONTENT_LENGTH, |
| | cparser.HPE_INVALID_CHUNK_SIZE, |
| | cparser.HPE_INVALID_EOF_STATE, |
| | cparser.HPE_INVALID_TRANSFER_ENCODING}: |
| | return BadHttpMessage(err_msg) |
| | elif errno == cparser.HPE_INVALID_METHOD: |
| | return BadHttpMethod(error=err_msg) |
| | elif errno in {cparser.HPE_INVALID_STATUS, |
| | cparser.HPE_INVALID_VERSION}: |
| | return BadStatusLine(error=err_msg) |
| | elif errno == cparser.HPE_INVALID_URL: |
| | return InvalidURLError(err_msg) |
| |
|
| | return BadHttpMessage(err_msg) |
| |
|