Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
_http_parser.pyx878 linesDownload Raw Back to aiohttp
1# Based on https://github.com/MagicStack/httptools
2#
3
4from cpython cimport (
5    Py_buffer,
6    PyBUF_SIMPLE,
7    PyBuffer_Release,
8    PyBytes_AsString,
9    PyBytes_AsStringAndSize,
10    PyObject_GetBuffer,
11)
12from cpython.mem cimport PyMem_Free, PyMem_Malloc
13from libc.limits cimport ULLONG_MAX
14from libc.string cimport memcpy
15
16from multidict import CIMultiDict as _CIMultiDict, CIMultiDictProxy as _CIMultiDictProxy
17from yarl import URL as _URL
18
19from aiohttp import hdrs
20from aiohttp.helpers import DEBUG, set_exception
21
22from .http_exceptions import (
23    BadHttpMessage,
24    BadHttpMethod,
25    BadStatusLine,
26    ContentLengthError,
27    InvalidHeader,
28    InvalidURLError,
29    LineTooLong,
30    PayloadEncodingError,
31    TransferEncodingError,
32)
33from .http_parser import DeflateBuffer as _DeflateBuffer
34from .http_writer import (
35    HttpVersion as _HttpVersion,
36    HttpVersion10 as _HttpVersion10,
37    HttpVersion11 as _HttpVersion11,
38)
39from .streams import EMPTY_PAYLOAD as _EMPTY_PAYLOAD, StreamReader as _StreamReader
40
41cimport cython
42
43from aiohttp cimport _cparser as cparser
44
45include "_headers.pxi"
46
47from aiohttp cimport _find_header
48
49ALLOWED_UPGRADES = frozenset({"websocket"})
50DEF DEFAULT_FREELIST_SIZE = 250
51
52cdef extern from "Python.h":
53    int PyByteArray_Resize(object, Py_ssize_t) except -1
54    Py_ssize_t PyByteArray_Size(object) except -1
55    char* PyByteArray_AsString(object)
56
57__all__ = ('HttpRequestParser', 'HttpResponseParser',
58           'RawRequestMessage', 'RawResponseMessage')
59
60cdef object URL = _URL
61cdef object URL_build = URL.build
62cdef object CIMultiDict = _CIMultiDict
63cdef object CIMultiDictProxy = _CIMultiDictProxy
64cdef object HttpVersion = _HttpVersion
65cdef object HttpVersion10 = _HttpVersion10
66cdef object HttpVersion11 = _HttpVersion11
67cdef object SEC_WEBSOCKET_KEY1 = hdrs.SEC_WEBSOCKET_KEY1
68cdef object CONTENT_ENCODING = hdrs.CONTENT_ENCODING
69cdef object EMPTY_PAYLOAD = _EMPTY_PAYLOAD
70cdef object StreamReader = _StreamReader
71cdef object DeflateBuffer = _DeflateBuffer
72cdef bytes EMPTY_BYTES = b""
73
74# RFC 9110 singleton headers — duplicates are rejected in strict mode.
75# In lax mode (response parser default), the check is skipped entirely
76# since real-world servers (e.g. Google APIs, Werkzeug) commonly send
77# duplicate headers like Content-Type or Server.
78cdef frozenset SINGLETON_HEADERS = frozenset({
79    hdrs.CONTENT_LENGTH,
80    hdrs.CONTENT_LOCATION,
81    hdrs.CONTENT_RANGE,
82    hdrs.CONTENT_TYPE,
83    hdrs.ETAG,
84    hdrs.HOST,
85    hdrs.MAX_FORWARDS,
86    hdrs.SERVER,
87    hdrs.TRANSFER_ENCODING,
88    hdrs.USER_AGENT,
89})
90
91cdef inline object extend(object buf, const char* at, size_t length):
92    cdef Py_ssize_t s
93    cdef char* ptr
94    s = PyByteArray_Size(buf)
95    PyByteArray_Resize(buf, s + length)
96    ptr = PyByteArray_AsString(buf)
97    memcpy(ptr + s, at, length)
98
99
100DEF METHODS_COUNT = 46;
101
102cdef list _http_method = []
103
104for i in range(METHODS_COUNT):
105    _http_method.append(
106        cparser.llhttp_method_name(<cparser.llhttp_method_t> i).decode('ascii'))
107
108
109cdef inline str http_method_str(int i):
110    if i < METHODS_COUNT:
111        return <str>_http_method[i]
112    else:
113        return "<unknown>"
114
115cdef inline object find_header(bytes raw_header):
116    cdef Py_ssize_t size
117    cdef char *buf
118    cdef int idx
119    PyBytes_AsStringAndSize(raw_header, &buf, &size)
120    idx = _find_header.find_header(buf, size)
121    if idx == -1:
122        return raw_header.decode('utf-8', 'surrogateescape')
123    return headers[idx]
124
125
126@cython.freelist(DEFAULT_FREELIST_SIZE)
127cdef class RawRequestMessage:
128    cdef readonly str method
129    cdef readonly str path
130    cdef readonly object version  # HttpVersion
131    cdef readonly object headers  # CIMultiDict
132    cdef readonly object raw_headers  # tuple
133    cdef readonly object should_close
134    cdef readonly object compression
135    cdef readonly object upgrade
136    cdef readonly object chunked
137    cdef readonly object url  # yarl.URL
138
139    def __init__(self, method, path, version, headers, raw_headers,
140                 should_close, compression, upgrade, chunked, url):
141        self.method = method
142        self.path = path
143        self.version = version
144        self.headers = headers
145        self.raw_headers = raw_headers
146        self.should_close = should_close
147        self.compression = compression
148        self.upgrade = upgrade
149        self.chunked = chunked
150        self.url = url
151
152    def __repr__(self):
153        info = []
154        info.append(("method", self.method))
155        info.append(("path", self.path))
156        info.append(("version", self.version))
157        info.append(("headers", self.headers))
158        info.append(("raw_headers", self.raw_headers))
159        info.append(("should_close", self.should_close))
160        info.append(("compression", self.compression))
161        info.append(("upgrade", self.upgrade))
162        info.append(("chunked", self.chunked))
163        info.append(("url", self.url))
164        sinfo = ', '.join(name + '=' + repr(val) for name, val in info)
165        return '<RawRequestMessage(' + sinfo + ')>'
166
167    def _replace(self, **dct):
168        cdef RawRequestMessage ret
169        ret = _new_request_message(self.method,
170                                   self.path,
171                                   self.version,
172                                   self.headers,
173                                   self.raw_headers,
174                                   self.should_close,
175                                   self.compression,
176                                   self.upgrade,
177                                   self.chunked,
178                                   self.url)
179        if "method" in dct:
180            ret.method = dct["method"]
181        if "path" in dct:
182            ret.path = dct["path"]
183        if "version" in dct:
184            ret.version = dct["version"]
185        if "headers" in dct:
186            ret.headers = dct["headers"]
187        if "raw_headers" in dct:
188            ret.raw_headers = dct["raw_headers"]
189        if "should_close" in dct:
190            ret.should_close = dct["should_close"]
191        if "compression" in dct:
192            ret.compression = dct["compression"]
193        if "upgrade" in dct:
194            ret.upgrade = dct["upgrade"]
195        if "chunked" in dct:
196            ret.chunked = dct["chunked"]
197        if "url" in dct:
198            ret.url = dct["url"]
199        return ret
200
201cdef _new_request_message(str method,
202                           str path,
203                           object version,
204                           object headers,
205                           object raw_headers,
206                           bint should_close,
207                           object compression,
208                           bint upgrade,
209                           bint chunked,
210                           object url):
211    cdef RawRequestMessage ret
212    ret = RawRequestMessage.__new__(RawRequestMessage)
213    ret.method = method
214    ret.path = path
215    ret.version = version
216    ret.headers = headers
217    ret.raw_headers = raw_headers
218    ret.should_close = should_close
219    ret.compression = compression
220    ret.upgrade = upgrade
221    ret.chunked = chunked
222    ret.url = url
223    return ret
224
225
226@cython.freelist(DEFAULT_FREELIST_SIZE)
227cdef class RawResponseMessage:
228    cdef readonly object version  # HttpVersion
229    cdef readonly int code
230    cdef readonly str reason
231    cdef readonly object headers  # CIMultiDict
232    cdef readonly object raw_headers  # tuple
233    cdef readonly object should_close
234    cdef readonly object compression
235    cdef readonly object upgrade
236    cdef readonly object chunked
237
238    def __init__(self, version, code, reason, headers, raw_headers,
239                 should_close, compression, upgrade, chunked):
240        self.version = version
241        self.code = code
242        self.reason = reason
243        self.headers = headers
244        self.raw_headers = raw_headers
245        self.should_close = should_close
246        self.compression = compression
247        self.upgrade = upgrade
248        self.chunked = chunked
249
250    def __repr__(self):
251        info = []
252        info.append(("version", self.version))
253        info.append(("code", self.code))
254        info.append(("reason", self.reason))
255        info.append(("headers", self.headers))
256        info.append(("raw_headers", self.raw_headers))
257        info.append(("should_close", self.should_close))
258        info.append(("compression", self.compression))
259        info.append(("upgrade", self.upgrade))
260        info.append(("chunked", self.chunked))
261        sinfo = ', '.join(name + '=' + repr(val) for name, val in info)
262        return '<RawResponseMessage(' + sinfo + ')>'
263
264
265cdef _new_response_message(object version,
266                           int code,
267                           str reason,
268                           object headers,
269                           object raw_headers,
270                           bint should_close,
271                           object compression,
272                           bint upgrade,
273                           bint chunked):
274    cdef RawResponseMessage ret
275    ret = RawResponseMessage.__new__(RawResponseMessage)
276    ret.version = version
277    ret.code = code
278    ret.reason = reason
279    ret.headers = headers
280    ret.raw_headers = raw_headers
281    ret.should_close = should_close
282    ret.compression = compression
283    ret.upgrade = upgrade
284    ret.chunked = chunked
285    return ret
286
287
288@cython.internal
289cdef class HttpParser:
290
291    cdef:
292        cparser.llhttp_t* _cparser
293        cparser.llhttp_settings_t* _csettings
294
295        bytes _raw_name
296        object _name
297        bytes _raw_value
298        bint      _has_value
299        int _header_name_size
300
301        object _protocol
302        object _loop
303        object _timer
304
305        size_t _max_line_size
306        size_t _max_field_size
307        size_t _max_headers
308        bint _response_with_body
309        bint _read_until_eof
310        bint _lax
311
312        bint    _started
313        object  _url
314        bytearray   _buf
315        str     _path
316        str     _reason
317        list    _headers
318        set     _seen_singletons
319        list    _raw_headers
320        bint    _upgraded
321        list    _messages
322        object  _payload
323        bint    _payload_error
324        object  _payload_exception
325        object  _last_error
326        bint    _auto_decompress
327        int     _limit
328
329        str     _content_encoding
330
331        Py_buffer py_buf
332
333    def __cinit__(self):
334        self._cparser = <cparser.llhttp_t*> \
335                                PyMem_Malloc(sizeof(cparser.llhttp_t))
336        if self._cparser is NULL:
337            raise MemoryError()
338
339        self._csettings = <cparser.llhttp_settings_t*> \
340                                PyMem_Malloc(sizeof(cparser.llhttp_settings_t))
341        if self._csettings is NULL:
342            raise MemoryError()
343
344    def __dealloc__(self):
345        PyMem_Free(self._cparser)
346        PyMem_Free(self._csettings)
347
348    cdef _init(
349        self, cparser.llhttp_type mode,
350        object protocol, object loop, int limit,
351        object timer=None,
352        size_t max_line_size=8190, size_t max_headers=128,
353        size_t max_field_size=8190, payload_exception=None,
354        bint response_with_body=True, bint read_until_eof=False,
355        bint auto_decompress=True,
356    ):
357        cparser.llhttp_settings_init(self._csettings)
358        cparser.llhttp_init(self._cparser, mode, self._csettings)
359        self._cparser.data = <void*>self
360        self._cparser.content_length = 0
361
362        self._protocol = protocol
363        self._loop = loop
364        self._timer = timer
365
366        self._buf = bytearray()
367        self._payload = None
368        self._payload_error = 0
369        self._payload_exception = payload_exception
370        self._messages = []
371
372        self._raw_name = EMPTY_BYTES
373        self._raw_value = EMPTY_BYTES
374        self._has_value = False
375        self._header_name_size = 0
376
377        self._max_line_size = max_line_size
378        self._max_headers = max_headers
379        self._max_field_size = max_field_size
380        self._response_with_body = response_with_body
381        self._read_until_eof = read_until_eof
382        self._upgraded = False
383        self._auto_decompress = auto_decompress
384        self._content_encoding = None
385        self._lax = False
386        self._seen_singletons = set()
387
388        self._csettings.on_url = cb_on_url
389        self._csettings.on_status = cb_on_status
390        self._csettings.on_header_field = cb_on_header_field
391        self._csettings.on_header_value = cb_on_header_value
392        self._csettings.on_headers_complete = cb_on_headers_complete
393        self._csettings.on_body = cb_on_body
394        self._csettings.on_message_begin = cb_on_message_begin
395        self._csettings.on_message_complete = cb_on_message_complete
396        self._csettings.on_chunk_header = cb_on_chunk_header
397        self._csettings.on_chunk_complete = cb_on_chunk_complete
398
399        self._last_error = None
400        self._limit = limit
401
402    cdef _process_header(self):
403        cdef str value
404        if self._raw_name is not EMPTY_BYTES:
405            name = find_header(self._raw_name)
406            value = self._raw_value.decode('utf-8', 'surrogateescape')
407
408            # reject null bytes in header values - matches the Python parser
409            # check at http_parser.py. llhttp in lenient mode doesn't reject
410            # these itself, so we need to catch them here.
411            # ref: RFC 9110 section 5.5 (CTL chars forbidden in field values)
412            if "\x00" in value:
413                raise InvalidHeader(self._raw_value)
414
415            if not self._lax and name in SINGLETON_HEADERS:
416                if name in self._seen_singletons:
417                    raise BadHttpMessage(f"Duplicate '{name}' header found.")
418                self._seen_singletons.add(name)
419            self._headers.append((name, value))
420            if len(self._headers) > self._max_headers:
421                raise BadHttpMessage("Too many headers received")
422
423            if name is CONTENT_ENCODING:
424                self._content_encoding = value
425
426            self._has_value = False
427            self._header_name_size = 0
428            self._raw_headers.append((self._raw_name, self._raw_value))
429            self._raw_name = EMPTY_BYTES
430            self._raw_value = EMPTY_BYTES
431
432    cdef _on_header_field(self, char* at, size_t length):
433        if self._has_value:
434            self._process_header()
435
436        if self._raw_name is EMPTY_BYTES:
437            self._raw_name = at[:length]
438        else:
439            self._raw_name += at[:length]
440
441    cdef _on_header_value(self, char* at, size_t length):
442        if self._raw_value is EMPTY_BYTES:
443            self._raw_value = at[:length]
444        else:
445            self._raw_value += at[:length]
446        self._has_value = True
447
448    cdef _on_headers_complete(self):
449        self._process_header()
450
451        should_close = not cparser.llhttp_should_keep_alive(self._cparser)
452        upgrade = self._cparser.upgrade
453        chunked = self._cparser.flags & cparser.F_CHUNKED
454
455        raw_headers = tuple(self._raw_headers)
456        headers = CIMultiDictProxy(CIMultiDict(self._headers))
457
458        if self._cparser.type == cparser.HTTP_REQUEST:
459            h_upg = headers.get("upgrade", "")
460            allowed = upgrade and h_upg.isascii() and h_upg.lower() in ALLOWED_UPGRADES
461            if allowed or self._cparser.method == cparser.HTTP_CONNECT:
462                self._upgraded = True
463        else:
464            if upgrade and self._cparser.status_code == 101:
465                self._upgraded = True
466
467        # do not support old websocket spec
468        if SEC_WEBSOCKET_KEY1 in headers:
469            raise InvalidHeader(SEC_WEBSOCKET_KEY1)
470
471        encoding = None
472        enc = self._content_encoding
473        if enc is not None:
474            self._content_encoding = None
475            if enc.isascii() and enc.lower() in {"gzip", "deflate", "br", "zstd"}:
476                encoding = enc
477
478        if self._cparser.type == cparser.HTTP_REQUEST:
479            method = http_method_str(self._cparser.method)
480            msg = _new_request_message(
481                method, self._path,
482                self.http_version(), headers, raw_headers,
483                should_close, encoding, upgrade, chunked, self._url)
484        else:
485            msg = _new_response_message(
486                self.http_version(), self._cparser.status_code, self._reason,
487                headers, raw_headers, should_close, encoding,
488                upgrade, chunked)
489
490        if (
491            ULLONG_MAX > self._cparser.content_length > 0 or chunked or
492            self._cparser.method == cparser.HTTP_CONNECT or
493            (self._cparser.status_code >= 199 and
494             self._cparser.content_length == 0 and
495             self._read_until_eof)
496        ):
497            payload = StreamReader(
498                self._protocol, timer=self._timer, loop=self._loop,
499                limit=self._limit)
500        else:
501            payload = EMPTY_PAYLOAD
502
503        self._payload = payload
504        if encoding is not None and self._auto_decompress:
505            self._payload = DeflateBuffer(payload, encoding)
506
507        if not self._response_with_body:
508            payload = EMPTY_PAYLOAD
509
510        self._messages.append((msg, payload))
511
512    cdef _on_message_complete(self):
513        self._payload.feed_eof()
514        self._payload = None
515
516    cdef _on_chunk_header(self):
517        self._payload.begin_http_chunk_receiving()
518
519    cdef _on_chunk_complete(self):
520        self._payload.end_http_chunk_receiving()
521
522    cdef object _on_status_complete(self):
523        pass
524
525    cdef inline http_version(self):
526        cdef cparser.llhttp_t* parser = self._cparser
527
528        if parser.http_major == 1:
529            if parser.http_minor == 0:
530                return HttpVersion10
531            elif parser.http_minor == 1:
532                return HttpVersion11
533
534        return HttpVersion(parser.http_major, parser.http_minor)
535
536    ### Public API ###
537
538    def feed_eof(self):
539        cdef bytes desc
540
541        if self._payload is not None:
542            if self._cparser.flags & cparser.F_CHUNKED:
543                raise TransferEncodingError(
544                    "Not enough data to satisfy transfer length header.")
545            elif self._cparser.flags & cparser.F_CONTENT_LENGTH:
546                raise ContentLengthError(
547                    "Not enough data to satisfy content length header.")
548            elif cparser.llhttp_get_errno(self._cparser) != cparser.HPE_OK:
549                desc = cparser.llhttp_get_error_reason(self._cparser)
550                raise PayloadEncodingError(desc.decode('latin-1'))
551            else:
552                self._payload.feed_eof()
553        elif self._started:
554            self._on_headers_complete()
555            if self._messages:
556                return self._messages[-1][0]
557
558    def feed_data(self, data):
559        cdef:
560            size_t data_len
561            size_t nb
562            char* base
563            cdef cparser.llhttp_errno_t errno
564
565        PyObject_GetBuffer(data, &self.py_buf, PyBUF_SIMPLE)
566        # Cache buffer pointer before PyBuffer_Release to avoid use-after-release.
567        base = <char*>self.py_buf.buf
568        data_len = <size_t>self.py_buf.len
569
570        errno = cparser.llhttp_execute(
571            self._cparser,
572            base,
573            data_len)
574
575        if errno is cparser.HPE_PAUSED_UPGRADE:
576            cparser.llhttp_resume_after_upgrade(self._cparser)
577
578            nb = cparser.llhttp_get_error_pos(self._cparser) - base
579
580        PyBuffer_Release(&self.py_buf)
581
582        if errno not in (cparser.HPE_OK, cparser.HPE_PAUSED_UPGRADE):
583            if self._payload_error == 0:
584                if self._last_error is not None:
585                    ex = self._last_error
586                    self._last_error = None
587                else:
588                    after = cparser.llhttp_get_error_pos(self._cparser)
589                    before = data[:after - base]
590                    after_b = after.split(b"\r\n", 1)[0]
591                    before = before.rsplit(b"\r\n", 1)[-1]
592                    data = before + after_b
593                    pointer = " " * (len(repr(before))-1) + "^"
594                    ex = parser_error_from_errno(self._cparser, data, pointer)
595                self._payload = None
596                raise ex
597
598        if self._messages:
599            messages = self._messages
600            self._messages = []
601        else:
602            messages = ()
603
604        if self._upgraded:
605            return messages, True, data[nb:]
606        else:
607            return messages, False, b""
608
609    def set_upgraded(self, val):
610        self._upgraded = val
611
612
613cdef class HttpRequestParser(HttpParser):
614
615    def __init__(
616        self, protocol, loop, int limit, timer=None,
617        size_t max_line_size=8190, size_t max_headers=128,
618        size_t max_field_size=8190, payload_exception=None,
619        bint response_with_body=True, bint read_until_eof=False,
620        bint auto_decompress=True,
621    ):
622        self._init(cparser.HTTP_REQUEST, protocol, loop, limit, timer,
623                   max_line_size, max_headers, max_field_size,
624                   payload_exception, response_with_body, read_until_eof,
625                   auto_decompress)
626
627    cdef object _on_status_complete(self):
628        cdef int idx1, idx2
629        if not self._buf:
630            return
631        self._path = self._buf.decode('utf-8', 'surrogateescape')
632        try:
633            idx3 = len(self._path)
634            if self._cparser.method == cparser.HTTP_CONNECT:
635                # authority-form,
636                # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3
637                self._url = URL.build(authority=self._path, encoded=True)
638            elif idx3 > 1 and self._path[0] == '/':
639                # origin-form,
640                # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1
641                idx1 = self._path.find("?")
642                if idx1 == -1:
643                    query = ""
644                    idx2 = self._path.find("#")
645                    if idx2 == -1:
646                        path = self._path
647                        fragment = ""
648                    else:
649                        path = self._path[0: idx2]
650                        fragment = self._path[idx2+1:]
651
652                else:
653                    path = self._path[0:idx1]
654                    idx1 += 1
655                    idx2 = self._path.find("#", idx1+1)
656                    if idx2 == -1:
657                        query = self._path[idx1:]
658                        fragment = ""
659                    else:
660                        query = self._path[idx1: idx2]
661                        fragment = self._path[idx2+1:]
662
663                self._url = URL.build(
664                    path=path,
665                    query_string=query,
666                    fragment=fragment,
667                    encoded=True,
668                )
669            else:
670                # absolute-form for proxy maybe,
671                # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.2
672                self._url = URL(self._path, encoded=True)
673        finally:
674            PyByteArray_Resize(self._buf, 0)
675
676
677cdef class HttpResponseParser(HttpParser):
678
679    def __init__(
680        self, protocol, loop, int limit, timer=None,
681            size_t max_line_size=8190, size_t max_headers=128,
682            size_t max_field_size=8190, payload_exception=None,
683            bint response_with_body=True, bint read_until_eof=False,
684            bint auto_decompress=True
685    ):
686        self._init(cparser.HTTP_RESPONSE, protocol, loop, limit, timer,
687                   max_line_size, max_headers, max_field_size,
688                   payload_exception, response_with_body, read_until_eof,
689                   auto_decompress)
690        # Use strict parsing on dev mode, so users are warned about broken servers.
691        if not DEBUG:
692            cparser.llhttp_set_lenient_headers(self._cparser, 1)
693            cparser.llhttp_set_lenient_optional_cr_before_lf(self._cparser, 1)
694            cparser.llhttp_set_lenient_spaces_after_chunk_size(self._cparser, 1)
695            self._lax = True
696
697    cdef object _on_status_complete(self):
698        if self._buf:
699            self._reason = self._buf.decode('utf-8', 'surrogateescape')
700            PyByteArray_Resize(self._buf, 0)
701        else:
702            self._reason = self._reason or ''
703
704cdef int cb_on_message_begin(cparser.llhttp_t* parser) except -1:
705    cdef HttpParser pyparser = <HttpParser>parser.data
706
707    pyparser._started = True
708    pyparser._headers = []
709    pyparser._seen_singletons = set()
710    pyparser._raw_headers = []
711    PyByteArray_Resize(pyparser._buf, 0)
712    pyparser._path = None
713    pyparser._reason = None
714    return 0
715
716
717cdef int cb_on_url(cparser.llhttp_t* parser,
718                   const char *at, size_t length) except -1:
719    cdef HttpParser pyparser = <HttpParser>parser.data
720    try:
721        if length > pyparser._max_line_size:
722            status = pyparser._buf + at[:length]
723            raise LineTooLong(status[:100] + b"...", pyparser._max_line_size)
724        extend(pyparser._buf, at, length)
725    except BaseException as ex:
726        pyparser._last_error = ex
727        return -1
728    else:
729        return 0
730
731
732cdef int cb_on_status(cparser.llhttp_t* parser,
733                      const char *at, size_t length) except -1:
734    cdef HttpParser pyparser = <HttpParser>parser.data
735    try:
736        if length > pyparser._max_line_size:
737            reason = pyparser._buf + at[:length]
738            raise LineTooLong(reason[:100] + b"...", pyparser._max_line_size)
739        extend(pyparser._buf, at, length)
740    except BaseException as ex:
741        pyparser._last_error = ex
742        return -1
743    else:
744        return 0
745
746
747cdef int cb_on_header_field(cparser.llhttp_t* parser,
748                            const char *at, size_t length) except -1:
749    cdef HttpParser pyparser = <HttpParser>parser.data
750    cdef Py_ssize_t size
751    try:
752        pyparser._on_status_complete()
753        size = len(pyparser._raw_name) + length
754        if size > pyparser._max_field_size:
755            name = pyparser._raw_name + at[:length]
756            raise LineTooLong(name[:100] + b"...", pyparser._max_field_size)
757        pyparser._header_name_size = size
758        pyparser._on_header_field(at, length)
759    except BaseException as ex:
760        pyparser._last_error = ex
761        return -1
762    else:
763        return 0
764
765
766cdef int cb_on_header_value(cparser.llhttp_t* parser,
767                            const char *at, size_t length) except -1:
768    cdef HttpParser pyparser = <HttpParser>parser.data
769    cdef Py_ssize_t size
770    try:
771        size = len(pyparser._raw_value) + length
772        if pyparser._header_name_size + size > pyparser._max_field_size:
773            value = pyparser._raw_value + at[:length]
774            raise LineTooLong(value[:100] + b"...", pyparser._max_field_size)
775        pyparser._on_header_value(at, length)
776    except BaseException as ex:
777        pyparser._last_error = ex
778        return -1
779    else:
780        return 0
781
782
783cdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1:
784    cdef HttpParser pyparser = <HttpParser>parser.data
785    try:
786        pyparser._on_status_complete()
787        pyparser._on_headers_complete()
788    except BaseException as exc:
789        pyparser._last_error = exc
790        return -1
791    else:
792        if pyparser._upgraded or pyparser._cparser.method == cparser.HTTP_CONNECT:
793            return 2
794        else:
795            return 0
796
797
798cdef int cb_on_body(cparser.llhttp_t* parser,
799                    const char *at, size_t length) except -1:
800    cdef HttpParser pyparser = <HttpParser>parser.data
801    cdef bytes body = at[:length]
802    try:
803        pyparser._payload.feed_data(body, length)
804    except BaseException as underlying_exc:
805        reraised_exc = underlying_exc
806        if pyparser._payload_exception is not None:
807            reraised_exc = pyparser._payload_exception(str(underlying_exc))
808
809        set_exception(pyparser._payload, reraised_exc, underlying_exc)
810
811        pyparser._payload_error = 1
812        return -1
813    else:
814        return 0
815
816
817cdef int cb_on_message_complete(cparser.llhttp_t* parser) except -1:
818    cdef HttpParser pyparser = <HttpParser>parser.data
819    try:
820        pyparser._started = False
821        pyparser._on_message_complete()
822    except BaseException as exc:
823        pyparser._last_error = exc
824        return -1
825    else:
826        return 0
827
828
829cdef int cb_on_chunk_header(cparser.llhttp_t* parser) except -1:
830    cdef HttpParser pyparser = <HttpParser>parser.data
831    try:
832        pyparser._on_chunk_header()
833    except BaseException as exc:
834        pyparser._last_error = exc
835        return -1
836    else:
837        return 0
838
839
840cdef int cb_on_chunk_complete(cparser.llhttp_t* parser) except -1:
841    cdef HttpParser pyparser = <HttpParser>parser.data
842    try:
843        pyparser._on_chunk_complete()
844    except BaseException as exc:
845        pyparser._last_error = exc
846        return -1
847    else:
848        return 0
849
850
851cdef parser_error_from_errno(cparser.llhttp_t* parser, data, pointer):
852    cdef cparser.llhttp_errno_t errno = cparser.llhttp_get_errno(parser)
853    cdef bytes desc = cparser.llhttp_get_error_reason(parser)
854
855    err_msg = "{}:\n\n  {!r}\n  {}".format(desc.decode("latin-1"), data, pointer)
856
857    if errno in {cparser.HPE_CB_MESSAGE_BEGIN,
858                 cparser.HPE_CB_HEADERS_COMPLETE,
859                 cparser.HPE_CB_MESSAGE_COMPLETE,
860                 cparser.HPE_CB_CHUNK_HEADER,
861                 cparser.HPE_CB_CHUNK_COMPLETE,
862                 cparser.HPE_INVALID_CONSTANT,
863                 cparser.HPE_INVALID_HEADER_TOKEN,
864                 cparser.HPE_INVALID_CONTENT_LENGTH,
865                 cparser.HPE_INVALID_CHUNK_SIZE,
866                 cparser.HPE_INVALID_EOF_STATE,
867                 cparser.HPE_INVALID_TRANSFER_ENCODING}:
868        return BadHttpMessage(err_msg)
869    elif errno == cparser.HPE_INVALID_METHOD:
870        return BadHttpMethod(error=err_msg)
871    elif errno in {cparser.HPE_INVALID_STATUS,
872                   cparser.HPE_INVALID_VERSION}:
873        return BadStatusLine(error=err_msg)
874    elif errno == cparser.HPE_INVALID_URL:
875        return InvalidURLError(err_msg)
876
877    return BadHttpMessage(err_msg)
878 
codekingpro/portable-devtools · Team Ai