EmailObfuscator
in package
Obfuscates mailto: links and plain-text email addresses in front-end output. The real address is XOR-hex encoded into a data-cce attribute and restored client-side by the decoder script. Two modes share this class: the content-filter mode (process()) and the full-page-buffer mode (process_page()). See the module-7 and module-8 design specs.
Was NICHT erfasst wird (EO-7, bewusste Grenze): Eine Adresse, die im Quelltext
kein literales „@" enthält — info@example.com, info@example.com,
info@example.com —, bleibt unverändert. Solche Schreibweisen sind
bereits eine Obfuskation des Autors; sie hier aufzulösen hieße, den gesamten
Text durch eine Entity-Dekodierung zu schicken und danach wieder korrekt zu
kodieren, mit deutlich mehr Möglichkeiten, fremdes Markup zu beschädigen, als
der Gewinn rechtfertigt. Der Fall ist fail-open: die Adresse bleibt sichtbar,
die Seite intakt.
Table of Contents
Constants
- SKIP_ELEMENTS = ['script', 'style', 'textarea', 'template']
- Elements whose content is never rewritten.
Methods
- process() : string
- Content-filter callback. Returns the content unchanged when the feature is off or the context is admin/feed; otherwise obfuscates addresses and enqueues the decoder script.
- process_page() : string
- Full-page-buffer entry — obfuscates addresses inside the page <body>.
- element_name() : string
- Lower-cased element name of a start or end tag; '' for declarations.
- encode() : string
- XOR-hex encodes a string: a random key byte followed by each byte of the value XOR-ed with that key, all two-digit hex. The result contains no "@" and no recognisable email structure.
- encode_tag() : string
- Encodes a `mailto:` href inside a single start tag.
- encode_text() : string
- Encodes plain-text addresses in a text segment.
- is_html_space() : bool
- Whether the byte is HTML whitespace (space, tab, LF, FF, CR).
- markup_end() : int|null
- Where the markup starting at `$lt` ends — or NULL when no markup starts there and the `<` is literal text.
- obfuscate() : string
- The shared, markup-aware obfuscation pass.
- raw_text_end() : int
- Offset of the `<` of the closing tag for a raw-text element, or the end of the document when it never closes.
Constants
SKIP_ELEMENTS
Elements whose content is never rewritten.
private
mixed
SKIP_ELEMENTS
= ['script', 'style', 'textarea', 'template']
script/style/textarea sind im HTML-Parser Raw-Text- bzw.
RCDATA-Elemente: ein „<" in ihrem Inhalt beginnt dort kein Tag. Genau das
bildet der Scanner nach — er überspringt bis zum passenden Schluss-Tag.
template steht aus einem anderen Grund hier: sein Inhalt ist eine
Vorlage, die der Decoder nie erreicht (querySelectorAll() greift nicht
in ein Template-Fragment), ein <span data-cce> darin bliebe also für
immer der Platzhaltertext (EO-3).
noscript gehört NICHT hierher, obwohl es bei aktivem Scripting
ebenfalls Raw-Text ist. Die Überlegung „der Platzhalter wäre dort
sichtbarer Quelltext" trägt für dieses Element nicht:
- Ist JavaScript AN, stellt der Browser den
<noscript>-Block gar nicht dar — ob dort eine Adresse oder ein Platzhalter steht, sieht niemand. - Ist JavaScript AUS, wird der Block dargestellt, der Decoder läuft aber nie. Dann ist der Platzhaltertext „[E-Mail-Adresse — bitte JavaScript aktivieren]" genau die richtige Anzeige.
In beiden Fällen ist der einzige messbare Unterschied, dass die Adresse
ohne Obfuskation im ausgelieferten Quelltext steht — dort, wo ein
Adress-Sammler liest. noscript auf der Skip-Liste war deshalb ein
Deckungsverlust gegenüber dem Vorgängerstand (B-M1).
Grenze: Übersprungen wird bis zum ERSTEN passenden Schluss-Tag. Bei einem
<template> in einem <template> endet der übersprungene Bereich damit
am inneren </template>.
Methods
process()
Content-filter callback. Returns the content unchanged when the feature is off or the context is admin/feed; otherwise obfuscates addresses and enqueues the decoder script.
public
process(mixed $content) : string
Parameters
- $content : mixed
-
The content passed by the WordPress filter.
Return values
stringprocess_page()
Full-page-buffer entry — obfuscates addresses inside the page <body>.
public
process_page(string $html) : string
Everything before the opening <body> tag and everything after the
closing </body> tag is copied through byte for byte; ohne <body>-Tag
bleibt das Dokument unverändert.
Diese Methode ist eine reine Zeichenketten-Transformation und prüft NICHT,
ob die Antwort überhaupt HTML ist oder wie groß sie ist — das entscheidet
der Aufrufer, bevor er sie überhaupt ruft (siehe
creationell_captcha_register_email_buffer() in email-obfuscation.php,
EO-4/EO-5).
Parameters
- $html : string
-
The full buffered page HTML.
Return values
stringelement_name()
Lower-cased element name of a start or end tag; '' for declarations.
private
element_name(string $tag) : string
Parameters
- $tag : string
-
Full tag including the angle brackets.
Return values
stringencode()
XOR-hex encodes a string: a random key byte followed by each byte of the value XOR-ed with that key, all two-digit hex. The result contains no "@" and no recognisable email structure.
private
encode(string $value) : string
Parameters
- $value : string
-
The value to encode.
Return values
stringencode_tag()
Encodes a `mailto:` href inside a single start tag.
private
encode_tag(string $tag) : string
EO-7: Der Vorgänger-Regex verlangte ein Anführungszeichenpaar und ließ
<a href=mailto:info@example.com> unverändert. Unquotete Attributwerte
sind unüblich, aber gültiges HTML.
Warum das Tag hier attributweise durchlaufen und nicht einfach der Regex
um eine unquotete Alternative erweitert wird: Ein Muster, das
href=mailto:… irgendwo im Tag sucht, trifft auch die Zeichenfolge
href=mailto: INNERHALB eines anderen Attributwerts
(<a data-original="href=mailto:x@y.de">) und würde dort mitten in den
Wert ein zweites Anführungszeichen schreiben — aus einem
Darstellungsproblem würde kaputtes Markup. Der Durchlauf weiß, wo ein
Attributname steht und wo ein Wert, und ersetzt nur echte href-Attribute.
Parameters
- $tag : string
-
Full tag including the angle brackets.
Return values
stringencode_text()
Encodes plain-text addresses in a text segment.
private
encode_text(string $text, bool $active, string $placeholder) : string
Parameters
- $text : string
-
The text segment.
- $active : bool
-
Whether rewriting is switched on here.
- $placeholder : string
-
Visible replacement text.
Return values
stringis_html_space()
Whether the byte is HTML whitespace (space, tab, LF, FF, CR).
private
is_html_space(string $char) : bool
Parameters
- $char : string
-
Single byte.
Return values
boolmarkup_end()
Where the markup starting at `$lt` ends — or NULL when no markup starts there and the `<` is literal text.
private
markup_end(string $html, int $lt) : int|null
Die Unterscheidung folgt der HTML-Tokenizer-Regel: nach „<" beginnt ein Tag nur bei einem Buchstaben, nach „</" ebenfalls nur bei einem Buchstaben; „<!" und „<?" leiten Deklaration bzw. Verarbeitungsanweisung ein. Alles andere („< b", „<3", „<=") ist Text.
Ein Anführungszeichen begrenzt einen Attributwert NUR unmittelbar nach
dem „=" (Leerzeichen dazwischen erlaubt) — genau wie im HTML-Tokenizer,
der aus dem „attribute value (unquoted) state" nicht mehr in einen
Quoted-Value-State wechselt. Eine frühere Fassung nahm jedes „'" und
jedes „"" im Tag als Wertbeginn; ein Apostroph in einem UNQUOTETEN Wert
(<p title=don't>) öffnete damit einen Bereich, der nie schloss, die
Schleife lief bis zum Dokumentende und der gesamte Rest wurde als EIN Tag
unverändert durchgereicht — die Obfuskation fiel ab dieser Stelle
ersatzlos aus, in beiden Modi und ohne Meldung.
Parameters
- $html : string
-
Full document.
- $lt : int
-
Offset of the
<.
Return values
int|null —Offset just past the closing >, or NULL for literal text.
obfuscate()
The shared, markup-aware obfuscation pass.
private
obfuscate(string $html, bool $body_only) : string
Läuft die Zeichenkette einmal linear durch und unterscheidet dabei Text, Tag, Kommentar und Raw-Text-Bereich — so, wie ein HTML-Parser die Grenzen zieht.
Genauer, damit die Zusicherung nicht mehr verspricht als sie hält: Der
Scanner bildet die Zustandsübergänge des HTML-Tokenizers nach, die über
die GRENZEN entscheiden (beginnt hier ein Tag? wo endet es? was ist
Raw-Text?). Er ist kein Parser: er baut keinen Baum, kennt keine
impliziten Schluss-Tags und keine Verschachtelung. An genau EINER Stelle
weicht er vom Tokenizer bewusst ab und begründet das dort: bei einem
quotierten Attributwert, der nie schließt (markup_end()).
Der Vorgänger zerlegte die Eingabe mit
preg_split( '#(<(?:[^>"\']+|"[^"]*"|\'[^\']*\')*>)#' ) und ging davon
aus, dass jedes „<" ein Tag beginnt. Ein rohes „<" im Text (a < b,
i<n in Inline-JS) fraß deshalb alles bis zum nächsten „>" — samt eines
dazwischenliegenden <script> (EO-2: der Skript-Rumpf lief anschließend
als Textsegment durch den Klartext-Regex, was ein JS-Stringliteral
zerbrach und einen echten SyntaxError erzeugte) oder samt des echten
</script> (EO-1: $skip hing, die Obfuskation blieb für den Rest des
Blocks aus). Hier entscheidet stattdessen das Zeichen NACH dem „<", ob
überhaupt Markup beginnt; alles andere ist Text und wird als Text
behandelt.
Parameters
- $html : string
-
The HTML to process.
- $body_only : bool
-
Nur den Inhalt zwischen
<body>und</body>umschreiben (Buffer-Modus).
Return values
stringraw_text_end()
Offset of the `<` of the closing tag for a raw-text element, or the end of the document when it never closes.
private
raw_text_end(string $html, string $name, int $from) : int
Parameters
- $html : string
-
Full document.
- $name : string
-
Lower-cased element name.
- $from : int
-
Offset just past the opening tag.