mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-26 06:19:49 +00:00
a92af3eebc
* fix(attachments): drop Bidi_Control characters from attachment names and rewrite legacy rows (BUG-3153) A name such as "x<U+202E>gvs.txt" is a .txt that every bidi-aware renderer shows as "xtxt.svg". About forty web read sites, the CLI list and the server render the stored name, so the fix is at the source rather than per renderer. DroppedFilenameRune now also drops the Unicode Bidi_Control property: U+202A to U+202E, U+2066 to U+2069, and the marks U+200E, U+200F and U+061C. It is chosen by property, so ZWJ (Join_Control) and emoji sequences are untouched. The rule is shared, so both ingest doors store the name clean and the download header drops the characters for any row. One of these characters inside an extension also hid that extension from the blocklist, as a control byte did: an SVG uploaded as "x.s<U+202E>vg" was accepted, and is now refused as ".svg". Legacy rows are rewritten once, at startup, by BackfillBidiAttachmentFilenames, and only rows containing a Bidi_Control character are touched. The new name is the SERVED name (ServedFilename), not a plain strip, so a legacy "x.s<U+202E>vg" becomes "x.bin" rather than storing the blocked "x.svg". The pass is idempotent with no marker, since a rewritten row no longer matches. Each UPDATE is conditional on the old name. Every rewrite is logged with its old and new names quoted to ASCII, so the rewrite is reversible, and the count is logged on every boot, 0 included. Claude-Session: https://claude.ai/code/session_01Vf8uZmYdsrQqJtbAmjUFSX * fix(store): the bidi filename backfill states its wider rewrite and is tested per rune on both dialects (BUG-3153, codex round 1) - The rewrite is the served name, so a selected row also loses whatever else NormalizeFilename removes from a legacy name. That is now said in the comment and pinned by a case, and rows without a Bidi_Control are still never touched. - "Reversible by the log" now says: for as long as the log is retained, with no durable record in the database. - A row that gains such a name later (derived from a parent a failed pass left) is picked up by the next boot, since selection is by content. - New test: one row per member of unicode.Bidi_Control, each selected by the SQL and rewritten. It enumerates the property itself rather than the list under test, so a rune missing from the LIKE list fails it. Claude-Session: https://claude.ai/code/session_01Vf8uZmYdsrQqJtbAmjUFSX * fix(store): a rows-affected error stops the bidi backfill instead of passing as a concurrent rename (BUG-3153, codex round 2) The UPDATE may have landed, and without a count the row could not be logged as rewritten, which the reversibility rests on. The comment also names the readers it means, instead of an unmeasured count. Claude-Session: https://claude.ai/code/session_01Vf8uZmYdsrQqJtbAmjUFSX * docs(attachments): NormalizeFilename covers the caller-supplied doors, and derived rows inherit (BUG-3153, codex round 3) "Every door that writes attachments.filename calls it" was not true of thumbnails, transforms or copies, which derive their name from another row. Claude-Session: https://claude.ai/code/session_01Vf8uZmYdsrQqJtbAmjUFSX * test(e2e): the hostile-name parity spec expects the stored name, with Bidi_Control dropped (BUG-3153) The spec uploads "start <U+202E>reversed<U+202C> مرحبا.png" and asserted that the tile and dialog carry it exactly as uploaded. Ingest now drops the override. The spec still uploads the hostile name, and now asserts that the STORED name (RTL letters kept, controls gone) is addressable and names its dialog. Its fixture now uses \u escapes instead of literal controls. CI caught this on #1452; the local sweep had not searched web/e2e. Against this tree's binary the spec passes, and against a binary without the fix it fails, so the spec discriminates. Claude-Session: https://claude.ai/code/session_01Vf8uZmYdsrQqJtbAmjUFSX