Mastershrimp: Selbstgebautes htmlentities() scheitert an Sonderzeichen + UTF-8

Heyho!

Zugegeben, ein etwas spezielleres Thema. Vielleicht denke ich auch einfach zu kompliziert. Ich erklärs mal in Ruhe:

  1. Ziel:
    Ich habe ein Formular, das den WYSIWYG-Editor "TinyMCE" einsetzt. In dieses Feld soll man beliebige Zeichen (auch Sonderzeichen wie das Pik/Karo/Herz-Zeichen und Umlaute) eintragen können. Diese landen beim Abspeichern in der Datenbank. Dort finde ich, sollten sie nicht maskiert landen, weil man sonst Probleme bekommt, wenn man nach DB-Inhalten sucht (macht Sinn, oder?).
    Also habe ich TinyMCE so eingestellt, dass es nix maskiert und den Inhalt einfach so an die DB weiterreicht. Funktioniert super.

Der Haken ist halt nun, dass bei der Ausgabe des Inhalts dieser maskiert werden muss. Hierfür habe ich zum einen die standardmäßige htmlentities(), die ja dank des letzten Parameters auch wunderbar mit UTF-8 umgehen kann. Für 90% meiner Inhalte reicht das auch.

Eine Ausnahme stellen TinyMCE-Inhalte dar, da diese ja HTML enthalten, was ja bei htmlentities() dann ebenfalls maskiert werden würde. Daher entschied ich mich, eine eigene Methode (quasi "htmlentitiesHTML()" ;-) ) zu bauen, die im wesentlichen wie htmlentities() funktioniert, ohne < > " ' zu maskieren. Dazu noch ein strip_tags() um böse Tags herauszuhauen und fertig.

In der Theorie super, in der Praxis scheitert's leider :/

  1. Problem:
    Hier mein erster Ansatz:
  
// --- Encode  
// Perform all actions of htmlentities() except replacing some special characters (see below)  
// FIXME  
// Convert temporarily  
$content = iconv("UTF-8", "ISO-8859-1", $content);  
  
// Remove html tags which are not allowed  
$content = strip_tags($content, ALLOWED_HTML);  
  
// Create $search and $replace, without the following characters: > < " ' and blanks  
$htmlentitiesTable = get_html_translation_table(HTML_ENTITIES, ENT_NOQUOTES);  
$search = array("&");		// Note: "&" is already in this array because all "&" need to be replaced at first! Otherwise the tags would be double-encoded  
$replace = array("&amp;");  
foreach($htmlentitiesTable as $key => $value) {  
	if($key!="<" && $key!=">" && $key!=" " && $key!="" && $key!="&") {  
		$search[]	= $key;  
		$replace[]	= $value;  
	}  
}  
$content = str_replace($search, $replace, $content);  
  
// Convert back to UTF8  
$content = iconv("ISO-8859-1", "UTF-8", $content);  
  
return $content;  

Ich konvertiere also zuerst den String in einen Zeichensatz, den PHP bedienen kann, mache die gewohnten String-Operationen und konvertiere anschließend zurück. Dummerweise wird der String vor dem ersten Sonderzeichen abgeschnitten. Irgendwo in der Docu stand, dass PHP das macht, wenn es ein Zeichen findet, das für den aktuellen Zeichensatz nicht gültig ist.
Lustigerweise werden Umlaute korrekt maskiert. Lediglich Sonderzeichen wie Karo/Pik/Herz oder das Unendlich-Zeichen zwingen die Funktion zum Abbruch.

Wenn ich statt "ISO-8859-1" "ASCII" nehme, bricht die Funktion auch bei Umlauten ab.

Und hier noch ein zweiter:

  
// Create $search and $replace, without the following characters: > < " ' and blanks  
$htmlentitiesTable = get_html_translation_table(HTML_ENTITIES, ENT_NOQUOTES);  
$search = array("&");		// Note: "&" is already in this array because all "&" need to be replaced at first! Otherwise the tags would be double-encoded  
$replace = array("&amp;");  
foreach($htmlentitiesTable as $key => $value) {  
	if($key!="<" && $key!=">" && $key!=" " && $key!="" && $key!="&") {  
		$search[]	= $key;  
		$replace[]	= $value;  
	}  
}  
  
mb_regex_encoding("UTF-8");  
for($i=0; $i<count($search); $i++)  
	$content = mb_ereg_replace($search[$i], $replace[$i], $content);  
  
return $content;  

Ergebnis: Nix wird maskiert. Der Inhalt wird einfach unmaskiert ausgegeben.

Ich hoffe ich hab euch jetzt mit dieser ausführlichen Beschreibung nicht erschlagen ;-)
Habt ihr ne Idee wie ich das Problem lösen könnte? Mir gehen langsam die Ideen aus....

Achja, vielleicht sollte ich noch erwähnen dass alle Dateien natürlich in UTF-8 gespeichert sind und auch sonst alles (Header, DB-Connection, ...) auf "UTF-8" steht. Die Ausgabe mit htmlentities() klappt ja auch soweit.

Viele Grüße

Mastershrimp

  1. Hi Mastershrimp,

    Zugegeben, ein etwas spezielleres Thema. Vielleicht denke ich auch einfach zu kompliziert.

    Warum willst du überhaupt irgendwelche Zeichen kodieren? Das schöne an UTF-8 ist doch, dass du dann keine Zeichen mehr in (X)HTML kodieren musst.

    // Convert temporarily

    $content = iconv("UTF-8", "ISO-8859-1", $content);

      
    An dieser Stelle verlierst du potentiell Informationen, denn nicht alles was du mit UTF-8 darstellen kannst, kannst du auch mit ISO-8859-1 darstellen. Du kannst zwar mit [//TRANSLIT](http://de3.php.net/manual/en/function.iconv.php)] versuchen, die Zeichen zu approximieren, aber das ändert nichts daran, das die ursprügnliche Information verloren ist. In deinem Fall wird dann sogar der Rest des Strings ab dem ersten unbekannten Zeichen noch abgeschnitten, weil du //IGNORE nicht verwendest.  
      
      
    Viele Grüße,  
      ~ Dennis.
    
    -- 
    Mein [SelfCode](http://community.de.selfhtml.org/fanprojekte/selfcode.htm): [ie:{ fl:( br:> va:) ls:\[ fo:) rl:( n4:# ss:) de:\] js:| ch:{ sh:| mo:} zu:|](http://www.peter.in-berlin.de/projekte/selfcode/?code=ie%3A%7B+fl%3A%28+br%3A%3E+va%3A%29+ls%3A%5B+fo%3A%29+rl%3A%28+n4%3A%23+ss%3A%29+de%3A%5D+js%3A%7C+ch%3A%7B+sh%3A%7C+mo%3A%7D+zu%3A%7C)
    
    1. Heyho!

      Danke für die schnelle Antwort!

      Warum willst du überhaupt irgendwelche Zeichen kodieren? Das schöne an UTF-8 ist doch, dass du dann keine Zeichen mehr in (X)HTML kodieren musst.

      Puh, ja, gute Frage ;-) Ich ging halt davon aus, dass man das einfach so macht... ;-) Bin durch das ganze UTF-8-Thema noch nicht 100% durchgestiegen ;-)

      »» ~~~php

      // Convert temporarily

      »» $content = iconv("UTF-8", "ISO-8859-1", $content);

      
      >   
      > An dieser Stelle verlierst du potentiell Informationen, denn nicht alles was du mit UTF-8 darstellen kannst, kannst du auch mit ISO-8859-1 darstellen.  
        
      Ok, das macht Sinn.  
        
      Was empfiehlst du denn dann? Einfach nur unmaskiert ausgeben? Hat das keine Nachteile? Wäre mir persönlich auch am liebsten.  
        
      strip\_tags() würde ich natürlich sicherheitshalber in der Ausgabeverarbeitung drin lassen, damit kein unerwünschtes HTML benutzt werden kann.  
        
        
      Gruß  
        
      Mastershrimp
      
  2. Moin!

    Ich habe ein Formular, das den WYSIWYG-Editor "TinyMCE" einsetzt.

    Dieser Editor kann, da er auf Javascript basiert, prima mit Unicode umgehen. Muss gar nicht separat eingebaut werden.

    In dieses Feld soll man beliebige Zeichen (auch Sonderzeichen wie das Pik/Karo/Herz-Zeichen und Umlaute) eintragen können.

    "Dieses Feld" ist eine Textarea, die TinyMCE beim Start entsprechend seiner Bedürfnisse umbaut. In die Textarea gehört der HTML-Quellcode, der zu bearbeiten ist, und zwar in der Form, wie er in Textareas zu landen hat: Als Resultat von htmlspecialchars(). Und diese Funktion macht mit UTF-8 auch keinerlei Probleme, da sie nur die Zeichen codiert, die sich auch im ASCII-Bereich befinden.

    Der Haken ist halt nun, dass bei der Ausgabe des Inhalts dieser maskiert werden muss. Hierfür habe ich zum einen die standardmäßige htmlentities(), die ja dank des letzten Parameters auch wunderbar mit UTF-8 umgehen kann. Für 90% meiner Inhalte reicht das auch.

    htmlentities ist definitiv die falsche Funktion, wenn du UTF-8 einsetzt. Egal was der Encoding-Parameter dir verspricht.

    Eine Ausnahme stellen TinyMCE-Inhalte dar, da diese ja HTML enthalten, was ja bei htmlentities() dann ebenfalls maskiert werden würde. Daher entschied ich mich, eine eigene Methode (quasi "htmlentitiesHTML()" ;-) ) zu bauen, die im wesentlichen wie htmlentities() funktioniert, ohne < > " ' zu maskieren. Dazu noch ein strip_tags() um böse Tags herauszuhauen und fertig.

    Kann ich nicht nachvollziehen. Entweder der gespeicherte HTML-String geht unmaskiert raus, oder er wird durch htmlspecialchars() geschleust. Nur eine dieser beiden Möglichkeiten ist sinnvoll, wenn UTF-8 benutzt wird, und welche Möglichkeit das ist, hängt davon ab, in welchem Kontext der entstehenden HTML-Seite der String benutzt wird. Wird er direkt als HTML-Quellcode benutzt, ist htmlspecialchars() natürlich falsch. Wird er als Inhalt einer Textarea benutzt, ist htmlspecialchars() richtig.

    Ich konvertiere also zuerst den String in einen Zeichensatz, den PHP bedienen kann,

    Und dabei zerstörst du dir alle Zeichen, die in ISO-8859-1 nicht enthalten sind - das sind die meisten Unicode-Zeichen!

    Warum das Gehampel mit dem Umcodieren? Kostet nur Zeit und macht Ärger!

    Lustigerweise werden Umlaute korrekt maskiert. Lediglich Sonderzeichen wie Karo/Pik/Herz oder das Unendlich-Zeichen zwingen die Funktion zum Abbruch.

    Weil das Zeichen sind, die in ISO-8859-1 nicht vorkommen. Also zerstört werden, und nicht wiederherstellbar sind.

    Wenn ich statt "ISO-8859-1" "ASCII" nehme, bricht die Funktion auch bei Umlauten ab.

    ASCII enthält nur Zeichen vom Code 0 bis 127. Umlaute sind darin nicht enthalten, die werden auch zerstört.

    Ich hoffe ich hab euch jetzt mit dieser ausführlichen Beschreibung nicht erschlagen ;-)
    Habt ihr ne Idee wie ich das Problem lösen könnte? Mir gehen langsam die Ideen aus....

    Du maskierst zuviel, du codierst zuviel um. Nutze UTF-8 doch einfach. ;) Dann gibts keine Notwendigkeit zu ISO-8859-1 zurückzukehren.

    - Sven Rautenberg

    1. Heyho!

      Super, gleich noch mehr Antworten :) Danke!

      Also ihr seid euch in einem einig: htmlentities() ist unnötig wenn man UTF-8 einsetzt. Das wusste ich noch gar nicht. Wie in der Antwort auf das Posting von Dennis gesagt, ging ich fälschlicherweise davon aus, dass "man das halt so macht" ;-)

      Ok, damit ich sicher bin, dass ich alles richtig verstanden habe, bitte folgende Statements einmal absegnen:

      [Exkurs, nur damit ihr wisst wie ich die Funktionen atm einsetze: Wird ein Formularfeld von PHP vorbelegt (à la <input type="text" name="feldname" value="$value" />), durchläuft $value momentan htmlentities() mit dem UTF-8-Parameter. In der Datenbank wird der eingegebene Inhalt plain als UTF-8 gespeichert. Also ohne Maskierungen. Die Ausgabeverarbeitung hängt davon ab, ob HTML gewollt ist (-> Quelle: TinyMCE) oder nicht (normales Texteingabefeld). In ersterem Fall wird bisher meine Spezialmethode "htmlentitiesHTML()" benutzt (s.o.), in letzterem das normale htmlentities() mit dem UTF-8-Parameter. Ausgegebene Inhalte sollen als Quellcode interpretiert werden, da das ganze für ein CMS ist. Exkurs Ende]

      1. Es macht in oben beschriebenem Szenario nirgendwo Sinn, htmlentities() einzusetzen, da ich alles in UTF-8 vorliegen habe.

      2. Ich sollte jedes htmlentities() durch htmlspecialchars() ersetzen (Da nur für das Befüllen von Formularfeldern gebraucht bzw für die Ausgabe von Inhalt, der definitiv kein HTML enthalten darf/wird)

      3. Ich sollte meine Spezialfunktion "htmlentitiesHTML()" dahingehend reduzieren, dass nur die ungewollten Tags per strip_tags() eliminiert werden. Maskierungen per htmlentities() oder htmlspecialchars() machen keinen Sinn, da Inhalt als HTML interpretierbar bleiben soll.

      Vielen Dank euch allen schonmal bis hierhin und viele Grüße

      Mastershrimp

      1. Moin!

        Ok, damit ich sicher bin, dass ich alles richtig verstanden habe, bitte folgende Statements einmal absegnen:

        Nö. Deine Statements sind mir zu allgemein.

        Entscheidend für die Wahl der korrekten Codierung der Ausgabe ist, in welchem Kontext sie eingesetzt wird.

        Gib mal konkret an, in welche HTML-Konstrukte du auf welche Weise deine aus PHP kommenden Strings einbauen willst, und als was du deren Inhalt verstanden haben möchtest. Dann kann man dir auch sagen, wie das zu codieren ist.

        1. Es macht in oben beschriebenem Szenario nirgendwo Sinn, htmlentities() einzusetzen, da ich alles in UTF-8 vorliegen habe.

        Das stimmt allemal. htmlentities() ist eine überflüssige Funktion. Das bedeutet aber nicht, dass eine andere Funktion wie z.B. htmlspecialchars() nicht doch notwendig ist.

        htmlentities() ist übrigens auch dann überflüssig, wenn man konsistent durchgehend auf ISO-8859-1 oder sonst eine einheitliche Codierung setzt - denn man wird es ja dann auch immer nur mit Zeichen zu tun haben, die man innerhalb dieser gewählten Codierung darstellen kann, so dass es der Entities nicht bedarf.

        1. Ich sollte jedes htmlentities() durch htmlspecialchars() ersetzen (Da nur für das Befüllen von Formularfeldern gebraucht bzw für die Ausgabe von Inhalt, der definitiv kein HTML enthalten darf/wird)

        Das wäre auch keine schlechte Idee, hängt aber vom Kontext ab: Wo htmlspecialchars() nicht gebraucht wird, wird htmlentities() auch nicht gebraucht.

        1. Ich sollte meine Spezialfunktion "htmlentitiesHTML()" dahingehend reduzieren, dass nur die ungewollten Tags per strip_tags() eliminiert werden. Maskierungen per htmlentities() oder htmlspecialchars() machen keinen Sinn, da Inhalt als HTML interpretierbar bleiben soll.

        Das sind zwei Paar Schuhe: Das eine ist die Validierung und Säuberung des Codes, der aus dem TinyMCE kommt, das andere ist die Codierung der Ausgabe entsprechend Kontext.

        - Sven Rautenberg

        1. Heyho!

          »» Ok, damit ich sicher bin, dass ich alles richtig verstanden habe, bitte folgende Statements einmal absegnen:

          Nö. Deine Statements sind mir zu allgemein.

          Entscheidend für die Wahl der korrekten Codierung der Ausgabe ist, in welchem Kontext sie eingesetzt wird.

          Ja, das dachte ich hätte ich in dem "Exkurs" ausführlich genug erklärt. Aber ok, dann noch ausführlicher ;-)

          Das ganze ist wie gesagt ein CMS, wo im Backend der Benutzer Inhalt eingeben kann, der dann im Frontend angezeigt wird (ok, so läuft's wohl meistens ;-) ). Dabei arbeitet der User (vereinfacht gesagt) "Boxweise", sprich, er kann Inhaltsboxes auf einer Seite anlegen. Die haben einen Titel und einen Body. Der Titel darf kein HTML enthalten (der Text wird einfach in eine <h1>-Überschrift gepackt), der Body darf hingegen formatiert werden (mit den Bordmitteln von TinyMCE).
          Die Ausgabe ist template-basiert, mit Smarty, aber das ist wohl hier eher egal.

          Nach dem, was ich hier gehört habe, würde die Ausgabe in etwa so ablaufen (wie gesagt, in echt ist's template-basiert, sodass ich kein HTML/PHP-Gemisch habe)

            
          <div>  
          <h1><?php echo(htmlspecialchars($divTitle)); ?></h1>  
          <?php echo(strip_tags($divBody, ALLOWED_HTML); ?>  
          </div>  
          
          

          Der Body darf die gängigen Textformatierungs-Tags enthalten (fett, kursiv, unterstrichen, farbig, Listen, Tabellen). Der Rest wird weg-gestrippt.

          Die dazugehörigen Formularfelder erhalten beim Editieren die alten Werte über htmlspecialchars:

            
          <input type="text" name="divTitle" id="divTitle" value="<?php echo(htmlspecialchars($divTitle)); ?>" />  
          <textarea class="tinyMCE" name="divBody" id="divBody"><?php echo(htmlspecialchars($divBody)); ?></textarea>  
          
          

          In der DB wird alles unmaskiert gespeichert, wie bereits gesagt.

          Brauchst du noch mehr Informationen? Das oben ist nur Beispielcode, in Wahrheit ist das etwas komplizierter. Aber ist hier nicht wichtig ;-)

          Warum kann man nicht für meinen Fall generell sagen, dass
          * htmlspecialchars() benutzt werden soll, wenn man HTML nicht interpretiert haben möchte (im divTitle und bei den values der Formularfelder)

          * diese Spezialmethode mit strip_tags() benutzt werden soll, wenn vom Benutzer eingegebenes HTML ausgegeben und interpretiert werden soll (wie beim divBody)

          So würde das für mich Sinn machen.

          »» 3) Ich sollte meine Spezialfunktion "htmlentitiesHTML()" dahingehend reduzieren, dass nur die ungewollten Tags per strip_tags() eliminiert werden. Maskierungen per htmlentities() oder htmlspecialchars() machen keinen Sinn, da Inhalt als HTML interpretierbar bleiben soll.

          Das sind zwei Paar Schuhe: Das eine ist die Validierung und Säuberung des Codes, der aus dem TinyMCE kommt, das andere ist die Codierung der Ausgabe entsprechend Kontext.

          Das ist mir klar. Ich wollte das ganze Thema auf die "Anwendungsebene" hochziehen, also was für mich gerade am meisten Sinn macht. Ach das passt schon, oben hab ichs ja nochmal etwas weiter ausformuliert ;-)

          Viele Grüße

          Mastershrimp

          1. Moin!

            Ja, das dachte ich hätte ich in dem "Exkurs" ausführlich genug erklärt. Aber ok, dann noch ausführlicher ;-)

            Nicht ausführlicher - konkreter. :)

            <div>
            <h1><?php echo(htmlspecialchars($divTitle)); ?></h1>
            <?php echo(strip_tags($divBody, ALLOWED_HTML); ?>
            </div>

              
            
            > ~~~php
              
            
            > <input type="text" name="divTitle" id="divTitle" value="<?php echo(htmlspecialchars($divTitle)); ?>" />  
            > <textarea class="tinyMCE" name="divBody" id="divBody"><?php echo(htmlspecialchars($divBody)); ?></textarea>  
            > 
            
            

            Das ist so, wie es angewendet ist, alles vollkommen ok. Vielleicht bis auf die Tatsache, dass echo keine Funktion ist, und deshalb keine Klammern um die auszugebenden Bestandteile notwendig sind - ich würde sie immer weglassen, Klammern hat man in PHP schon so genug.

            In der DB wird alles unmaskiert gespeichert, wie bereits gesagt.

            Also auch unmaskierter HTML-Quellcode. Ist ja aber nicht schlimm - auch als UTF-8 nicht.

            Warum kann man nicht für meinen Fall generell sagen, dass
            * htmlspecialchars() benutzt werden soll, wenn man HTML nicht interpretiert haben möchte (im divTitle und bei den values der Formularfelder)

            Das konnte ich nicht generell sagen, weil du die Ausgabe von Text-in-HTML und HTML-als-HTML gemischt hast.

            * diese Spezialmethode mit strip_tags() benutzt werden soll, wenn vom Benutzer eingegebenes HTML ausgegeben und interpretiert werden soll (wie beim divBody)

            Tja, ob strip_tags so verlässlich ist, ist immer die Frage. Für freundlich gesinnte Benutzer wie die Anwender eines CMS, bei denen man davon ausgehen kann, dass sie an vernünftigen Resultaten ein Interesse haben, kann man sie sicherlich anwenden. Bei Situationen, in denen man befürchten muss, dass man wirklich böse Eingaben erhält, sollte man strip_tags nicht anwenden, sondern einen echten HTML-Parser (sofern man denn unbedingt HTML erlauben will, und nicht irgendeinen Metacode wie z.B. phpBB-Code) drauf ansetzen.

            - Sven Rautenberg

  3. hi,

    malne Frage: wenn Du serverseitig alles in utf-8 hast, warum möchtest Du dann die Specialchars zur Browser-Ausgabe in HTML-Entities umwandeln? Btw., Zeichen in utf-8 haben mehr als eine 8-Bit-Kodierung, das wäre beim Umsetzen eines solchen Vorhabens zu beachten, ansonsten kommt Müll dabei raus.

    Hotte

    --
    ???
  4. Hi,

    Eine Ausnahme stellen TinyMCE-Inhalte dar, da diese ja HTML enthalten, was ja bei htmlentities() dann ebenfalls maskiert werden würde. Daher entschied ich mich, eine eigene Methode (quasi "htmlentitiesHTML()" ;-) ) zu bauen, die im wesentlichen wie htmlentities() funktioniert, ohne < > " ' zu maskieren.

    Und am vierten Parameter von htmlentities gefiel dir was genau nicht?

    (Ok, der kam erst mit 5.2.3 hinzu - aber wer setzt denn noch was älteres ein?)

    MfG ChrisB

    --
    Light travels faster than sound - that's why most people appear bright until you hear them speak.
    1. Heyho!

      Nee also wenn ich den 4. Parameter richtig verstanden habe, verhindert der nur ein doppeltes Maskieren. Maskiert (nämlich 1x) wird immer noch, was ich aber bei < > ' " nicht will. Das ganze scheint aber eh überflüssig, siehe andere Antworten in diesem Thema.

      Dennoch danke

      Viele Grüße

      Mastershrimp