From 259e45884794b7889fc364327d3862b8f9bc7ade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=2E=20K=C3=A4rkk=C3=A4inen?= <98187+Tronic@users.noreply.github.com> Date: Mon, 6 Mar 2023 04:39:16 +0000 Subject: [PATCH] Simplified parse_content_header escaping (#2707) --- sanic/headers.py | 13 ++++++++----- tests/test_headers.py | 23 ++++------------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/sanic/headers.py b/sanic/headers.py index 067e985b..b7754adb 100644 --- a/sanic/headers.py +++ b/sanic/headers.py @@ -19,7 +19,6 @@ OptionsIterable = Iterable[Tuple[str, str]] # May contain duplicate keys _token, _quoted = r"([\w!#$%&'*+\-.^_`|~]+)", r'"([^"]*)"' _param = re.compile(rf";\s*{_token}=(?:{_token}|{_quoted})", re.ASCII) -_firefox_quote_escape = re.compile(r'\\"(?!; |\s*$)') _ipv6 = "(?:[0-9A-Fa-f]{0,4}:){2,7}[0-9A-Fa-f]{0,4}" _ipv6_re = re.compile(_ipv6) _host_re = re.compile( @@ -268,19 +267,23 @@ def parse_accept(accept: Optional[str]) -> AcceptList: def parse_content_header(value: str) -> Tuple[str, Options]: """Parse content-type and content-disposition header values. - E.g. 'form-data; name=upload; filename=\"file.txt\"' to + E.g. `form-data; name=upload; filename="file.txt"` to ('form-data', {'name': 'upload', 'filename': 'file.txt'}) Mostly identical to cgi.parse_header and werkzeug.parse_options_header - but runs faster and handles special characters better. Unescapes quotes. + but runs faster and handles special characters better. + + Unescapes %22 to `"` and %0D%0A to `\n` in field values. """ - value = _firefox_quote_escape.sub("%22", value) pos = value.find(";") if pos == -1: options: Dict[str, Union[int, str]] = {} else: options = { - m.group(1).lower(): m.group(2) or m.group(3).replace("%22", '"') + m.group(1) + .lower(): (m.group(2) or m.group(3)) + .replace("%22", '"') + .replace("%0D%0A", "\n") for m in _param.finditer(value[pos:]) } value = value[:pos] diff --git a/tests/test_headers.py b/tests/test_headers.py index b6cd5210..072357a9 100644 --- a/tests/test_headers.py +++ b/tests/test_headers.py @@ -49,32 +49,17 @@ def raised_ceiling(): ("attachment", {"filename": "strange;name", "size": "123"}), ), ( - 'form-data; name="files"; filename="fo\\"o;bar\\"', - ("form-data", {"name": "files", "filename": 'fo"o;bar\\'}) - # cgi.parse_header: - # ('form-data', {'name': 'files', 'filename': 'fo"o;bar\\'}) - # werkzeug.parse_options_header: - # ( - # "form-data", - # {"name": "files", "filename": '"fo\\"o', 'bar\\"': None}, - # ), + 'form-data; name="foo"; value="%22\\%0D%0A"', + ("form-data", {"name": "foo", "value": '\"\\\n'}) ), # with Unicode filename! ( - # Chrome: + # Chrome, Firefox: # Content-Disposition: form-data; name="foo%22;bar\"; filename="😀" 'form-data; name="foo%22;bar\\"; filename="😀"', ("form-data", {"name": 'foo";bar\\', "filename": "😀"}) # cgi: ('form-data', {'name': 'foo%22;bar"; filename="😀'}) - # werkzeug: ('form-data', {'name': 'foo%22;bar"; filename='}) - ), - ( - # Firefox: - # Content-Disposition: form-data; name="foo\";bar\"; filename="😀" - 'form-data; name="foo\\";bar\\"; filename="😀"', - ("form-data", {"name": 'foo";bar\\', "filename": "😀"}) - # cgi: ('form-data', {'name': 'foo";bar"; filename="😀'}) - # werkzeug: ('form-data', {'name': 'foo";bar"; filename='}) + # werkzeug (pre 2.3.0): ('form-data', {'name': 'foo%22;bar"; filename='}) ), ], )