bambuddy/backend/app/utils/http.py
maziggy 3f58fc74b4 fix(http): RFC 6266-encode Content-Disposition so non-ASCII filenames don't crash response (issue #1245)
Reported by @1000Delta. The printer file download (and three sibling
  endpoints) raised UnicodeEncodeError: 'latin-1' codec can't encode
  characters... on any filename outside U+0000..U+00FF (Chinese,
  Japanese, Arabic, accented Latin), because the route pushed `filename`
  straight into Content-Disposition: attachment; filename="...".
  Starlette/uvicorn encodes response headers as latin-1, so the assignment
  crashed at write-time.

  New backend/app/utils/http.py::build_content_disposition emits both an
  ASCII-stripped legacy filename="..." fallback and an RFC 5987
  filename*=UTF-8''<percent-encoded> parameter. Every modern browser
  prefers the *= form, so the original Unicode filename round-trips
  through Save-As intact.

  Same shape was latent in three siblings and fixed in the same PR
  (no deferred follow-ups): archive QR endpoint (archive.print_name
  from 3MF metadata), project ZIP export (project.name — the existing
  isalnum() sanitiser passes non-ASCII through), and the PDF label
  streamer (latent today, callers ASCII-only but the helper hardens it).
2026-05-08 14:06:40 +02:00

17 lines
859 B
Python

"""HTTP response helpers."""
from urllib.parse import quote
def build_content_disposition(filename: str, disposition: str = "attachment") -> str:
"""Build an RFC 6266-compliant Content-Disposition header value.
Starlette/uvicorn encodes response headers as latin-1, so any non-ASCII
character in a raw `filename="..."` parameter raises UnicodeEncodeError.
The fix is RFC 5987's `filename*=UTF-8''<percent-encoded>` form alongside
a stripped ASCII fallback in the legacy `filename="..."` parameter — every
modern browser prefers the `*` form when present.
"""
ascii_fallback = filename.encode("ascii", "ignore").decode("ascii").strip(" ._-") or "download"
ascii_fallback = ascii_fallback.replace('"', "").replace("\\", "")
return f"{disposition}; filename=\"{ascii_fallback}\"; filename*=UTF-8''{quote(filename)}"