Content-Disposition Filename Parser
Extract a safe basename from Content-Disposition headers, honoring RFC 5987 encoding and rejecting traversal segments.
Code
import re
from pathlib import PurePosixPath
from urllib.parse import unquote
NAME_STAR = re.compile(r"filename\*\s*=\s*([^;]+)", re.IGNORECASE)
NAME = re.compile(r'filename\s*=\s*(?:"([^"]*)"|([^;]+))', re.IGNORECASE)
def safe_basename(value: str) -> str | None:
candidate = PurePosixPath(value.replace("\\", "/")).name.strip()
if candidate in {"", ".", ".."} or "\x00" in candidate:
return None
return candidate
def attachment_filename(header: str) -> str | None:
match = NAME_STAR.search(header)
if match:
value = match.group(1).strip().strip('"')
charset, _, remainder = value.partition("'")
_, _, encoded = remainder.partition("'")
try:
decoded = unquote(
encoded or remainder,
encoding=charset or "utf-8",
errors="strict",
)
except (LookupError, UnicodeDecodeError):
decoded = unquote(encoded or remainder)
return safe_basename(decoded)
match = NAME.search(header)
if not match:
return None
value = match.group(1) if match.group(1) is not None else match.group(2)
return safe_basename(unquote(value.strip()))
def main() -> None:
headers = [
'attachment; filename="report final.pdf"',
"attachment; filename=../../etc/passwd",
"attachment; filename*=UTF-8''%E5%B9%B4%E6%8A%A5.pdf",
'attachment; filename=".."',
]
for header in headers:
print(attachment_filename(header))
if __name__ == "__main__":
main()Report a problem with this exampleOpens a pre-filled issue on GitHub.