aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/aiohttp/_http_parser.pyx
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/aiohttp/_http_parser.pyx')
-rw-r--r--.venv/lib/python3.12/site-packages/aiohttp/_http_parser.pyx837
1 files changed, 837 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/aiohttp/_http_parser.pyx b/.venv/lib/python3.12/site-packages/aiohttp/_http_parser.pyx
new file mode 100644
index 00000000..19dc3e63
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/aiohttp/_http_parser.pyx
@@ -0,0 +1,837 @@
+#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)