dedlfix: Character Encoding - UTF-8 - multibyte String-Funktionen

Beitrag lesen

Hi!

Alle Funktionen, die dir eine Zeichenkodierungserkennung versprechen, kochen auch nur mit Wasser und liefern nicht in jedem Fall ein fehlerfreies Ergebnis.
Überhaupt scheint das ein "schwieriges" Kapitel zu sein. In den diversen User-Kommentaren zu einigen der PHP-Funktionen liest man immer das Wort "buggy" ...!

Eine einigermaßen sinnvolle Funktion achtet nur darauf, dass keine bei UTF-8 nicht vorkommenden Bytefolgen enthalten sind. Wie gültige Bytefolgen aussehen, zeigt unter anderem die Wikipedia im Abschnitt UTF-8 - Kodierung.

Es ist auch nicht möglich, einem Client die zu verwendende Kodierung vorzuschreiben. Das geht schon deshalb nicht, weil ein Client auch ohne den Server zu kennen, eine Anforderung absenden können muss. Weiterhin gibt es keine Regel, in welcher Kodierung er zu senden hat und auch keinen Header oder dergleichen, in dem der Client dem Server die verwendete Kodierung mitteilt.
Ja, klasse. Je mehr ich mich mit dem Thema (zwangsweise) befasse, umso erstaunter bin ich, dass das Surfen im Web bisher so "unproblematisch" funktioniert hat.

Die Vorreiter der Rechentechnik und des Internets haben keine Probleme mit komischen Zeichen, weil sie diese einfach nicht kennen. Sie haben seit je her nur 26 Buchstaben betrachtet und sich nicht für den Rest der Welt interessiert - und haben uns damit dieses ganze Dilemma eingebrockt. Da also vieles nur ASCII-tauglich erfunden wurde, tut man gut daran, an den kritischen Stellen nur ASCII zu verwenden. Kritische Stellen sind all die, an denen keine explizite Kodierung angegeben werden kann, also vor allem bei URLs und Dateinamen.

Wenn du eine URL als Text veröffentlichst, in der ein Umlaut enthalten ist, so wird es vermutlich jemanden geben, der die URL kopiert und bei sich veröffentlicht. Auf deinen Seiten ist alles kein Problem, solange die Leute nur klicken. Der Browser wird (wenn er nicht verkonfiguriert ist) die Zeichenkodierung verwenden, in der deine Seite vorliegt. Wird sie aber in eine Seite mit anderer Kodierung eingefügt, wirst du sie in eben dieser zurückbekommen. Machen kannst du dagegen nichts, nur vermeiden, indem du dich auf ASCII beschränkst, oder musst hinterher rumraten, welche Kodierung es sein könnte.

Hier fangen dann meine Probleme an. Denn welche Funktion muss ich denn nun bspw. verwenden: stripos() (für ISO-8859-x) oder mb_stripos() (für UTF-8)?

Zunächst musst du wissen, welche Zeichenkodierung vorliegt. Das weißt du entweder, weil du volle Kontrolle über die Systeme hast - beispielsweise in Richtung DBMS - oder weil du auf eine bestimmte Kodierung vertraust oder sie zumindest auf syntaktische Fehlerfreiheit geprüft hast.

Und es fängt ja schon viel früher an. So ist mir u.a. auch noch nicht klar, wann, wie und warum welcher Browser in der Adresszeile "Klartext" (auch mit Umlauten etc.) und wann URL kodierten Text (also mit %xx) anzeigt? Mein FF 3.5 zeigt bspw. bei einem Klick auf einen Link den "Klartext" in der Adresszeile (in der Statuszeile ebenso) an und bei manueller Eingabe den kodierten Text.

Das ist auch einigermaßen unerheblich, weil du als Server nur den Request siehst, nicht was der Client anzeigt.

Lasse ich mir dann bspw. per Script die $_SERVER Variablen anzeigen, dann sind $_SERVER[REQUEST_URI] und $_SERVER[QUERY_STRING] immer kodiert, während $_SERVER[PATH_INFO] im Klartext angezeigt wird.
Beispiel:
$_SERVER[REQUEST_URI]: /Umlaute:%20%C3%A4%C3%84%C3%B6%C3%96%C3%BC%C3%9C%C3%9F%20Preis:%20%E2%82%AC%2030,-
$_SERVER[PATH_INFO]: /Umlaute: äÄöÖüÜß Preis: € 30,-

Du musst hier zwischen kontexterechter Kodierung und Zeichenkodierung unterscheiden. Die %xx-Schreibweise ist dem URL-Kontext geschuldet. PathInfo ist bereits ein vom Server dekodierter Wert, während RequestUri als Original angezeigt wird, also so wie der Webserver ihn empfangen hat. Wenn du damit weiterarbeiten willst, empfiehlt es sich, erst einmal die "Transportsicherung" zu entfernen, also url_decode() darauf anzuwenden.

Das Ergebnis ist eine Bytefolge. Mit bin2hex() kannst du dir die Bytewerte genau ansehen, doch aus der URL-Kodierung lassen sich die uns interessierenden Bytes ebenfalls herauslesen. %20 ist ein Leerzeichen, nicht weiter beachtenswert. %C3%B4 steht für das ä, also ein UTF-8-kodiertes. Sähest du stattdessen ein %E4, wäre es mit hoher Wahrscheinlichkeit ein ISO-8859-1-kodiertes. Und so weiter. Das €-Zeichen am Schluss wird übrigens in UTF-8 mit drei Bytes kodiert: %E2%82%AC.

Für die UTF-8-"Erkennung" gibt es einige Funktionen in den Userkommentaren bei den üblichen verdächtigen Funktionen (suche bei utf8_decode, utf8_encode, mb_detect_encoding). Sie können aber lediglich die formale Richtigkeit von UTF-8-Sequenzen ermitteln, nicht aber, ob tatsächlich eine UTF-8-Kodierung vom Autor beabsichtig war oder ob die Bytesequenz nur zufällig gültiges UTF-8 ergibt.
Ja, danke! Mit den ganzen Funktionen komme ich auch noch nicht wirklich zurecht. Solange ich die_nicht_verwende, funktioniert bisher alles (augenscheinlich) wie es soll. Sobald ich mit einer der Funktionen anfange "rumzudoktern", klappt nichts mehr!

Versuch genauer hinzuschauen, notfalls auch indem du dir die Bytewerte mit bin2hex() ausgeben lässt. Was genau liegt vor? Was verspricht eine Funktion zu tun? Was genau ist das Ergebnis? Welches Ziel sollte erreicht werden und stimmt das mit dem tatsächlich vorliegenden Ergebnis überein?

Dabei musst du auch beachten: Muss eine weitere Kodierung beachtet werden, beispielsweise die URL-Kodierung? Sehe ich Zeichen? Wenn ja, gemäß welcher Zeichenkodierung sind sie aktuell interpretiert worden? Was zeigt der Browser im Kontextmenü an oder was hat er unter Ansicht->(Zeichen)kodierung angehakt?

Übrigens, den Request allein im Hinblick auf UTF-8-Fähigkeit zu beachten, ist oft noch nicht ausreichend. Auch das Speichern der Daten verlangt eine Betrachtung und damit die dafür verwendeten Medien (Dateien oder Datenbanken). Du kannst ja mal aufzählen, welche Stellen dir im Webumfeld einfallen, an denen eine Zeichenkodierung eine Rolle spielt und eingestellt werden kann. Wir™ schauen dann mal, ob du alle kennst. (Das Nachschlagen im hiesigen Archiv ist nicht nur nicht verboten, sondern ausdrücklich erwünscht :-)
Ja wie jetzt? Gibt es denn noch mehr, als die, die du in deinem Artikel genannt hast? Dann wäre der ja unvollständig. ;-)

Ja, der Request ist ja nur ein Teil des gesamten Verarbeitungsvorgangs. Zu beachten sind immer zwei Dinge: die Verarbeitung innerhalb eines Systems und die Kommunikation zwischen zwei Systemen.

Mein Artikel ist unvollständig, wenn du so willst. Er betrachtet nur den Kontextwechsel an sich und welche Zeichen in welchen Situationen wie maskiert werden müssen. Er behandelt nicht das Thema Zeichenkodierung, also welche Bits und Bytes und welches System zum Speichern dieser Zeichen verwendet wurden, weil das ebenfalls ein komplexes Thema und einen weiteren Artikel wert ist. Angenommen es gäbe kein Zeichenkodierungsproblem, das Kontextproblem besteht weiterhin.

Ein Aufruf von mb_internal_encoding() gibt mir ISO-8859-1 als interne Kodierung aus.
Diese müsste ich, wenn ich durchgehend mit UTF-8 arbeiten will, dann entsprechend umstellen?
Diese Frage konnte das PHP-Handbuch nicht klären?
Nein, nicht wirklich.

Schade, ich habe nämlich die mb-Fuktionen selbst noch nicht verwendet und wollte deshalb eine konkrete Antwort vermeiden. So ein stringverarbeitendes System benötigt aber stets Informationen, welche Kodierung vorliegt, denn raten ist ja schlecht bis unmöglich. Prinzipiell kann man das so lösen, dass man die Kodierung über einen weiteren Parameter bekanntgibt oder man verwendet generel nur eine Kodierung, die man zentral einstellt, was dann wohl mb_internal_encoding() macht.

Außerdem müsste ich doch dann nach meinem bisherigen Verständnis sicherstellen, dass meine zu verarbeitenden Strings und Arrays auch wirklich alle definitiv UTF-8 kodiert sind, oder nicht? Ansonsten würde ich ja wiederum "falsche" Ergebnisse bekommen?

Ja klar, so ein System ist per se dumm. Wenn du ihm sagst: "englisch", kannst du ihm nicht mit französisch kommen und Fehlerfreiheit erwarten. Du musst sicherstellen, dass englisch ankommt und gegebenenfalls französisch nach englisch übersetzen.

Stellt sich für mich aber gleich die nächste Frage: Lohnt sich der "Aufwand/ Aufstand" (bei PHP < 6), [...]
Anforderung ist im Prinzip "nur", dass aufgrund meiner "sprechenden" URLs eben auch Umlaute korrekt gehandhabt werden können sollen (tolles Satzkonstrukt). Dabei war ich wohl fälschlicherweise davon ausgegangen, bzw. dem Trugschluss erlegen, dass wenn ich eh alles andere UTF-8 kodiere, es dann auch "einfacher" wäre, scriptintern gleich mit UTF-8 kodierten Strings und Arrays zu arbeiten. Dem scheint aber wohl momentan zumindest noch nicht so zu sein.

Das wird sich auch in absehbarer Zukunft nicht ändern, solange nicht UTF-8 mit Höchstquote im Internet Verwendung findet. Das Problem hängt ja nicht nur an PHP6 oder nicht, sprich: an der eigenen Fähigkeit UTF-8 verarbeiten zu können, sondern gerade bei den ULRs an den Zulieferern.

Nicht-ASCII-Zeichen in URLs zu verwenden, die auch in anderen, nicht selbst kontrollierten Systemen abgelegt werden können, ist derzeit nicht wirklich empfehlenswert. Als einen Weg, wenn du trotzdem Nicht-ASCII-Zeichen in URLs haben möchtest, sehe ich nur, einkommende URLs einem UTF-8-Syntax-Check zu unterziehen und bei Nichtbestehen, vor dem Weiterverarbeiten von ISO-8859-1[*] nach UTF-8 umzukodieren.

[*] Windows-1252 kann auch eine Option sein, besonders wenn das €-Zeichen eine Rolle spielt, denn das gibt es nicht in ISO-8859-1 wohl aber in Windows-1252. Im Allgemeinen verwenden Browser beim Vorkommen von den in Windows-1252 zusätzlich enthaltenen Zeichen tatsächlich Windows-1252 statt ISO-8859-1, auch wenn es als letzteres deklariert wird. (Siehe zu den Unterschieden den Wikipedia-Eintrag zu ISO-8859-1.)

Lo!