Skip to content
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
18 changes: 18 additions & 0 deletions .github/workflows/agent-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: AgentScan

on:
pull_request_target:
types:
- opened
- reopened

jobs:
agentscan:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- name: AgentScan
uses: MatteoGabriele/agentscan-action@v2.5.0
with:
mode: labels
1 change: 1 addition & 0 deletions CHANGES/13462.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed multipart not returning 415 when decoding failed -- by :user:`dxbjavid`.
1 change: 1 addition & 0 deletions CHANGES/13580.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed ``BaseRequest.http_range`` not accepting case-insensitive range units -- by :user:`Manny7717`.
1 change: 1 addition & 0 deletions CHANGES/13581.bugfix.rst
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ Lukasz Marcin Dobrzanski
Lukshya Supyal
L茅n谩rd Szolnoki
Makc Belousow
Manny7717
Manuel Miranda
Marat Sharafutdinov
Marc Mueller
Expand Down
9 changes: 7 additions & 2 deletions aiohttp/web_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,8 @@ def http_range(self) -> "slice[int, int, int]":
if rng is not None:
try:
pattern = r"^bytes=(\d*)-(\d*)$"
start, end = re.findall(pattern, rng, re.ASCII)[0]
# https://www.rfc-editor.org/info/rfc9110/#section-14.1-4
start, end = re.findall(pattern, rng, re.ASCII | re.IGNORECASE)[0]
except IndexError: # pattern was not found in header
raise ValueError("range not in acceptable format")

Expand Down Expand Up @@ -847,7 +848,11 @@ async def post(self) -> "MultiDictProxy[str | bytes | FileField]":

if field_ct is None or field_ct.startswith("text/"):
charset = field.get_charset(default="utf-8")
out.add(field.name, value.decode(charset))
try:
decoded = value.decode(charset)
except (LookupError, UnicodeDecodeError):
raise HTTPUnsupportedMediaType()
out.add(field.name, decoded)
else:
out.add(field.name, value) # type: ignore[arg-type]
else:
Expand Down
39 changes: 39 additions & 0 deletions tests/test_web_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,15 @@ def test_range_non_ascii() -> None:
req.http_range


def test_range_to_slice_uppercase_unit() -> None:
# https://www.rfc-editor.org/info/rfc9110/#section-14.1-4
req = make_mocked_request(
"GET", "/", headers=CIMultiDict([("RANGE", "Bytes=0-499")])
)
assert isinstance(req.http_range, slice)
assert req.http_range.start == 0 and req.http_range.stop == 500


def test_non_keepalive_on_http10() -> None:
req = make_mocked_request("GET", "/", version=HttpVersion(1, 0))
assert not req.keep_alive
Expand Down Expand Up @@ -1045,6 +1054,36 @@ async def test_multipart_formdata(protocol: BaseProtocol) -> None:
assert dict(result) == {"a": "b", "c": "d"}


@pytest.mark.parametrize(
("part_charset", "part_body"),
(
("not-a-real-codec", b"hello"),
("utf-8", b"\xff\xfe"),
),
)
async def test_multipart_formdata_field_bad_charset(
protocol: BaseProtocol, part_charset: str, part_body: bytes
) -> None:
payload = StreamReader(protocol, 2**16, loop=asyncio.get_running_loop())
payload.feed_data(
b"-----------------------------326931944431359\r\n"
b'Content-Disposition: form-data; name="a"\r\n'
b"Content-Type: text/plain; charset=" + part_charset.encode() + b"\r\n"
b"\r\n" + part_body + b"\r\n"
b"-----------------------------326931944431359--\r\n"
)
content_type = (
"multipart/form-data; boundary=---------------------------326931944431359"
)
payload.feed_eof()
req = make_mocked_request(
"POST", "/", headers={"CONTENT-TYPE": content_type}, payload=payload
)
with pytest.raises(web.HTTPUnsupportedMediaType) as err:
await req.post()
assert err.value.status_code == 415


async def test_urlencoded_form_with_invalid_default_encoding(
protocol: BaseProtocol,
) -> None:
Expand Down
Loading