﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
37198	"content_disposition_header emits invalid header for a filename with a trailing newline (""$"" should be ""\Z"")"	Aman Agrawal		"content_disposition_header() is meant to emit the percent-encoded
filename*=utf-8'' form for any filename that is not a valid RFC 9110
quoted-string, and to only use the bare quoted form for filenames that
are. Its check has a blind spot for a trailing newline:

{{{
    >>> from django.utils.http import content_disposition_header
    >>> content_disposition_header(True, ""report.pdf\n"")
    'attachment; filename=""report.pdf\n""'
    >>> content_disposition_header(True, ""\n"")
    'attachment; filename=""\n""'

}}}


The returned value contains a raw newline, so setting it as a header
raises BadHeaderError (Django responses), and boto3/http.client raises
ValueError(""Invalid header value ..."") — an uncaught 500 for anyone
serving a user-supplied filename that ends in a newline. (A newline
*elsewhere* in the filename is handled correctly.)

Root cause
----------
The quotable-character test in django/utils/http.py is:

{{{
    quotable_characters = r""^[\t \x21-\x7e]*$""
    if is_ascii and re.match(quotable_characters, filename):

}}}


In Python, ""$"" matches at the end of the string *or immediately before
a trailing ""\n""*. So a filename of quotable characters plus one
trailing newline matches, takes the quoted-string branch, and is
emitted verbatim. This is the same class of bug as CVE-2021-32052
(URLValidator ""$"" accepting a trailing newline), after which validators
were switched to ""\Z"".

This was introduced with the control-character handling in #36023.

Proposed fix
------------
Anchor with ""\Z"" (matches only the true end of string):

{{{
    -    quotable_characters = r""^[\t \x21-\x7e]*$""
    +    quotable_characters = r""^[\t \x21-\x7e]*\Z""

}}}


Verified this sends ""report.pdf\n"" and ""\n"" to the filename*=utf-8''
branch while leaving all other filenames (e.g. ""report.pdf"",
""my report.png"") on the existing quoted-string branch. A regression
case should be added to ContentDispositionHeaderTests in
tests/utils_tests/test_http.py, e.g.:

{{{
    (""attachment; filename*=utf-8''report.pdf%0A"", True, ""report.pdf\n""),
}}}

"	Bug	new	HTTP handling	5.2	Normal			Aman Agrawal	Unreviewed	0	0	0	0	1	0
