feat(anhaenge): Anhaenge im Web-Editor und im Detail-Popup
Im Editor werden Dateien gestaget und erst beim Speichern angewendet -- sonst haette ein neuer Termin keine UID, an die der Anhang gehen koennte, und "Abbrechen" wuerde nicht mehr bedeuten, dass nichts passiert ist. Schlaegt danach ein Upload fehl, sagt der Toast das, statt Erfolg vorzutaeuschen. Das Popup laedt die Liste erst beim Oeffnen, weil das Event-JSON nur die Anzahl traegt. Thumbnails gehen ueber authentifiziertes fetch auf eine Blob-URL, weil ein img-Tag den Token nicht mitschicken kann -- die URL wird nach dem Dekodieren freigegeben, anders als an den beiden bestehenden Stellen, die sie liegen lassen. Die fest verdrahtete Popup-Hoehe von 200 px wird jetzt gemessen; mit einer Anhangliste stimmte die Annahme nicht mehr. api.js haengt den HTTP-Status an geworfene Fehler, damit die Oberflaeche 409/413/400 uebersetzen kann statt das deutsche Backend-Detail zu zeigen. Der ASCII-Rueckfallname im Content-Disposition war keiner: isalnum() ist fuer Umlaute wahr. Faellt beim Herunterladen von "Rechnung Zahnaerztin.pdf" auf, jetzt mit Test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a024fa54b9
commit
e2a7edf421
@@ -87,8 +87,11 @@ def _download_response(att: models.EventAttachment) -> FileResponse:
|
||||
# Row without bytes: the sweep will reconcile it on its next run.
|
||||
raise HTTPException(404, "Attachment not found")
|
||||
# RFC 6266: an ASCII fallback plus the real UTF-8 name, so umlauts survive.
|
||||
# str.isalnum() is true for "ä" and friends, so the ASCII test is explicit —
|
||||
# otherwise the "ASCII" fallback would carry the very bytes it exists to
|
||||
# avoid, and older clients would garble the name.
|
||||
ascii_name = "".join(
|
||||
c for c in att.filename if c.isalnum() or c in " -_."
|
||||
c for c in att.filename if c.isascii() and (c.isalnum() or c in " -_.")
|
||||
).strip() or "anhang"
|
||||
disposition = (
|
||||
f'attachment; filename="{ascii_name}"; '
|
||||
|
||||
@@ -645,3 +645,17 @@ def test_caldav_put_leaves_attachments_alone(client):
|
||||
assert r.status_code in (201, 204), r.text
|
||||
assert _row_count() == 1
|
||||
assert len(_attach_props(client.get(dav_path).text)) == 1
|
||||
|
||||
|
||||
def test_download_filename_fallback_is_really_ascii(client):
|
||||
"""str.isalnum() is true for umlauts, so the ASCII fallback needs an
|
||||
explicit ascii test — otherwise it carries the bytes it exists to avoid."""
|
||||
token = register_admin(client)
|
||||
ev = _make_event(client, token, _make_calendar(client, token))
|
||||
att_id = _upload(client, token, ev["id"], name="Rechnung Zahnärztin.pdf").json()["id"]
|
||||
|
||||
dl = client.get(f"/api/local/attachments/{att_id}", headers=auth(token))
|
||||
disposition = dl.headers["content-disposition"]
|
||||
fallback = disposition.split('filename="', 1)[1].split('"', 1)[0]
|
||||
assert fallback.isascii(), fallback
|
||||
assert "filename*=UTF-8''Rechnung%20Zahn%C3%A4rztin.pdf" in disposition
|
||||
|
||||
Reference in New Issue
Block a user