I download a file using the get
function of Python requests
library. For storing the file, I'd like to determine the filename they way a web browser would for its 'save' or 'save as ...' dialog.
Easy, right? I can just get it from the Content-Disposition
HTTP header, accessible on the response object:
import re
d = r.headers['content-disposition']
fname = re.findall("filename=(.+)", d)
But looking more closely at this topic, it isn't that easy:
According to RFC 6266 section 4.3, and the grammar in the section 4.1, the value can be an unquoted token (e.g. the_report.pdf
) or a quoted string that can also contain whitespace (e.g. "the report.pdf"
) and escape sequences (the latter are discouraged, though, thus their handling isn't a hard requirement for me). Further,
when both "filename" and "filename*" are present in a single header field value, [we] SHOULD pick "filename*" and ignore "filename".
The value of filename*
, though, is yet a bit more complicated than the one of filename
.
Also, the RFC seems to allow for additional whitespace around the =
.
Thus, for the examples listed in the RFC, I'd want the following results:
filename:Content-Disposition: Attachment; filename=example.html
example.html
filename:Content-Disposition: INLINE; FILENAME= "an example.html"
an example.html
filename:Content-Disposition: attachment; filename*= UTF-8''%e2%82%ac%20rates
€ rates
filename:Content-Disposition: attachment; filename="EURO rates"; filename*=utf-8''%e2%82%ac%20rates
€ rates
here, too (notEURO rates
, asfilename*
takes precedence)
I could implement the parsing of the Content-Disposition
header I get from requests
accordingly myself, but if I can avoid it and use an existing proven implementation instead, I'd prefer that.
Is there a Python library that can do this?
Requirements
The library would have to
- provide a function that extracts and returns the proper filename (if there is one) from a passed
requests
response
or - provide a function that extracts and returns the proper filename (if there is one) from a passed
Content-Disposition
header field value (a string)
or - provide a function accepting the all the same parameters as
requests.get
that performs the request, and returns the response as well as the filename (if there is one)
or - provides something similarly practical
Non-requirements
What it doesn't have to handle (but if it does, even better) as I can do that myself:
- sanitize values so that they don't contain directory names or other path elements except for a single filename, so storing with that name won't cause files to be created or overwritten at arbitrary locations
- produce "save" filename extensions "optimally matching the media type of the received payload" (see section 4.3)
- sanitize filenames to prevent user confusion (section 4.3 mentions replacing "control characters and leading and trailing whitespace")
provide a fall-back
- for when neither the
filename
nor thefilename*
disposition parameter are present or - for when the ones that are present cannot be parsed or
- for when the complete
Content-Disposition
header is missing
Though it should report that consistently (be it by raising or by returning
None
or''
), so that I can let my own fall-back kick in.- for when neither the