Markdown Support: Technische Details
Technik von Markdown Support: Plugin-Metadaten, Core-Patch und Backup, Signal-Handler, E-Mail-Vorverarbeitung, Preview-Endpoint, Sicherheitsmaßnahmen und Dateistruktur.
Markdown Support: Technische Details
Plugin-Metadaten
| Eigenschaft | Wert |
|---|---|
| Plugin-ID | com.osticket:markdown-support |
| Name | Markdown Support |
| Plugin-Klasse | MarkdownPlugin in class.MarkdownPlugin.php |
| Konfigurationsklasse | MarkdownConfig in config.php |
| Instanzen | Singleton: genau eine Instanz, enable() legt sie bei Bedarf an |
| PHP | 8.0 oder neuer: composer.json deklariert >=7.4, aber ConfigCache verwendet den PHP-8.0-Typ mixed. Unter PHP 7.4 lösen get() und set() einen TypeError aus |
| Abhängigkeit | erusev/parsedown ^1.7 (gesperrt auf 1.7.4) |
Ablauf beim Start
graph TD
A["bootstrap()"] --> B["Konfiguration in ConfigCache laden"]
B --> C["Version prüfen und Preview-Endpoint kopieren"]
C --> D{"Enable Markdown Support an?"}
D -->|"Nein"| E["Ende"]
D -->|"Ja"| F["ThreadEntryBody::types um markdown erweitern"]
F --> G["MarkdownThreadEntryBody laden, class.thread.php patchen"]
G --> H["POST für E-Mail vorverarbeiten"]
H --> I["Signale object.view und threadentry.created verbinden"]
I --> J["Output-Buffer für Editor-Seiten"]
Core-Eingriff
Das Plugin braucht zwei Eingriffe in osTicket:
- Per Reflection: Es ergänzt die statische Eigenschaft
ThreadEntryBody::$typesum den Wertmarkdown. Dafür ist keine Dateiänderung nötig. - Per Datei-Patch: Es fügt in
include/class.thread.phphinter dem Zweigcase 'html':vonThreadEntryBody::fromFormattedText()den Zweigcase 'markdown': return new MarkdownThreadEntryBody(...)ein. Kann es das Muster nicht finden (andere osTicket-Version), loggt esUnable to apply patch - pattern not foundin das PHP-Error-Log und patcht nicht.
Enthält die Datei bereits einen case 'markdown':, tut das Plugin nichts. Vor dem Patch kopiert es die Datei nach include/class.thread.php.markdown-backup, sofern noch keine Sicherung existiert. Beim Deaktivieren stellt es die Datei aus dieser Sicherung wieder her und löscht sie. Fehlt die Sicherung, stellt es nichts wieder her. Eine bereits vorhandene Sicherung wird nicht überschrieben (siehe Installation, Abschnitt osTicket aktualisieren).
Rendering
MarkdownThreadEntryBody erweitert ThreadEntryBody mit dem Typ markdown und rendert in display():
ParsedownmitsetSafeMode(true)wandelt Markdown zu HTML.Format::sanitize()von osTicket bereinigt das Ergebnis.- Im Modus
pdfersetzt das Pluginimg-Tags mithttp(s)-Quelle durch den Text[Image]. Im Modusemailbleibt das HTML unverändert.
Die Suchindizierung (getSearchable()) wandelt das gerenderte HTML mit Format::html2text() in Text um.
E-Mail-Verarbeitung
osTicket verwendet für E-Mails den rohen POST-Text. Der Markdown-Editor sendet Klartext, deshalb würden E-Mails sonst Markdown-Syntax zeigen. Das Plugin geht so vor:
- Bei einem POST mit
format=markdownsichert es die Felderresponse,noteundmessage, rendert sie mitMarkdownThreadEntryBodyzu HTML und setztformatfür osTicket aufhtml. - osTicket legt den Eintrag als HTML an und versendet die E-Mail mit dem gerenderten Text.
- Der Handler für
threadentry.createdmerkt sich die ID des Eintrags. - Eine Shutdown-Funktion schreibt am Ende der Anfrage das ursprüngliche Markdown und
format = 'markdown'per SQL inTHREAD_ENTRY_TABLEzurück.
Zusätzlich korrigiert der Handler das Format über threadentry.created, wenn die Anfrage ein gültiges format mitbringt (siehe API-Integration).
Editor-Assets
- Signal
object.view: Bei einemTicketgibt das Plugin den Konfigurationsblockwindow.osTicketMarkdownConfig(defaultFormat,allowFormatSwitch,showToolbar,previewApiUrl), das Stylesheet und das Scriptjs/markdown-editor.jsaus. Die Dateien erhalten?v=<installierte Version>als Cache-Parameter. - Output-Buffer: Auf nicht-AJAX-Seiten mit dem Skriptnamen
tickets.phpoderticket.php(undajax.php-URLs mitticket) setzt das Plugin zusätzlich einen Output-Buffer, der dieselben Assets vor</head>einfügt. Doppeltes Einfügen verhindert die Prüfung aufwindow.osTicketMarkdownConfig.
Preview-Endpoint
markdown-preview.php wird beim Aktivieren (und bei jedem Start, wenn die Datei abweicht) nach api/markdown-preview.php kopiert, weil include/ per .htaccess gesperrt ist. Der Endpoint:
- akzeptiert nur
POSTmit JSON{"markdown": "..."}und antwortet mit{"success": true, "html": "..."}, - rendert mit Parsedown im SafeMode und filtert danach
javascript:- unddata:-Werte inhref/src,on*-Attribute,stylemitexpression()undscript-Tags, - lädt osTicket nicht und prüft deshalb weder Login noch CSRF-Token,
- begrenzt die Größe des Feldes
markdownnicht, - sucht Parsedown unter
vendor/autoload.phpneben der Datei oder unter../include/plugins/markdown-support/vendor/autoload.php.
Der Endpoint existiert unter zwei Pfaden: als
api/markdown-preview.php(vom Plugin dorthin kopiert) und als Original im Plugin-Ordnerinclude/plugins/markdown-support/markdown-preview.php, das die.htaccessdes Plugins freigibt. Beide Pfade sind ohne Anmeldung erreichbar, prüfen kein CSRF-Token, begrenzen die Größe der Eingabe nicht und laden osTicket nicht. Die Live-Vorschau im Editor nutzt nur die Kopie inapi/.
Die Vorschau läuft nicht durch
Format::sanitize(), sondern nur durch den eigenen Filter. Sie zeigt deshalb nicht in jedem Fall exakt das HTML, das später in der Ticket-Ansicht erscheint.
Sicherheit
| Maßnahme | Wo |
|---|---|
| Parsedown SafeMode (HTML im Text wird maskiert, unsichere URL-Schemata werden entschärft) | Rendering und Vorschau |
Format::sanitize() (osTicket) |
Rendering von Thread-Einträgen |
Regex-Filter für javascript:/data:-URLs, on*, expression(), script |
Preview-Endpoint |
Bild-Upload: MIME-Whitelist (JPEG, PNG, GIF, WebP, BMP), URL-Prüfung, Alt-Text ohne [](/), Abbruch ohne CSRF-Token |
Editor im Browser |
Eigene .htaccess im Plugin-Ordner: gibt markdown-preview.php frei, sperrt *.log |
Plugin-Ordner |
SQL-Werte über db_input() |
Format- und Body-Updates |
Die Klasse MarkdownSanitizer ist mit Tests enthalten, wird im Plugin-Ablauf aber nicht aufgerufen.
Änderungen an include/.htaccess
Beim Aktivieren fügt das Plugin zwei Blöcke ein: einen FilesMatch-Block, der js, css, map, json, Bilder und Fonts freigibt, und einen Block für PHP-Dateien unter plugins/. Beim Deaktivieren entfernt es beide Blöcke wieder. Für NGINX gibt es keine automatische Anpassung.
Die Regel für PHP-Dateien (<FilesMatch "^plugins/.+\.php$">) wird von Apache nur gegen Dateinamen ohne Pfad geprüft und greift deshalb vermutlich nie. Geöffnet wird markdown-preview.php durch die Files-Direktive in der .htaccess des Plugin-Ordners.
Dateistruktur
markdown-support/
├── plugin.php # Plugin-Metadaten
├── class.MarkdownPlugin.php # Plugin-Klasse, Signale, enable/disable
├── config.php # MarkdownConfig mit den Optionen
├── markdown-body.php # MarkdownThreadEntryBody
├── markdown-preview.php # Preview-Endpoint (Kopie landet in api/)
├── MarkdownDetector.php # Erkennung von Markdown-Syntax
├── .htaccess # Freigabe des Endpoints, Sperre für Logs
├── src/
│ ├── Config/ConfigCache.php
│ ├── Core/CorePatcher.php
│ ├── Asset/AssetInjector.php
│ ├── Asset/AssetDeployer.php
│ ├── Http/MarkdownPostProcessor.php
│ ├── Signal/ThreadEntryHandler.php
│ └── Format/ # Sanitizer und Interfaces
├── css/markdown-editor.css
├── js/markdown-editor.js # gebündelt aus js/src/
├── i18n/de_DE/ # Deutsche Übersetzung der Konfiguration
└── vendor/ # Parsedown (Composer)
Das Repository enthält zusätzlich Tests (PHPUnit), Build-Quellen für das JavaScript und Dokumentation. Die CI führt eine Syntaxprüfung (php -l) mit PHP 7.4 bis 8.3 aus.