codekingpro/portable-devtools
115k
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 