Skip to content

Commit

Permalink
Backport PRs 2979 2980 (#2981)
Browse files Browse the repository at this point in the history
* Add workflow_dispatch release

* RTD build fix

* RTD build fix (another)

* RTD build fix (yet another)

* Update Crowdin configuration file

* Fix Docker publish (#2887)

* Fix Docker publish

* Remove workflow dispatch

The actions uses data from the release object itself, so workflow dispatch doesn't work anyway

* More fixes

* Backport PRs 2979 2980

* Cleanup tests

---------

Co-authored-by: Néstor Pérez <[email protected]>
  • Loading branch information
ahopkins and prryplatypus authored Jun 30, 2024
1 parent 234e3fb commit 9f98bec
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 4 deletions.
22 changes: 18 additions & 4 deletions sanic/http/http1.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ async def http1_request_header(self): # no cov
name, value = h = name.lower(), value.lstrip()

if name in ("content-length", "transfer-encoding"):
if request_body:
raise ValueError(
"Duplicate Content-Length or Transfer-Encoding"
)
request_body = True
elif name == "connection":
self.keep_alive = value.lower() == "keep-alive"
Expand Down Expand Up @@ -269,9 +273,12 @@ async def http1_request_header(self): # no cov
pos -= 2 # One CRLF stays in buffer
else:
self.request_body = True
self.request_bytes_left = self.request_bytes = int(
headers["content-length"]
)
try:
self.request_bytes_left = self.request_bytes = (
self._safe_int(headers["content-length"])
)
except Exception:
raise BadRequest("Bad content-length")

# Remove header and its trailing CRLF
del buf[: pos + 4]
Expand Down Expand Up @@ -506,7 +513,8 @@ async def read(self) -> Optional[bytes]: # no cov
await self._receive_more()

try:
size = int(buf[2:pos].split(b";", 1)[0].decode(), 16)
raw = buf[2:pos].split(b";", 1)[0].decode()
size = self._safe_int(raw, 16)
except Exception:
self.keep_alive = False
raise BadRequest("Bad chunked encoding")
Expand Down Expand Up @@ -592,3 +600,9 @@ def set_header_max_size(cls, *sizes: int):
*sizes,
cls.HEADER_CEILING,
)

@staticmethod
def _safe_int(value: str, base: int = 10) -> int:
if "-" in value or "+" in value or "_" in value:
raise ValueError
return int(value, base)
79 changes: 79 additions & 0 deletions tests/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,82 @@ def test_url_encoding(client):

assert b"400 Bad Request" in headers
assert b"URL may only contain US-ASCII characters." in body


@pytest.mark.parametrize(
"content_length",
(
b"-50",
b"+50",
b"5_0",
b"50.5",
),
)
def test_invalid_content_length(content_length, client):
body = b"Hello" * 10
client.send(
b"POST /upload HTTP/1.1\r\n"
+ b"content-length: "
+ content_length
+ b"\r\n\r\n"
+ body
+ b"\r\n\r\n"
)

response = client.recv()
headers, body = response.rsplit(b"\r\n\r\n", 1)

assert b"400 Bad Request" in headers
assert b"Bad content-length" in body


@pytest.mark.parametrize(
"chunk_length",
(
b"-50",
b"+50",
b"5_0",
b"50.5",
),
)
def test_invalid_chunk_length(chunk_length, client):
body = b"Hello" * 10
client.send(
b"POST /upload HTTP/1.1\r\n"
+ b"transfer-encoding: chunked\r\n\r\n"
+ chunk_length
+ b"\r\n"
+ body
+ b"\r\n"
+ b"0\r\n\r\n"
)

response = client.recv()
headers, body = response.rsplit(b"\r\n\r\n", 1)

assert b"400 Bad Request" in headers
assert b"Bad chunked encoding" in body


def test_smuggle(client):
client.send(
"""
POST /upload HTTP/1.1
Content-Length: 5
Transfer-Encoding: chunked
Transfer-Encoding: xchunked
5
hello
0
GET / HTTP/1.1
""" # noqa
)

response = client.recv()
num_responses = response.count(b"HTTP/1.1")
assert num_responses == 1

headers, body = response.rsplit(b"\r\n\r\n", 1)
assert b"400 Bad Request" in headers
assert b"Bad Request" in body

0 comments on commit 9f98bec

Please sign in to comment.