In einem Redaktionssystem gibt es Felder, die reinen Text enthalten sollen: Stichpunkte, Kurz-Fakten, Antworten auf häufige Fragen. Sie werden beim Erzeugen der Seiten ausgegeben – und genau dort entscheidet sich, ob aus einer Angabe Text wird oder Markup.

Der Ausgangsfehler

Ein solches Feld wurde ohne Maskierung ausgegeben. Steht in einem Stichpunkt eine spitze Klammer, führt der Browser sie als Element aus. Bei einer Angabe über eine Auszeichnungssprache passiert das ständig – ein geöffnetes Skript-Element verschluckte in einem Fall den Rest der Zeile.

Die naheliegende Korrektur zerstört Daten

Die erste Idee ist, das Feld zu maskieren. Die zweite, es vorher von Markup zu befreien. Beide sind falsch, sobald zwei Schreibweisen im Bestand vorkommen – und das tun sie, sobald mehr als eine Person schreibt:

KorrekturText mit rohem MarkupText mit Entities
nur maskierenrichtigzeigt die Entity im Klartext
Markup entfernen, dann maskierenAngabe verschwindetrichtig
Entities auflösen, dann maskierenrichtigrichtig

Die mittlere Zeile ist die gefährliche. Aus einer Zeile, die ein Element als Beispiel nennt, wird ein Halbsatz. Kein Fehler, keine Meldung – nur ein Satz, der nicht mehr aussagt, was er aussagen sollte. In einem geprüften Bestand betraf das sechs Einträge.

Die tragfähige Fassung

function text_h(?string $s): string {
    return htmlspecialchars(
        html_entity_decode((string)$s, ENT_QUOTES, 'UTF-8'),
        ENT_QUOTES, 'UTF-8'
    );
}

Erst auflösen, dann maskieren. Beide Schreibweisen kommen richtig heraus, und die Redaktion muss nichts nachziehen – was in einem Bestand mit mehreren hundert Einträgen den Unterschied zwischen einer Korrektur und einem Projekt ausmacht.

Die Regel dahinter

Ein Feld hat genau eine Art der Ausgabe, und die gilt überall. Wo dasselbe Feld an einer Stelle roh und an einer anderen maskiert erscheint, ist es nur eine Frage der Zeit, bis der Unterschied auffällt – meist an einer Seite, die schon lange steht.

Heuristiken sind dabei besonders tückisch: Eine Ausgabe, die anhand des Inhalts entscheidet, ob maskiert wird, verhält sich bei zwei ähnlichen Texten verschieden. Genau so entstand im selben System die Anzeige „<br>" statt eines Zeilenumbruchs.