Deus F (noreg): Zeichencodierungs-Fuckup

Guten Morgen,
ich bin nicht sicher ob die Kategorie "Datenbank" korrekt ist, denn möglicherweise ist es auch ein HTTP-Problem oder ein Browser-Problem oder gar ein HTML-Problem.

Ich habe nämlich Schwierigkeiten Umlaute aus einem Formular in meine Datenbank und zurück zu bringen.

Zu den Einzelheiten:
Ich verwende PHP um eine mySQL-Datenbank anzusteuern und (in der Regel) HTML5 zu erzeugen.
Alle meine PHP-Dateien sind UTF8-codiert ohne BOM.
Alle meine Ausgaben werden ebenfalls als UTF-8 ausgeliefert (jede Seite die Ausgaben erzeugt beginnt mit header('Content-Type: text/html; charset=utf-8'); und nahezu jede Ausgabe beginnt mit <!DOCTYPE html><html><head><title></title><style></style></head>(...))
Mein Eingabeformular ist so definiert: <form action="./backend.php" method="post">
mein SQL wird (beispielsweise) so erzeugt:

$sql_query = "UPDATE `".$db_data['database']."`.`Tabellenname` SET  
`artist` =  '".mysql_real_escape_string($_REQUEST['artist'])."',  
`translator` =  '".mysql_real_escape_string($_REQUEST['translator'])."',  
`title` =  '".mysql_real_escape_string($_REQUEST['title'])."',  
WHERE  `jpbms_comics`.`cid` =".(int)$_REQUEST['mycid'].";";

und dann mit mysql_query($sql_query); übergeben.

Wenn in dem Request Umlaute vorkommen (und ich nehme an auch andere Sonderzeichen, "&" funktioniert aber z.B.) dann wird es in unerwünschter Weise abgespeichert. Kippe ich in "translator" den String "Übersetzer" (im Formular) so...
...gebe ich den Query einfach im HTML aus wird er korrekt angezeigt.
...schaue ich mir die Daten mit phpMyAdmin an steht da Ãœbersetzer
...lese ich sie mit meinem PHP aus der Datenbank wieder aus und schicke sie ins HTML (z.B. so: echo '<input type="text" name="translator" id="translator" value="'.$sql_array['translator'].'" />'; werden sie wieder korrekt angezeigt.

Schreibe ich mit PMA in die Datenbank "Übersetzer" (oder kopiere gar den ausgegebenen Query von oben ins PMA) so...
...erscheint mit dem gleichen Code wie oben "�bersetzer" im HTML.
...sieht es im PMA korrekt aus.

Soweit ich das sehe ist das alles nicht soooo tragisch, weil die Codierung scheinbar hin in die DB und zurück durchaus funktioniert.
Problematisch könnte es werden wenn man irgendwas auf der Datenbank direkt ändert (warum auch immer, aber kann ja mal nötig sein), dann kann man keine Umlaute verwenden oder muss sich irgendwie die entsprechenden Zeichen erst zusammen kramen.

Also ich habe irgendwo Zeichensatz-Probleme aber ich weiß nicht wo. Kann mir jemand sagen wo ich suchen kann/soll. Oder kann aus den Angaben gar erkennen was bei mir schief läuft?

Dankeschön und falls man sich nicht mehr liest: Guten Rutsch!

  1. Tach!

    ich bin nicht sicher ob die Kategorie "Datenbank" korrekt ist, denn möglicherweise ist es auch ein HTTP-Problem oder ein Browser-Problem oder gar ein HTML-Problem.
    Ich habe nämlich Schwierigkeiten Umlaute aus einem Formular in meine Datenbank und zurück zu bringen.

    Zwei Grundregeln beim Umgang mit Zeichenkodierungen gibt es:

    • Jedes System muss mit der gewählten Kodierung umgehen können, oder es reicht die Daten nur unverändert durch.
    • An jeder Schnittstelle zwischen zwei Systemen muss dem nachfolgenden System mitgeteilt werden, in welcher Kodierung die Daten vorliegen (oder zurückgeliefert werden sollen).

    Alle meine Ausgaben werden ebenfalls als UTF-8 ausgeliefert (jede Seite die Ausgaben erzeugt beginnt mit header('Content-Type: text/html; charset=utf-8'); und nahezu jede Ausgabe beginnt mit <!DOCTYPE html><html><head><title></title><style></style></head>(...))

    Wie das HTML beginnt, ist weniger interessant, wichtiger ist die Kodierungsangabe im Header. Die Angabe im HTTP-Header sticht zwar, wenn sie vorhanden ist, aber das ist sie nicht immer. Beim lokalen Speichern ist sie zum Beispiel nicht mehr da. Das ist zwar für dein aktuelles Problem nicht relevant, aber wenn du schonmal beim Korrigieren bist ...

    Mein Eingabeformular ist so definiert: <form action="./backend.php" method="post">

    Hier könnte noch ein accept-charset-Attribut hinzugefügt werden, aber zumindest einige ältere Browser ignorieren das. Üblicherweise reicht die Kodierungsangabe im HTTP-Header oder HTML-Head.

    mein SQL wird (beispielsweise) so erzeugt: [...]
    und dann mit mysql_query($sql_query); übergeben.

    Und Regel 2? Wird immer wieder gern nicht beachtet. Woher soll MySQL wissen, was du ihm da übergibst?

    Wenn in dem Request Umlaute vorkommen (und ich nehme an auch andere Sonderzeichen, "&" funktioniert aber z.B.)

    Alles jenseits von ASCII. & ist in ASCII enthalten.

    ...gebe ich den Query einfach im HTML aus wird er korrekt angezeigt.
    ...schaue ich mir die Daten mit phpMyAdmin an steht da Ãœbersetzer

    Das ist ein typischer Fall von: MySQL weiß nichts von deiner Kodierung und nimmt einen Default-Wert an. Wenn dieser und die Angabe der Feldkodierung unterschiedlich sind, kodiert es selbständig um. Da der PMA beide Regeln beachtet, sieht er korrekterweise das Ergebnis der ungewollten Umkodierung.

    ...lese ich sie mit meinem PHP aus der Datenbank wieder aus und schicke sie ins HTML (z.B. so: echo '<input type="text" name="translator" id="translator" value="'.$sql_array['translator'].'" />'; werden sie wieder korrekt angezeigt.

    Da fehlt noch die Kontextwechselbeachtung, also ein htmlspecialchars(). Ansonsten hebt hier derselbe Fehler beim Hin- und Rückweg das Problem teilweise wieder auf.

    Soweit ich das sehe ist das alles nicht soooo tragisch, weil die Codierung scheinbar hin in die DB und zurück durchaus funktioniert.
    Problematisch könnte es werden wenn man irgendwas auf der Datenbank direkt ändert (warum auch immer, aber kann ja mal nötig sein), dann kann man keine Umlaute verwenden oder muss sich irgendwie die entsprechenden Zeichen erst zusammen kramen.

    Ja, dieses Problem bleibt und hinzu kommt, dass du keine gescheiten Stringoperationen im DBMS anwenden kannst. Nichtmal Sortieren geht richtig.

    Also ich habe irgendwo Zeichensatz-Probleme aber ich weiß nicht wo. Kann mir jemand sagen wo ich suchen kann/soll. Oder kann aus den Angaben gar erkennen was bei mir schief läuft?

    Derartige Probleme kommen recht häufig vor, weswegen ich im SELFHTML-Wiki einen Themenschwerpunkt zur Zeichenkodierung angefangen habe. Die wichtigsten für dich relevanten Teile sind schon ausgearbeitet.

    dedlfix.

    1. Wie das HTML beginnt, ist weniger interessant, wichtiger ist die Kodierungsangabe im Header. Die Angabe im HTTP-Header sticht zwar, wenn sie vorhanden ist, aber das ist sie nicht immer. Beim lokalen Speichern ist sie zum Beispiel nicht mehr da. Das ist zwar für dein aktuelles Problem nicht relevant, aber wenn du schonmal beim Korrigieren bist ...

      Joa, mal sehen... eigentlich ginge ich davon aus, dass der Browser die Datei dann auch im entsprechenden Zeichensatz speichert.
      Aber man kann es rein schreiben, sicherlich (ich mag das meta-Element nur irgendwie nicht).
      Ich hab das nur dazu geschrieben weil es ja auch "irgendwas mit Zeichensatz" zu tun hat und wollte die möglicherweise aufquellende Frage vorab beantworten. (wie sich das gehört in technischen Foren, alle möglicherweise relevanten Infos geben)
      Als "relevant" sah ich hier an "alles was mit Charsets zu tun hat".

      Hier könnte noch ein accept-charset-Attribut hinzugefügt werden, aber zumindest einige ältere Browser ignorieren das. Üblicherweise reicht die Kodierungsangabe im HTTP-Header oder HTML-Head.

      Ah okay, ich schau's mir an. (ältere Browser... pfff ich berücksichtige normalerweise Textbrowser und Mobilbrowser aber halbwegs aktuell sollten sie sein)

      Und Regel 2? Wird immer wieder gern nicht beachtet. Woher soll MySQL wissen, was du ihm da übergibst?

      Nun es hätte durchaus sein können, dass der Parser das von selbst tut oder dass SQL davon ausgeht, dass es den Zeichensatz kriegt, den ich dort eingestellt habe.

      Da fehlt noch die Kontextwechselbeachtung, also ein htmlspecialchars(). Ansonsten hebt hier derselbe Fehler beim Hin- und Rückweg das Problem teilweise wieder auf.

      Stimmt, bei manchen Feldern muss ich das machen, aber in den meisten kommt kein <>&" vor.
      Du hast aber Recht, beim Schreiben in die DB bzw. auch allen Abfragen escape ich aus Sicherheitsgründen (wenn es geht mit (int)) dann sollte man es beim Auslesen genauso konsequent tun XD

      Derartige Probleme kommen recht häufig vor, weswegen ich im SELFHTML-Wiki einen Themenschwerpunkt zur Zeichenkodierung angefangen habe. Die wichtigsten für dich relevanten Teile sind schon ausgearbeitet.

      Ah cool danke, tatsächlich hat dies hier geholfen:

      $db_link = mysql_connect ($db_data['host'], $db_data['user'], $db_data['password']);  
      if ($db_link) {  
      	mysql_select_db( $db_data['database'] );  
      	mysql_set_charset('utf8');  
      }
      

      Also die vorletzte Zeile ist freilich neu.

      dedlfix.

      Vielen Dank und frohes neues Jahr!

      1. @@Deus F (noreg):

        nuqneH

        Joa, mal sehen... eigentlich ginge ich davon aus, dass der Browser die Datei dann auch im entsprechenden Zeichensatz speichert.

        Der Zeichensatz ist für alle HTML-Dateien derselbe: Unicode.

        Zum Verständnis der Problematik ist es wichtig, zwischen Zeichensatz und Zeichencodierung zu unterscheiden.

        Qapla'

        --
        Gut sein ist edel. Andere lehren, gut zu sein, ist noch edler. Und einfacher.
        (Mark Twain)
      2. Tach!

        Wie das HTML beginnt, ist weniger interessant, wichtiger ist die Kodierungsangabe im Header. Die Angabe im HTTP-Header sticht zwar, wenn sie vorhanden ist, aber das ist sie nicht immer. Beim lokalen Speichern ist sie zum Beispiel nicht mehr da. Das ist zwar für dein aktuelles Problem nicht relevant, aber wenn du schonmal beim Korrigieren bist ...
        Joa, mal sehen... eigentlich ginge ich davon aus, dass der Browser die Datei dann auch im entsprechenden Zeichensatz speichert.

        Der Browser speichert zwar die Datei auf der Platte, so wie er sie bekommen hat. Aber beim nächsten Lesen von der Platte, ist kein HTTP-Header mehr vorhanden. Wenn dann keine Angabe im HTML-Head steht, kann der Lesende nur noch raten.

        Aber man kann es rein schreiben, sicherlich (ich mag das meta-Element nur irgendwie nicht).

        Es gibt keine andere Möglichkeit, die Kodierung des Dateiinhalts zu kennzeichnen. Da muss dein Geschmack der Praktikabilität wegen zurückstecken.

        Und Regel 2? Wird immer wieder gern nicht beachtet. Woher soll MySQL wissen, was du ihm da übergibst?
        Nun es hätte durchaus sein können, dass der Parser das von selbst tut oder dass SQL davon ausgeht, dass es den Zeichensatz kriegt, den ich dort eingestellt habe.

        Na klar, aber wo hast du das denn eingestellt? Die Kodierungsangabe eines Feldes ist irrelevant. Jedes Feld kann seine eigene Angabe haben. MySQL nimmt für die Daten, die über eine Verbindung zum Client kommen, einen separaten Konfigurationswert. Und wenn du jedes Datum extra kodieren willst, kannst du sogar jeden String-Wert einzeln kennzeichnen (braucht man normalerweise nicht).

        Da fehlt noch die Kontextwechselbeachtung, also ein htmlspecialchars().
        Stimmt, bei manchen Feldern muss ich das machen, aber in den meisten kommt kein <>&" vor.

        Es sollte egal sein, ob etwas aktuell vorkommt oder nicht. Mach es einfach, das kostet so gut wie nichts und spart dir in der Zukunft eine Neubetrachtung, wenn sich irgendwas ändert. Oder bist du bei jeder Änderung nach geraumer Zeit so konzentriert und immer noch wissend, welche genauen Auswirkungen welches Drehen an einem bestimmten Rädchen hat?

        dedlfix.

        1. Der Browser speichert zwar die Datei auf der Platte, so wie er sie bekommen hat. Aber beim nächsten Lesen von der Platte, ist kein HTTP-Header mehr vorhanden. Wenn dann keine Angabe im HTML-Head steht, kann der Lesende nur noch raten.

          Okay, irgendwie hatte ich wohl angenommen, dass es darüber Metadaten (wie Erstellungs- und Änderungsdatum etc.) gibt. Denn meistens wenn ich eine Textdatei in einem Editor öffne weiß dieser wie sie kodiert ist, auch wenn ich sie mit einem anderen Editor erstellt habe.
          Aber stimmt klappt auch nicht immer. Entweder die Editoren raten sehr gut oder es kommt aufs Dateisystem an (keine Ahnung was die Inhaltstabellen da jeweils erlauben) oooder manche Editoren legen es irgendwie ab und manche nicht XD

          Es gibt keine andere Möglichkeit, die Kodierung des Dateiinhalts zu kennzeichnen. Da muss dein Geschmack der Praktikabilität wegen zurückstecken.

          ACK

          Nun es hätte durchaus sein können, dass der Parser das von selbst tut oder dass SQL davon ausgeht, dass es den Zeichensatz kriegt, den ich dort eingestellt habe.

          Na klar, aber wo hast du das denn eingestellt? Die Kodierungsangabe eines Feldes ist irrelevant. Jedes Feld kann seine eigene Angabe haben. MySQL nimmt für die Daten, die über eine Verbindung zum Client kommen, einen separaten Konfigurationswert. Und wenn du jedes Datum extra kodieren willst, kannst du sogar jeden String-Wert einzeln kennzeichnen (braucht man normalerweise nicht).

          Auf drei Ebenen habe ich utf-8 eingestellt:
          allgemeine Variablen:
          character set client, character set connection, character set results, character set system, collation connection (hier allerdings utf8_general_ci)
          Zweite Ebene, die "Kollation" der Tabelle und
          dritte Ebene, auch die "Kollation" der jeweiligen Datenfelder.

          Ich muss aber zugeben, dass bei den Variablen hier und da auch "latin" oder "latin1_swedish_ci" steht. (v.a. unter "Globaler Wert"). Ich kenne mich mit SQL schon mehr schlecht als Recht aus, die Tiefen von MySQL verstehe ich nun gar nicht :)

          Da fehlt noch die Kontextwechselbeachtung, also ein htmlspecialchars().
          Stimmt, bei manchen Feldern muss ich das machen, aber in den meisten kommt kein <>&" vor.
          Es sollte egal sein, ob etwas aktuell vorkommt oder nicht. Mach es einfach, das kostet so gut wie nichts und spart dir in der Zukunft eine Neubetrachtung, wenn sich irgendwas ändert. Oder bist du bei jeder Änderung nach geraumer Zeit so konzentriert und immer noch wissend, welche genauen Auswirkungen welches Drehen an einem bestimmten Rädchen hat?

          Jaaa, ich stimme dir doch zu :) und NATÜRLICH verstehe ich meinen Code von vor 10 Jahren, du etwa nicht? ^^

          Liebe Grüße

          1. Tach!

            Der Browser speichert zwar die Datei auf der Platte, so wie er sie bekommen hat. Aber beim nächsten Lesen von der Platte, ist kein HTTP-Header mehr vorhanden. Wenn dann keine Angabe im HTML-Head steht, kann der Lesende nur noch raten.
            Okay, irgendwie hatte ich wohl angenommen, dass es darüber Metadaten (wie Erstellungs- und Änderungsdatum etc.) gibt. Denn meistens wenn ich eine Textdatei in einem Editor öffne weiß dieser wie sie kodiert ist, auch wenn ich sie mit einem anderen Editor erstellt habe.

            Mir ist kein Dateisystem bekannt, das solche Angaben zum Inhalt festhält. Die Editoren raten anhand bestimmter Merkmale. Es ist wenig wahrscheinlich, dass Bytewerte, die gültige UTF-8-Sequenzen ergeben, in einem sinnvollen ISO-8859-1-Text stehen. Wenn also Bytes größer als 0x7F vorkommen und nur gültige UTF-8-Sequenzen ergeben, wird es wohl ein UTF-8-kodierter Text sein. UTF-16 und -32 erkennen sie an der notwendigen BOM. Auch eine UTF-8-BOM ist ein starker Indikator für einen UTF-8-Text. Aber die UTF-8-BOM ist keine Pflicht und im Web eher störend, also meist nicht vorhanden.

            Aber stimmt klappt auch nicht immer. Entweder die Editoren raten sehr gut oder es kommt aufs Dateisystem an (keine Ahnung was die Inhaltstabellen da jeweils erlauben) oooder manche Editoren legen es irgendwie ab und manche nicht XD

            Spätestens zwischen den ISO-8859-x-Kodierungen (mit Ausnahme von x=5,6,7,8,11) lässt sich nur schwer anhand des Inhaltes erraten, welche Kodierung gemeint sein soll. Da muss der Editor schon mit Wörterbüchern antanzen, um seine Trefferquote zu erhöhen. Die Kodierung merken können sich die Editoren nur in irgendeiner privaten Ablage und wenn sie die Datei kennen sowie die Angaben zur Kodierung, die du gemacht hast. Ansonsten gibt es, wie gesagt, kein allgemeingültiges System zur Ablage außerhalb der Datei.

            Auf drei Ebenen habe ich utf-8 eingestellt:
            allgemeine Variablen:
            character set client, character set connection, character set results, character set system, collation connection (hier allerdings utf8_general_ci)

            Da gibt es Default-Werte und selbst eingestellte. Da ein Programm auf die Defaultwerte üblicherweise keinen Einfluss hat und diese auch nicht kennt, ist es effektiver und effizienter, wenn es die Kodierung auf der Verbindung individuell aushandelt (statt sich auf irgendetwas zu verlassen. Abfragen ist auch umständlicher als ein Setzen.)

            Zweite Ebene, die "Kollation" der Tabelle und

            Das ist nur eine Default-Angabe für neu anzulegende Felder, wenn bei ihnen nichts konkretes angegeben wird.

            dritte Ebene, auch die "Kollation" der jeweiligen Datenfelder.

            Das ist ein relevanter Wert, der für das Ablegen und für Verarbeitungen herangezogen wird. Die Kommunikation nach außen ist aber eine andere Baustelle. Wenn man mal annimmt, dass diese Feldkodierungsangabe für die Client-Verbindung relevant sein sollte, so müsste bei jedem Datenverkehr der Client prüfen, welche Kodierung dieses Feld hat. Beim Senden müsste er das vorher machen und dann seine Daten entsprechend kodieren - vom TOCTTOU-Problem ganz zu schweigen. Es kann ja jemand anderes zwischendurch umgestellt haben. Solch eine Vorgehensweise wäre völlig unpraktisch. Deshalb Regel 2: explizit aushandeln.

            Ich muss aber zugeben, dass bei den Variablen hier und da auch "latin" oder "latin1_swedish_ci" steht. (v.a. unter "Globaler Wert"). Ich kenne mich mit SQL schon mehr schlecht als Recht aus, die Tiefen von MySQL verstehe ich nun gar nicht :)

            Der globale Wert ist die Systemkonfiguration. Der wird session-individuell überschrieben durch beispielsweise mysql_set_charset() oder ein SET NAMES. Wenn du das übrigens mit dem phpMyAdmin kontrollierst, so gelten für dich ebenfalls nur die globalen Werte, denn lokale Werte hat er sich selbst für seine eigenen Belange gesetzt. Diese gehen wie alle individuellen Werte am Session-Ende, also spätestens am Script-Ende, ihren Weg zum Garbage Collector.

            und NATÜRLICH verstehe ich meinen Code von vor 10 Jahren, du etwa nicht? ^^

            Ich habe schon Schwierigkeiten nach 10 Wochen wieder reinzukommen, besonders weil ich mir nie notiere, was eigentlich noch die offenen Baustellen sind. Bei abgeschlossenen und länger zurückliegenden Projekten sollen eigentlich Kommentare helfen, aber das was ich damals als selbstverständlich und nicht dokumentierenswert eingestuft hatte, ist mittlerweile doch in Vergessenheit geraten. Verstehen tue ich das alles wieder - aber die Einarbeitungszeit geht mir für aktuelle Dinge verloren.

            dedlfix.