Markus Michalski FAQ

Markdown Support: Technical Details

Technical details of Markdown Support: plugin metadata, core patch and backup, signal handlers, email pre-processing, preview endpoint, security measures and file structure.

Deutsche Version

Back to overview

Markdown Support: Technical Details

Plugin metadata

Property Value
Plugin ID com.osticket:markdown-support
Name Markdown Support
Plugin class MarkdownPlugin in class.MarkdownPlugin.php
Configuration class MarkdownConfig in config.php
Instances Singleton: exactly one instance, enable() creates it if needed
PHP 8.0 or newer: composer.json declares >=7.4, but ConfigCache uses the PHP 8.0 type mixed. On PHP 7.4 get() and set() raise a TypeError
Dependency erusev/parsedown ^1.7 (locked to 1.7.4)

Startup flow

graph TD
    A["bootstrap()"] --> B["Load configuration into ConfigCache"]
    B --> C["Check version and copy preview endpoint"]
    C --> D{"Enable Markdown Support on?"}
    D -->|"No"| E["End"]
    D -->|"Yes"| F["Extend ThreadEntryBody::types with markdown"]
    F --> G["Load MarkdownThreadEntryBody, patch class.thread.php"]
    G --> H["Pre-process POST for email"]
    H --> I["Connect signals object.view and threadentry.created"]
    I --> J["Output buffer for editor pages"]

Core intervention

The plugin needs two interventions in osTicket:

If the file already contains a case 'markdown':, the plugin does nothing. Before patching, it copies the file to include/class.thread.php.markdown-backup if no backup exists yet. On deactivation it restores the file from this backup and deletes it. If the backup is missing, it restores nothing. An existing backup is not overwritten (see Installation, section Updating osTicket).

Rendering

MarkdownThreadEntryBody extends ThreadEntryBody with the type markdown and renders in display():

  1. Parsedown with setSafeMode(true) converts Markdown to HTML.
  2. osTicket's Format::sanitize() cleans the result.
  3. In pdf mode the plugin replaces img tags with an http(s) source with the text [Image]. In email mode the HTML stays unchanged.

Search indexing (getSearchable()) converts the rendered HTML to text with Format::html2text().

Email handling

osTicket uses the raw POST text for emails. The Markdown editor sends plain text, so emails would otherwise show Markdown syntax. The plugin proceeds as follows:

  1. On a POST with format=markdown it saves the fields response, note and message, renders them to HTML with MarkdownThreadEntryBody and sets format to html for osTicket.
  2. osTicket creates the entry as HTML and sends the email with the rendered text.
  3. The handler for threadentry.created remembers the ID of the entry.
  4. A shutdown function writes the original Markdown and format = 'markdown' back to THREAD_ENTRY_TABLE via SQL at the end of the request.

In addition, the handler corrects the format via threadentry.created when the request carries a valid format (see API integration).

Editor assets

Preview endpoint

markdown-preview.php is copied to api/markdown-preview.php on activation (and on every start if the file differs), because include/ is blocked by .htaccess. The endpoint:

The endpoint exists at two paths: as api/markdown-preview.php (copied there by the plugin) and as the original in the plugin folder include/plugins/markdown-support/markdown-preview.php, which the plugin's .htaccess opens. Both paths are reachable without login, check no CSRF token, do not limit the size of the input and do not load osTicket. The editor's live preview uses only the copy in api/.

The preview does not pass through Format::sanitize(), only through its own filter. It therefore does not always show exactly the HTML that later appears in the ticket view.

Security

Measure Where
Parsedown SafeMode (HTML in the text is escaped, unsafe URL schemes are neutralized) Rendering and preview
Format::sanitize() (osTicket) Rendering of thread entries
Regex filter for javascript:/data: URLs, on*, expression(), script Preview endpoint
Image upload: MIME whitelist (JPEG, PNG, GIF, WebP, BMP), URL check, alt text without [](/), abort without CSRF token Editor in the browser
Own .htaccess in the plugin folder: allows markdown-preview.php, blocks *.log Plugin folder
SQL values via db_input() Format and body updates

The class MarkdownSanitizer is included with tests but is not called in the plugin's runtime flow.

Changes to include/.htaccess

On activation the plugin inserts two blocks: a FilesMatch block that allows js, css, map, json, images and fonts, and a block for PHP files under plugins/. On deactivation it removes both blocks again. There is no automatic adjustment for NGINX.

Apache matches the rule for PHP files (<FilesMatch "^plugins/.+\.php$">) only against file names without a path, so it probably never matches. markdown-preview.php is opened by the Files directive in the plugin folder's .htaccess.

File structure

markdown-support/
β”œβ”€β”€ plugin.php                 # plugin metadata
β”œβ”€β”€ class.MarkdownPlugin.php   # plugin class, signals, enable/disable
β”œβ”€β”€ config.php                 # MarkdownConfig with the options
β”œβ”€β”€ markdown-body.php          # MarkdownThreadEntryBody
β”œβ”€β”€ markdown-preview.php       # preview endpoint (a copy goes to api/)
β”œβ”€β”€ MarkdownDetector.php       # Markdown syntax detection
β”œβ”€β”€ .htaccess                  # allows the endpoint, blocks logs
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ Config/ConfigCache.php
β”‚   β”œβ”€β”€ Core/CorePatcher.php
β”‚   β”œβ”€β”€ Asset/AssetInjector.php
β”‚   β”œβ”€β”€ Asset/AssetDeployer.php
β”‚   β”œβ”€β”€ Http/MarkdownPostProcessor.php
β”‚   β”œβ”€β”€ Signal/ThreadEntryHandler.php
β”‚   └── Format/                # sanitizer and interfaces
β”œβ”€β”€ css/markdown-editor.css
β”œβ”€β”€ js/markdown-editor.js      # bundled from js/src/
β”œβ”€β”€ i18n/de_DE/                # German translation of the configuration
└── vendor/                    # Parsedown (Composer)

The repository additionally contains tests (PHPUnit), build sources for the JavaScript and documentation. CI runs a syntax check (php -l) with PHP 7.4 to 8.3.