Skip to content

Commit f01cb5e

Browse files
authored
Ignore empty parts when parsing Content-Disposition header (#11243)
1 parent 55e4300 commit f01cb5e

File tree

3 files changed

+35
-0
lines changed

3 files changed

+35
-0
lines changed

CHANGES/11243.bugfix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Updated `Content-Disposition` header parsing to handle trailing semicolons and empty parts
2+
-- by :user:`PLPeeters`.

aiohttp/multipart.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ def unescape(text: str, *, chars: str = "".join(map(re.escape, CHAR))) -> str:
114114
while parts:
115115
item = parts.pop(0)
116116

117+
if not item: # To handle trailing semicolons
118+
warnings.warn(BadContentDispositionHeader(header))
119+
continue
120+
117121
if "=" not in item:
118122
warnings.warn(BadContentDispositionHeader(header))
119123
return None, {}

tests/test_client_response.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from aiohttp.client_reqrep import ClientResponse, RequestInfo
1818
from aiohttp.connector import Connection
1919
from aiohttp.helpers import TimerNoop
20+
from aiohttp.multipart import BadContentDispositionHeader
2021

2122

2223
class WriterMock(mock.AsyncMock):
@@ -996,6 +997,34 @@ def test_content_disposition_no_parameters() -> None:
996997
assert {} == response.content_disposition.parameters
997998

998999

1000+
@pytest.mark.parametrize(
1001+
"content_disposition",
1002+
(
1003+
'attachment; filename="archive.tar.gz";',
1004+
'attachment;; filename="archive.tar.gz"',
1005+
),
1006+
)
1007+
def test_content_disposition_empty_parts(content_disposition: str) -> None:
1008+
response = ClientResponse(
1009+
"get",
1010+
URL("http://def-cl-resp.org"),
1011+
request_info=mock.Mock(),
1012+
writer=WriterMock(),
1013+
continue100=None,
1014+
timer=TimerNoop(),
1015+
traces=[],
1016+
loop=mock.Mock(),
1017+
session=mock.Mock(),
1018+
)
1019+
h = {"Content-Disposition": content_disposition}
1020+
response._headers = CIMultiDictProxy(CIMultiDict(h))
1021+
1022+
with pytest.warns(BadContentDispositionHeader):
1023+
assert response.content_disposition is not None
1024+
assert "attachment" == response.content_disposition.type
1025+
assert "archive.tar.gz" == response.content_disposition.filename
1026+
1027+
9991028
def test_content_disposition_no_header() -> None:
10001029
response = ClientResponse(
10011030
"get",

0 commit comments

Comments
 (0)