Skip to content

Instantly share code, notes, and snippets.

@jkoop
Created March 8, 2024 15:03
Show Gist options
  • Save jkoop/7b06b38ade0cee87771b77ee4e635464 to your computer and use it in GitHub Desktop.
Save jkoop/7b06b38ade0cee87771b77ee4e635464 to your computer and use it in GitHub Desktop.
Laravel 10: middleware: prevent HTML and Markdown attacks
<?php
namespace App\Http\Middleware;
use HTMLPurifier;
use HTMLPurifier_HTML5Config;
use Illuminate\Foundation\Http\Middleware\TransformsRequest;
/**
* Use HTMLPurifier to purify all inputs with names matching `/[-_]html\s*$/i`.
* Requires @package laravel/framework (v10), and @package xemlock/htmlpurifier-html5.
*
* Add this middleware to Kernel.php just before TrimStrings
*
* @copyright 2024 Church Renewal Inc.
* @license MIT
*/
class PurifyHtml extends TransformsRequest {
/**
* The names of the attributes that should not be purified.
*
* @var array<int, string>
*/
protected $except = ["extra_head_html"];
/**
* Transform the given value.
*
* @param string $key
* @param string|int|float|null $value
* @return string|null
*/
protected function transform($key, $value) {
if (is_null($value) or in_array($key, $this->except) or !preg_match('/[-_]html\s*$/i', $key)) {
return $value;
}
return self::filterHtml((string) $value);
}
/**
* Purify HTML.
* @param string $html
* @return string
*/
private static function filterHtml($html) {
static $purifier = false;
if (!$purifier) {
$config = HTMLPurifier_HTML5Config::createDefault();
$config->set("Attr.AllowedFrameTargets", ["_blank", "_self", "_parent", "_top"]);
$config->set("AutoFormat.RemoveSpansWithoutAttributes", true);
$config->set("HTML.SafeIframe", true);
$config->set("HTML.IframeAllowFullscreen", true);
// protocol must be http or https, hostname must contain a period, hostname's tld must begin with a letter, and port number must not be specified
/** @link https://regex101.com/r/o3lEvC/2 */
$config->set("URI.SafeIframeRegexp", "#^(https?:)?//[^/]+\.[a-z][a-z0-9]+/.*#iu");
$purifier = new HTMLPurifier($config);
}
return $purifier->purify($html);
}
}
<?php
namespace App\Http\Middleware;
use HTMLPurifier;
use HTMLPurifier_HTML5Config;
use Illuminate\Foundation\Http\Middleware\TransformsRequest;
use Illuminate\Mail\Markdown;
use League\HTMLToMarkdown\HtmlConverter;
/**
* Use HTMLPurifier to purify all inputs with names matching `/[-_]md\s*$/i`.
* Requires @package laravel/framework (v10), @package xemlock/htmlpurifier-html5, and @package league/html-to-markdown.
*
* Add this middleware to Kernel.php just before TrimStrings
*
* @copyright 2024 Church Renewal Inc.
* @license MIT
*/
class PurifyMarkdown extends TransformsRequest {
/**
* The names of the attributes that should not be purified.
*
* @var array<int, string>
*/
protected $except = [];
/**
* Transform the given value.
*
* @param string $key
* @param string|int|float|null $value
* @return string|null
*/
protected function transform($key, $value) {
if (is_null($value) or in_array($key, $this->except) or !preg_match('/[-_]md\s*$/i', $key)) {
return $value;
}
$html = Markdown::parse((string) $value); // convert markdown to html
$html = self::filterHtml($html); // purify html
return self::htmlToMarkdown($html); // convert html back to markdown
}
/**
* Convert the given HTML to Markdown, preserving comments
* @param string $html
* @return string markdown
*/
private static function htmlToMarkdown($html) {
static $converter = false;
if (!$converter) {
$converter = new HtmlConverter(["header_style" => "atx", "preserve_comments" => true]);
}
return $converter->convert($html);
}
/**
* Purify HTML.
* @param string $html
* @return string
*/
private static function filterHtml($html) {
static $purifier = false;
if (!$purifier) {
$config = HTMLPurifier_HTML5Config::createDefault();
$config->set("Attr.AllowedFrameTargets", ["_blank", "_self", "_parent", "_top"]);
$config->set("AutoFormat.RemoveSpansWithoutAttributes", true);
$config->set("HTML.SafeIframe", true);
$config->set("HTML.IframeAllowFullscreen", true);
// protocol must be http or https, hostname must contain a period, hostname's tld must begin with a letter, and port number must not be specified
/** @link https://regex101.com/r/o3lEvC/2 */
$config->set("URI.SafeIframeRegexp", "#^(https?:)?//[^/]+\.[a-z][a-z0-9]+/.*#iu");
$purifier = new HTMLPurifier($config);
}
return $purifier->purify($html);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment