CreaCaptcha

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
string

process_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
string

element_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
string

encode()

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
string

encode_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
string

encode_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
string

is_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
bool

markup_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
string

raw_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.

Return values
int

        
On this page

Search results