Changelog
Each release names the Chromium revision it follows. A release that follows a newer revision can return different names wherever Chromium changed its rules, even when the API stays the same.
0.2.0
- Follows the same Chromium revision as 0.1.0,
dfd5cca812cefda32e909ae39965ed22041d5e65(main, 2026-10-16), and its ICU 78. suggested_filename()takes a response's headers as a whole and works out the MIME type from them as Chrome does, short of sniffing the body, and the newsanitize_filename()cleans a name from anywhere. Calls written for 0.1.0 may need the changes below.
Incompatible changes
| 0.1.0 | 0.2.0 |
|---|---|
suggested_filename(url) |
suggested_filename(url=url) |
suggested_filename(url, header) |
suggested_filename(url=url, content_disposition=header) |
suggested_filename(None, header) |
suggested_filename(content_disposition=header) |
suggested_filename(url, suggested_name=name) |
suggested_filename(url=url, download_attribute=name) |
suggested_filename(url, mime_type=content_type) |
suggested_filename(url=url, content_type=content_type) |
suggested_filename(url, default_name="") |
suggested_filename(url=url, default_name=None) |
suggested_filename(url=url, content_disposition=header) on Windows |
suggested_filename(url=url, content_disposition=header, platform="posix") |
parse_content_disposition(value=header) |
parse_content_disposition(header) |
suggested_filename()takes only one argument by position, the newheaders, which cannot be a str or bytes: a URL or a header passed by position raisesTypeError.suggested_nameis nowdownload_attribute, andmime_typeis nowcontent_type, the Content-Type header as the server sent it (see Changed behavior).default_name=Noneasks for the URL's host. An emptydefault_name, which did that in 0.1.0, raisesValueError.platformdefaults toNone, which picks Windows' rules when the code runs on Windows and the POSIX rules elsewhere. 0.1.0 used the POSIX rules everywhere: on Windows, passplatform="posix"to keep its names.- The
valueofparse_content_disposition()is positional-only.
To pass all of a response's headers instead of one header, see Usage.
New features
suggested_filename(headers, url=...)takes the response's headers, as a mapping or as (name, value) pairs with str or bytes values (HeadersLike, exported for type hints). The first value of the first Content-Disposition line names the file, and the Content-Type lines and X-Content-Type-Options give the MIME type.content_dispositionandcontent_type, when notNone, take the place of those lines.parse_content_disposition()also takes a response's headers, and its result has a new field,blocks_download: whether Chrome refuses the response because the Content-Disposition values, split on commas outside quotes, are not all the same, or because the headers hold a NUL.suggested_filename()names the file from the first value all the same. The field counts in equality and shows in the repr: forattachment; filename=a,b.txt, the result no longer equalsContentDisposition("attachment", "a").headers_encodingnames the codec your HTTP library decoded the headers with, such as"latin-1"for requests andurllib.request, and turns every str header value back into the bytes it was decoded from.content_typealso takes bytes.sanitize_filename()cleans a name from anywhere, such as a page title, as Chrome cleans an<a download>value.- Usage shows how to pass
the headers from httpx2, httpx, requests, aiohttp and
urllib.request, and tests fetch responses with each of them to check it.
Changed behavior
Some calls give another result in 0.2.0. The changes are of three kinds.
Closer to Chromium.
- Every header value, including
content_disposition,content_typeand thevalueofparse_content_disposition(), is read as the rest of its header line as it was sent (see Reading header lines): a line that starts with a space or a tab is joined on with one space, and any other line, or a blank line, ends the value. 0.1.0 read CR and LF as characters of the value:attachment;\r\n filename=wire.pdfgave no file name, andattachment; filename=a.pdf\r\nX-Note: 1took the next line into the name, where 0.2.0 giveswire.pdfanda.pdf. Values without CR or LF are not affected. content_typeis read the way Chrome's network stack reads a Content-Type header (see The MIME type): the type ends at the first space, tab,;or(, and of comma-separated values, the last one that gives a type wins, while the value*/*itself changes nothing. So with the URLhttps://example.com/page,text/html(comment),text/html charset=utf-8andtext/plain, text/htmlgivepage.html, where 0.1.0 gavepage.- The feed types
application/rss+xmlandapplication/atom+xmlaretext/plain, as in Chrome: they givepage.txt, where 0.1.0 gavepage.rssandpage. - Chrome sniffs the body for a MIME type only with
http:,https:,file:andfilesystem:URLs. With a valid URL of another scheme, such asftp://example.com/page, acontent_typethat gives no type, such as an empty one, istext/plain: it givespage.txt, where 0.1.0 gavepage.
content_type is the header as sent. 0.1.0 said to pass the MIME type
Chrome would use when the server does not send
X-Content-Type-Options: nosniff, since Chrome may then sniff the body for
another type. 0.2.0 takes that back: content_type is the Content-Type
header as the server sent it, and where Chrome sniffs the body, which the
package never sees, the package uses the type the headers declare. For a
response from https://example.com/page without Content-Type whose body is
HTML, the advice was to pass mime_type="text/html", which gave page.html;
0.2.0 gives page for these headers, while Chrome reports page.html (see
When Chrome sniffs the body).
Passing the whole headers. Moving from one header to all of them can change the name:
X-Content-Type-Options: nosniffcounts. With it, Chrome does not sniff, and a response without a Content-Type, or with one that gives no type, istext/plain:https://example.com/pagegivespage.txt, where 0.1.0 gavepage.- Every Content-Type line counts: the last value that gives a type wins, and
the value
*/*itself changes nothing.urllib.requestand aiohttp give the first line when you look a header up by name: for the linestext/plainandapplication/pdf, 0.1.0 gavepage.txtfor the first one, and 0.2.0 givespage.pdffor both. - The first value of the first Content-Disposition line names the file, as
before. Lines joined into one, as the
response.headersof requests, httpx and httpx2 give them, can give another result than the lines themselves (see Joined lines and guessed bytes).
0.1.0
- Initial release, following Chromium revision
dfd5cca812cefda32e909ae39965ed22041d5e65(main, 2026-10-16) and its ICU 78. suggested_filename()gives the name Chromium suggests for a download, andparse_content_disposition()reads a Content-Disposition header as Chromium does. Both take the header as bytes or str.- Pure Python with no dependencies, for CPython 3.11 and later, and PyPy.