From 1894d2db67f96d3b595ec854a29e18d831de5598 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 25 Aug 2026 11:11:01 +0000 Subject: [PATCH] =?UTF-8?q?Slim:=20sanitizeFileName=20k=C3=BCrzt=20hinten?= =?UTF-8?q?=20statt=20vorne=20und=20beh=C3=A4lt=20die=20Dateiendung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Lange Namen werden am Ende gekürzt (vorher substr(-33): "Bildschirmfoto…" wurde zu "ldschirmfoto…", der Namensanfang fiel weg) - Whitespace inkl. geschütztem Leerzeichen (macOS-Screenshots) wird zu "-", damit der Dateiname ohne Encoding in URLs funktioniert - Fallback "datei", wenn nach der Bereinigung nichts Verwertbares übrig bleibt - 5 Unit-Tests für Kürzung, Whitespace, Sonderzeichen und Fallback Co-Authored-By: Claude Fable 5 --- app/Services/Slim.php | 32 +++++++++++++++-- .../Services/SlimSanitizeFileNameTest.php | 34 +++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 tests/Unit/Services/SlimSanitizeFileNameTest.php diff --git a/app/Services/Slim.php b/app/Services/Slim.php index ac7ec52b..33c3d8c4 100644 --- a/app/Services/Slim.php +++ b/app/Services/Slim.php @@ -11,6 +11,9 @@ abstract class SlimStatus class Slim { + /** Maximale Länge des bereinigten Dateinamens inklusive Endung. */ + private const MAX_FILENAME_LENGTH = 33; + /** * Speichert ein Slim-Cropper-Upload (Base64-Payload aus getImages()) im * public-Disk und liefert Dateiname + Metadaten — gemeinsame Logik für @@ -237,16 +240,41 @@ class Slim * * @return string */ + /** + * Bereinigt einen hochgeladenen Dateinamen für die Ablage im public-Disk. + * + * Kürzt bei Bedarf am ENDE des Namens und behält die Endung — die frühere + * Fassung schnitt mit substr($str, -33) vorne ab, wodurch aus + * "Bildschirmfoto ....png" ein "ldschirmfoto ....png" wurde. Leerzeichen + * werden zu "-", damit die Datei ohne Encoding in einer URL stehen kann. + */ public static function sanitizeFileName($str) { + // Unicode-Trennzeichen (z. B. geschütztes Leerzeichen in macOS-Screenshot- + // Namen) zuerst zu einem normalen Leerzeichen machen. Der ASCII-Filter + // darunter arbeitet byteweise und würde sie sonst spurlos entfernen, + // sodass Wörter zusammenkleben. Bei ungültigem UTF-8 liefert preg_replace + // null — dann bleibt der Originalwert stehen. + $str = preg_replace('/[\p{Z}\s]+/u', ' ', (string) $str) ?? $str; + // Basic clean up $str = preg_replace('([^\w\s\d\-_~,;\[\]\(\).])', '', $str); // Remove any runs of periods $str = preg_replace('([\.]{2,})', '', $str); + // Whitespace (auch geschützte Leerzeichen aus macOS-Screenshots) zu "-" + $str = preg_replace('(\s+)', '-', $str); + $str = trim($str, '-'); - $str = (strlen($str) > 33) ? substr($str, -33) : $str; + $extension = pathinfo($str, PATHINFO_EXTENSION); + $suffix = $extension !== '' ? '.'.$extension : ''; + $base = $suffix !== '' ? substr($str, 0, -strlen($suffix)) : $str; - return $str; + $maxBase = max(1, self::MAX_FILENAME_LENGTH - strlen($suffix)); + if (strlen($base) > $maxBase) { + $base = rtrim(substr($base, 0, $maxBase), '-.'); + } + + return ($base === '' ? 'datei' : $base).$suffix; } /** diff --git a/tests/Unit/Services/SlimSanitizeFileNameTest.php b/tests/Unit/Services/SlimSanitizeFileNameTest.php new file mode 100644 index 00000000..dca0987d --- /dev/null +++ b/tests/Unit/Services/SlimSanitizeFileNameTest.php @@ -0,0 +1,34 @@ +toStartWith('Bildschirmfoto') + ->and($name)->toEndWith('.png') + ->and(strlen($name))->toBeLessThanOrEqual(33); +}); + +it('ersetzt Leerzeichen, damit der Name ohne Encoding in eine URL passt', function (): void { + expect(Slim::sanitizeFileName('Bild Kersrin.jpeg'))->toBe('Bild-Kersrin.jpeg') + // Geschuetztes Leerzeichen (macOS-Screenshots) zaehlt ebenfalls als Whitespace + ->and(Slim::sanitizeFileName("Foto\u{00A0}Neu.png"))->toBe('Foto-Neu.png'); +}); + +it('laesst kurze, saubere Namen unveraendert', function (): void { + expect(Slim::sanitizeFileName('IMG_6524.jpeg'))->toBe('IMG_6524.jpeg') + ->and(Slim::sanitizeFileName('ruth-thomas.jpg'))->toBe('ruth-thomas.jpg'); +}); + +it('entfernt unerlaubte Zeichen und Punktfolgen', function (): void { + expect(Slim::sanitizeFileName('re/port*?.png'))->toBe('report.png') + ->and(Slim::sanitizeFileName('archiv..tar.gz'))->toBe('archivtar.gz'); +}); + +it('liefert auch ohne verwertbaren Namen etwas Brauchbares', function (): void { + expect(Slim::sanitizeFileName('***.png'))->toBe('datei.png') + ->and(Slim::sanitizeFileName('bild'))->toBe('bild'); +});