Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump bundled llhttp to 9.2.1 #113

Merged
merged 1 commit into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions httptools/parser/cparser.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,14 @@ cdef extern from "llhttp.h":
const char* llhttp_method_name(llhttp_method_t method)

void llhttp_set_error_reason(llhttp_t* parser, const char* reason);

void llhttp_set_lenient_headers(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_chunked_length(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_keep_alive(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_transfer_encoding(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_version(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_data_after_close(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_optional_lf_after_cr(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_optional_crlf_after_chunk(llhttp_t* parser, bint enabled);
void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, bint enabled);
49 changes: 48 additions & 1 deletion httptools/parser/parser.pyx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#cython: language_level=3

from __future__ import print_function
from typing import Optional

from cpython.mem cimport PyMem_Malloc, PyMem_Free
from cpython cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, \
Py_buffer, PyBytes_AsString
Expand Down Expand Up @@ -144,6 +146,51 @@ cdef class HttpParser:

### Public API ###

def set_dangerous_leniencies(
self,
lenient_headers: Optional[bool] = None,
lenient_chunked_length: Optional[bool] = None,
lenient_keep_alive: Optional[bool] = None,
lenient_transfer_encoding: Optional[bool] = None,
lenient_version: Optional[bool] = None,
lenient_data_after_close: Optional[bool] = None,
lenient_optional_lf_after_cr: Optional[bool] = None,
lenient_optional_cr_before_lf: Optional[bool] = None,
lenient_optional_crlf_after_chunk: Optional[bool] = None,
lenient_spaces_after_chunk_size: Optional[bool] = None,
):
cdef cparser.llhttp_t* parser = self._cparser
if lenient_headers is not None:
cparser.llhttp_set_lenient_headers(
parser, lenient_headers)
if lenient_chunked_length is not None:
cparser.llhttp_set_lenient_chunked_length(
parser, lenient_chunked_length)
if lenient_keep_alive is not None:
cparser.llhttp_set_lenient_keep_alive(
parser, lenient_keep_alive)
if lenient_transfer_encoding is not None:
cparser.llhttp_set_lenient_transfer_encoding(
parser, lenient_transfer_encoding)
if lenient_version is not None:
cparser.llhttp_set_lenient_version(
parser, lenient_version)
if lenient_data_after_close is not None:
cparser.llhttp_set_lenient_data_after_close(
parser, lenient_data_after_close)
if lenient_optional_lf_after_cr is not None:
cparser.llhttp_set_lenient_optional_lf_after_cr(
parser, lenient_optional_lf_after_cr)
if lenient_optional_cr_before_lf is not None:
cparser.llhttp_set_lenient_optional_cr_before_lf(
parser, lenient_optional_cr_before_lf)
if lenient_optional_crlf_after_chunk is not None:
cparser.llhttp_set_lenient_optional_crlf_after_chunk(
parser, lenient_optional_crlf_after_chunk)
if lenient_spaces_after_chunk_size is not None:
cparser.llhttp_set_lenient_spaces_after_chunk_size(
parser, lenient_spaces_after_chunk_size)

def get_http_version(self):
cdef cparser.llhttp_t* parser = self._cparser
return '{}.{}'.format(parser.http_major, parser.http_minor)
Expand All @@ -161,7 +208,7 @@ cdef class HttpParser:
cparser.llhttp_errno_t err
Py_buffer *buf
bint owning_buf = False
char* err_pos
const char* err_pos

if PyMemoryView_Check(data):
buf = PyMemoryView_GET_BUFFER(data)
Expand Down
61 changes: 60 additions & 1 deletion tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@

RESPONSE1_HEAD = b'''HTTP/1.1 200 OK
Date: Mon, 23 May 2005 22:38:34 GMT
Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)
Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
ETag: "3f80f-1b6-3e1cb03b"
Content-Type: text/html; charset=UTF-8
Content-Length: 130
Accept-Ranges: bytes
Connection: close

'''.replace(b'\n', b'\r\n')

RESPONSE1_SPACES_IN_HEAD = b'''HTTP/1.1 200 OK
Date: Mon, 23 May 2005 22:38:34 GMT
Server: Apache/1.3.3.7
(Unix) (Red-Hat/Linux)
Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
Expand Down Expand Up @@ -89,7 +101,7 @@ def test_parser_response_1(self):
self.assertEqual(len(headers), 8)
self.assertEqual(headers.get(b'Connection'), b'close')
self.assertEqual(headers.get(b'Content-Type'),
b'text/html; charset=UTF-8')
b'text/html; charset=UTF-8')

self.assertFalse(m.on_body.called)
p.feed_data(bytearray(RESPONSE1_BODY))
Expand All @@ -109,6 +121,53 @@ def test_parser_response_1b(self):
'Expected HTTP/'):
p.feed_data(b'12123123')

def test_parser_response_leninent_headers_1(self):
m = mock.Mock()

headers = {}
m.on_header.side_effect = headers.__setitem__

p = httptools.HttpResponseParser(m)

with self.assertRaisesRegex(
httptools.HttpParserError,
"whitespace after header value",
):
p.feed_data(memoryview(RESPONSE1_SPACES_IN_HEAD))

def test_parser_response_leninent_headers_2(self):
m = mock.Mock()

headers = {}
m.on_header.side_effect = headers.__setitem__

p = httptools.HttpResponseParser(m)

p.set_dangerous_leniencies(lenient_headers=True)
p.feed_data(memoryview(RESPONSE1_SPACES_IN_HEAD))

self.assertEqual(p.get_http_version(), '1.1')
self.assertEqual(p.get_status_code(), 200)

m.on_status.assert_called_once_with(b'OK')

m.on_headers_complete.assert_called_once_with()
self.assertEqual(m.on_header.call_count, 8)
self.assertEqual(len(headers), 8)
self.assertEqual(headers.get(b'Connection'), b'close')
self.assertEqual(headers.get(b'Content-Type'),
b'text/html; charset=UTF-8')

self.assertFalse(m.on_body.called)
p.feed_data(bytearray(RESPONSE1_BODY))
m.on_body.assert_called_once_with(RESPONSE1_BODY)

m.on_message_complete.assert_called_once_with()

self.assertFalse(m.on_url.called)
self.assertFalse(m.on_chunk_header.called)
self.assertFalse(m.on_chunk_complete.called)

def test_parser_response_2(self):
with self.assertRaisesRegex(TypeError, 'a bytes-like object'):
httptools.HttpResponseParser(None).feed_data('')
Expand Down
2 changes: 1 addition & 1 deletion vendor/llhttp
Submodule llhttp updated 6 files
+8 −6 CMakeLists.txt
+96 −29 README.md
+101 −13 include/llhttp.h
+49 −1 src/api.c
+23 −3 src/http.c
+1,530 −10,044 src/llhttp.c
Loading