UTF-8-Zeichen in Dateinamen

Probleme rund um UTF-8 gibt es immer wieder. Und Erklärungen zu UTF-8-Konfiguration für TYPO3 gibt es inzwischen auch zahlreich. Meistens endet es aber bei der Datenbank und der textlichen Ausgabe. Die Fallstricke im Umgang mit UTF-8-Zeichen im Dateisystem werden kaum beleuchtet.

Ausgangspunkt

Wir haben eine TYPO3-Installation:

  • Datenbank arbeitet mit UTF-8 (Standard-Kollation)
  • DB-Verbindung weiß, dass sie mit UTF-8 arbeiten soll ($TYPO3_CONF_VARS[‚SYS‘][’setDBinit‘])
  • TYPO3-Backend ist angewiesen UTF-8 zu nutzen ($TYPO3_CONF_VARS[‚BE‘][‚forceCharset‘])
  • TYPO3 weiß, dass unser Filesystem UTF-8 versteht ($TYPO3_CONF_VARS[‚SYS‘][‚UTF8filesystem‘])

…und trotzdem finden sich error.log-Einträge, die vermisste Dateien protokollieren:

/usr/bin/gm convert: Unable to open file (uploads/tx_example/berraschung.jpg) [No such file or directory].

Analyse

Wie kann das sein? Warum wird überhaupt eine Datei „berraschung.jpg“ angefordert? In der Datenbank steht doch korrekt „Überraschung.jpg“ drin. Im Dateisystem liegt im Ordner unserer Extension die Datei auch korrekt. Und im PHP-Code wird doch extra vor der Verarbeitung des Bildes nochmals geprüft, ob die Datei existiert (file_exists()).

Verfolgen wir den Dateinamen auf dem Weg aus der Datenbank bis zur Generierung des Bildes:
Um aus dem cObject IMAGE an das Bild zu gelangen, stoßen wir auf imageMagickConvert(). Die Methode t3lib_stdgraphic::imageMagickConvert() ruft t3lib_div::imageMagickCommand() auf, die wiederum t3lib_utility_Command::imageMagickCommand() aufruft, um dann die Rückgabe (Kommando) an t3lib_utility_Command::imageMagickCommand::exec() zu übergeben und ausführen zu lassen.
Auf diesem Weg wird das generierte Kommando auch einmal durch escapeshellarg() geschickt – und hier lauert die Ursache.

Schauen wir uns das mal in rohem PHP genauer an:

$cmd = "'/usr/bin/gm' convert +profile '*' -geometry 170x136! -colorspace RGB -quality 90  '/var/www/example.org/htdocs/uploads/tx_example/Überraschung.jpg'[0] '/var/www/example.org/htdocs/typo3temp/pics/Überraschung.jpg'";
echo escapeshellarg($cmd);

Ausgabe:

''\''/usr/bin/gm'\'' convert +profile '\''*'\'' -geometry 170x136! -colorspace RGB -quality 90 '\''/var/www/example.org/htdocs/uploads/tx_example/berraschung.jpg'\''[0] '\''/var/www/example.org/htdocs/typo3temp/pics/berraschung.jpg'\'''

Ups…Da fehlt plötzlich das „Ü“ 🙁

Lösung

Ein Kommentar in der PHP-Dokumentation verrät die Lösung: unsere Locales passen nicht, wodurch UTF-8-Zeichen beim Escapen verschluckt werden. Mit einer kleinen Änderung am PHP-Beispiel klappt es dann:

setlocale(LC_CTYPE, "de_DE.UTF-8");
$cmd = "'/usr/bin/gm' convert +profile '*' -geometry 170x136! -colorspace RGB -quality 90  '/var/www/example.org/htdocs/uploads/tx_example/Überraschung.jpg'[0] '/var/www/example.org/htdocs/typo3temp/pics/Überraschung.jpg'";
echo escapeshellarg($cmd);

Ausgabe:

''\''/usr/bin/gm'\'' convert +profile '\''*'\'' -geometry 170x136! -colorspace RGB -quality 90 '\''/var/www/example.org/htdocs/uploads/tx_example/Überraschung.jpg'\''[0] '\''/var/www/example.org/htdocs/typo3temp/pics/Überraschung.jpg'\'''

Ok, und wie bekommen wir es nun noch hin, dass das in den Tiefen von TYPO3 funktioniert? Wie so oft findet sich die Lösung in der TSref: config.locale_all

PHP: setlocale(„LC_ALL“, [value]);
value-examples: deutsch, de_DE, danish, portuguese, spanish, french, norwegian, italian. See www.php.net for other value.
Also on linux, look at /usr/share/locale/

Ein Blick in den Ordner verrät uns, dass es neben de_DE auch ein de_DE.UTF-8 gibt (auf anderen Systemen auch de_DE@UTF-8 odgl.). Somit ist die Lösung, im TS-Setup:

config.locale_all = de_DE.UTF-8