Skip to content

Commit e876a0f

Browse files
authored
Merge branch 'master' into win_arm64
2 parents 80b4db1 + cf10ce6 commit e876a0f

13 files changed

Lines changed: 141 additions & 65 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ jobs:
7878
- "cp312"
7979
- "cp313"
8080
- "cp314"
81+
- "cp314t"
8182
exclude:
8283
- os: ubuntu-latest
8384
cibw_arch: universal2

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ else
1212
endif
1313

1414
compile:
15-
$(PIP) install -e .
15+
$(PIP) install -e . --group dev
1616

1717
test: compile
1818
$(PYTHON) -m unittest -v
1919

20+
typecheck: compile
21+
$(PYTHON) -m pyright
22+
2023
clean:
2124
find $(ROOT)/httptools/parser -name '*.c' | xargs rm -f
2225
find $(ROOT)/httptools/parser -name '*.so' | xargs rm -f

httptools/__init__.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,35 @@
11
from . import parser
2-
from .parser import * # NOQA
2+
from .parser import (
3+
HTTPProtocol,
4+
HttpRequestParser,
5+
HttpResponseParser,
6+
HttpParserError,
7+
HttpParserCallbackError,
8+
HttpParserInvalidStatusError,
9+
HttpParserInvalidMethodError,
10+
HttpParserInvalidURLError,
11+
HttpParserUpgrade,
12+
parse_url,
13+
)
314

4-
from ._version import __version__ # NOQA
15+
from ._version import __version__
516

6-
__all__ = parser.__all__ + ('__version__',) # NOQA
17+
__all__ = (
18+
"parser",
19+
# protocol
20+
"HTTPProtocol",
21+
# parser
22+
"HttpRequestParser",
23+
"HttpResponseParser",
24+
# errors
25+
"HttpParserError",
26+
"HttpParserCallbackError",
27+
"HttpParserInvalidStatusError",
28+
"HttpParserInvalidMethodError",
29+
"HttpParserInvalidURLError",
30+
"HttpParserUpgrade",
31+
# url parser
32+
"parse_url",
33+
# version
34+
"__version__",
35+
)

httptools/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@
1010
# supported platforms, publish the packages on PyPI, merge the PR
1111
# to the target branch, create a Git tag pointing to the commit.
1212

13-
__version__ = '0.8.0.dev0'
13+
__version__ = '0.8.0'

httptools/parser/__init__.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
11
from .protocol import HTTPProtocol
2-
from .parser import * # NoQA
3-
from .errors import * # NoQA
4-
from .url_parser import * # NoQA
2+
from .parser import HttpRequestParser, HttpResponseParser # NoQA
3+
from .errors import (
4+
HttpParserError,
5+
HttpParserCallbackError,
6+
HttpParserInvalidStatusError,
7+
HttpParserInvalidMethodError,
8+
HttpParserInvalidURLError,
9+
HttpParserUpgrade,
10+
)
11+
from .url_parser import parse_url
512

6-
__all__ = parser.__all__ + errors.__all__ + url_parser.__all__ # NoQA
13+
__all__ = (
14+
# protocol
15+
"HTTPProtocol",
16+
# parser
17+
"HttpRequestParser",
18+
"HttpResponseParser",
19+
# errors
20+
"HttpParserError",
21+
"HttpParserCallbackError",
22+
"HttpParserInvalidStatusError",
23+
"HttpParserInvalidMethodError",
24+
"HttpParserInvalidURLError",
25+
"HttpParserUpgrade",
26+
# url_parser
27+
"parse_url",
28+
)

httptools/parser/parser.pyi

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,58 @@
1-
from typing import Union, Any
21
from array import array
32
from .protocol import HTTPProtocol
43

54
class HttpParser:
6-
def __init__(self, protocol: Union[HTTPProtocol, Any]) -> None:
7-
"""
8-
protocol -- a Python object with the following methods
9-
(all optional):
10-
11-
- on_message_begin()
12-
- on_url(url: bytes)
13-
- on_header(name: bytes, value: bytes)
14-
- on_headers_complete()
15-
- on_body(body: bytes)
16-
- on_message_complete()
17-
- on_chunk_header()
18-
- on_chunk_complete()
19-
- on_status(status: bytes)
5+
def __init__(self, protocol: HTTPProtocol | object) -> None:
6+
"""The HTTP parser.
7+
8+
Args:
9+
protocol (HTTPProtocol): Callback interface for the parser.
2010
"""
2111

12+
def set_dangerous_leniencies(
13+
self,
14+
lenient_headers: bool | None = None,
15+
lenient_chunked_length: bool | None = None,
16+
lenient_keep_alive: bool | None = None,
17+
lenient_transfer_encoding: bool | None = None,
18+
lenient_version: bool | None = None,
19+
lenient_data_after_close: bool | None = None,
20+
lenient_optional_lf_after_cr: bool | None = None,
21+
lenient_optional_cr_before_lf: bool | None = None,
22+
lenient_optional_crlf_after_chunk: bool | None = None,
23+
lenient_spaces_after_chunk_size: bool | None = None,
24+
) -> None:
25+
"""Set dangerous leniencies for the parser."""
26+
2227
def get_http_version(self) -> str:
23-
"""Return an HTTP protocol version."""
24-
...
28+
"""Retrieve the HTTP protocol version e.g. "1.1"."""
2529

2630
def should_keep_alive(self) -> bool:
27-
"""Return ``True`` if keep-alive mode is preferred."""
28-
...
31+
"""Return `True` if keep-alive mode is preferred."""
2932

3033
def should_upgrade(self) -> bool:
31-
"""Return ``True`` if the parsed request is a valid Upgrade request.
34+
"""Return `True` if the parsed request is a valid Upgrade request.
3235
The method exposes a flag set just before on_headers_complete.
3336
Calling this method earlier will only yield `False`."""
34-
...
3537

36-
def feed_data(self, data: Union[bytes, bytearray, memoryview, array]) -> None:
38+
def feed_data(self, data: bytes | bytearray | memoryview | array[int]) -> None:
3739
"""Feed data to the parser.
3840
39-
Will eventually trigger callbacks on the ``protocol``
40-
object.
41+
Will eventually trigger callbacks on the ``protocol`` object.
4142
4243
On HTTP upgrade, this method will raise an
4344
``HttpParserUpgrade`` exception, with its sole argument
4445
set to the offset of the non-HTTP data in ``data``.
4546
"""
4647

4748
class HttpRequestParser(HttpParser):
48-
"""Used for parsing http requests from the server's side"""
49+
"""Used for parsing http requests from the server side."""
4950

5051
def get_method(self) -> bytes:
51-
"""Return HTTP request method (GET, HEAD, etc)"""
52+
"""Retrieve the HTTP method of the request."""
5253

5354
class HttpResponseParser(HttpParser):
54-
"""Used for parsing http requests from the client's side"""
55+
"""Used for parsing http responses from the client side."""
5556

5657
def get_status_code(self) -> int:
57-
"""Return the status code of the HTTP response"""
58+
"""Retrieve the status code of the HTTP response."""

httptools/parser/protocol.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
class HTTPProtocol(Protocol):
55
"""Used for providing static type-checking when parsing through the http protocol"""
66

7-
def on_message_begin() -> None: ...
8-
def on_url(url: bytes) -> None: ...
9-
def on_header(name: bytes, value: bytes) -> None: ...
10-
def on_headers_complete() -> None: ...
11-
def on_body(body: bytes) -> None: ...
12-
def on_message_complete() -> None: ...
13-
def on_chunk_header() -> None: ...
14-
def on_chunk_complete() -> None: ...
15-
def on_status(status: bytes) -> None: ...
7+
def on_message_begin(self) -> None: ...
8+
def on_url(self, url: bytes) -> None: ...
9+
def on_header(self, name: bytes, value: bytes) -> None: ...
10+
def on_headers_complete(self) -> None: ...
11+
def on_body(self, body: bytes) -> None: ...
12+
def on_message_complete(self) -> None: ...
13+
def on_chunk_header(self) -> None: ...
14+
def on_chunk_complete(self) -> None: ...
15+
def on_status(self, status: bytes) -> None: ...

httptools/parser/url_parser.pyi

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from typing import Union
21
from array import array
32

43
class URL:
@@ -10,18 +9,5 @@ class URL:
109
fragment: bytes
1110
userinfo: bytes
1211

13-
def parse_url(url: Union[bytes, bytearray, memoryview, array]) -> URL:
14-
"""Parse URL strings into a structured Python object.
15-
16-
Returns an instance of ``httptools.URL`` class with the
17-
following attributes:
18-
19-
- schema: bytes
20-
- host: bytes
21-
- port: int
22-
- path: bytes
23-
- query: bytes
24-
- fragment: bytes
25-
- userinfo: bytes
26-
"""
27-
...
12+
def parse_url(url: bytes | bytearray | memoryview | array[int]) -> URL:
13+
"""Parse a URL string into a structured Python object."""

httptools/parser/url_parser.pyx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ from . cimport url_cparser as uparser
1212

1313
__all__ = ('parse_url',)
1414

15+
DEF MAX_URL_LENGTH = (1 << 16) - 1
16+
1517
@cython.freelist(250)
1618
cdef class URL:
1719
cdef readonly bytes schema
@@ -63,6 +65,14 @@ def parse_url(url):
6365

6466
PyObject_GetBuffer(url, &py_buf, PyBUF_SIMPLE)
6567
try:
68+
if py_buf.len > MAX_URL_LENGTH:
69+
# http_parser stores URL field offsets/lengths as uint16_t,
70+
# so URLs longer than this will cause silent truncation.
71+
# See https://github.com/MagicStack/httptools/issues/142
72+
raise HttpParserInvalidURLError(
73+
"url is too long: url length of {} bytes exceeds the "
74+
"maximum of {} bytes".format(py_buf.len, MAX_URL_LENGTH))
75+
6676
buf_data = <char*>py_buf.buf
6777
res = uparser.http_parser_parse_url(buf_data, py_buf.len, 0, parsed)
6878

httptools/py.typed

Whitespace-only changes.

0 commit comments

Comments
 (0)