From b67775283bdb784e634e5e198a4dcee0836c4642 Mon Sep 17 00:00:00 2001 From: ScreenTinker Date: Tue, 12 May 2026 11:51:34 -0500 Subject: [PATCH] fix(server): NFC normalize user-facing filenames in safeFilename Single line change to safeFilename() in routes/content.js: add .normalize('NFC') before sanitizeString. Covers all 4 user-facing filename storage sites (POST /, POST /remote, POST /embed, PUT /:id rename) since they all flow through safeFilename. Fixes macOS NFD vs Linux NFC mismatch on filename storage that mangled umlauts (ae/oe/ue/ss) in displayed filenames. macOS clients send NFD-decomposed names (e.g. 'u' + combining diaeresis U+0308 instead of the precomposed U+00FC); Linux + most renderers expect NFC. Without this, names like 'Begruessungsscreens.jpg' arrive with the combining char floating and display as mojibake. Reported by semetra22 in Discord with extraordinarily good debugging narrowing (rename works, upload doesn't = bug is in upload path). Single-point fix at the convergence of all user-facing filename flows. Existing NFD-mangled rows in DB not backfilled; users can re-upload or rename to repair. Optional one-time UPDATE backfill captured as follow-up in handoff doc. Smoke verified by invoking safeFilename directly on NFD + NFC inputs of 'Begruessungsscreens.jpg' - both produce identical NFC-normalized bytes (42656772c3bcc39f756e677373637265656e732e6a7067). --- server/routes/content.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/server/routes/content.js b/server/routes/content.js index 31f3e20..3c0f07e 100644 --- a/server/routes/content.js +++ b/server/routes/content.js @@ -16,9 +16,17 @@ const { accessContext } = require('../lib/tenancy'); // bypassing sanitizeBody. Apply the same HTML-escape here so a filename like // `">.jpg` is stored as `"><img...` and // renders as text in every UI sink. Umlauts, spaces, dots, and other unicode are -// preserved — sanitizeString only touches `& < > " '`. +// preserved - sanitizeString only touches `& < > " '`. +// +// .normalize('NFC') first: macOS clients send NFD-decomposed filenames (an +// umlaut like "u" + combining diaeresis U+0308 instead of the precomposed +// "u-umlaut" U+00FC). Linux + most renderers expect NFC; without this, names +// like "Begrussungsscreens.jpg" arrive with the combining char floating and +// display as mojibake. Single-point fix - every user-facing filename storage +// site (POST /, POST /remote, POST /embed, PUT /:id rename) flows through +// safeFilename, so normalizing here covers all paths. function safeFilename(name) { - return sanitizeString(name || ''); + return sanitizeString((name || '').normalize('NFC')); } // SSRF gate for remote_url. Returns null if valid, else { status, error }.