_http_parser.pyx 29 KB

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