Projekte / deltachat_message_parser

deltachat_message_parser

RustWASM

Das Problem

Delta Chat läuft auf Android, iOS und Desktop. Jede Plattform hatte ihren eigenen Code zum Erkennen von Links, E-Mail-Adressen und anderen klickbaren Elementen in Nachrichten. Bugs wurden auf einer Plattform behoben, aber nicht auf der anderen, und das Verhalten war inkonsistent.

Die Idee war, einen einzelnen Parser in Rust zu bauen, den alle Plattformen teilen können. Wir begannen mit der Integration in Desktop, wo er eine fehlerhafte regex-basierte Bibliothek ersetzte, indem wir ihn zu WebAssembly kompilierten und als npm-Paket veröffentlichten. Der Plan war, ihn in Delta Chats Rust-Core zu integrieren, sobald er sich als stabil genug erwies, was es dann einfach gemacht hätte, ihn auf den anderen Plattformen zu nutzen.

Mehr als Markdown

Obwohl der Parser ein Markdown-Subset unterstützt (fett, kursiv, durchgestrichen, Inline-Code, Code-Blöcke), ist Markdown ein Extra-Feature, nicht das Hauptziel. Der Kernzweck ist einheitliche Linkification: zuverlässiges Erkennen und Klickbar-Machen aller Dinge, die in einer Chat-Nachricht klickbar sein sollten:

Der Parser hat zwei Modi: einen Nur-Text-Modus für reine Linkification und einen vollständigen Modus, der Markdown-Formatierung darüberlegt.

Wie es funktioniert

Der Parser ist mit nom gebaut, einer Parser-Combinator-Bibliothek für Rust. Das war eine bewusste Entscheidung gegen regex-basiertes Parsing: Die meisten Link-Parser (einschließlich Androids eingebautem) nutzen reguläre Ausdrücke, die anfällig für ReDoS-Attacken mit speziell konstruiertem Input sein können. noms Combinatoren machen kein Backtracking auf die Art, wie anfällige Regex-Engines es tun, daher gelten die spezifischen katastrophalen Backtracking-Muster hinter den meisten ReDoS-CVEs hier nicht.

Inspiriert davon, wie Discord Rich Messages handhabt, ist die Ausgabe ein Abstract Syntax Tree (AST) statt gerendertem HTML. Das lässt die UI-Schicht jeder Plattform entscheiden, wie Elemente mit ihren nativen Primitiven dargestellt werden: Android nutzt Spans, iOS nutzt NSAttributedString, und im Web kann der AST sicher auf React-Elemente abgebildet werden, was das XSS-Risiko von innerHTML vermeidet.

Der AST ermöglicht auch interaktives Verhalten über reines Rendering hinaus. Zum Beispiel löst ein beschrifteter Link wie [Klick hier](https://example.com) einen Bestätigungsdialog aus, der die tatsächliche URL zeigt, um Phishing zu verhindern. Internationalisierte Domainnamen werden aus demselben Grund in Punycode angezeigt.

Was schiefging

Der Parser funktionierte, aber das Link-Parsing hatte zu viele Edge Cases und Bugs. Ein entscheidender Fehler war, die Test-Suite von Grund auf zu bauen, statt mit einer etablierten zu beginnen. Link-Parsing ist trügerisch schwer, und ohne einen umfassenden Satz bestehender Testfälle zur Validierung rutschten immer wieder Edge Cases durch.

Niemand anderes übernahm die Pflege des Projekts, also waren Fixes langsam. Auf der Markdown-Seite gab (und gibt) es laufende Diskussionen darüber, ob Delta Chat überhaupt Markdown-Unterstützung will oder braucht, oder ob ein Inline-Editing-UI ein besserer Ansatz wäre.

Am Ende wechselte Delta Chat Desktop zu linkifyjs für Link-Erkennung, die experimentelle Markdown-Unterstützung wurde entfernt, und der Message Parser wurde eingefroren. Der Parser hat die anderen Plattformen nie erreicht.

Gelernte Lektionen

Wenn ich den Message-Parser wiederbeleben oder neu anfangen würde, würde ich linkifyjs' Test-Suite als Ausgangspunkt klonen, statt Tests von Grund auf zu schreiben. Ein praxiserprobter Satz von Edge Cases hätte vom ersten Tag an viele der Bugs aufgefangen, die uns letztlich zum Aufgeben brachten.

Die Kernidee bleibt solide: ein einzelner Rust-Parser mit umfangreicher Test-Suite, genutzt von allen Plattformen, der das Parsing an einer Stelle vereinheitlicht. Er müsste nur von Anfang an auf einer soliden Test-Suite aufbauen, statt nur reaktiv auf Bug-Reports zu reagieren. Das Projekt ruht derzeit, aber die WASM-Demo funktioniert noch und zeigt den Parser in Aktion.