Created
March 8, 2024 15:03
-
-
Save jkoop/7b06b38ade0cee87771b77ee4e635464 to your computer and use it in GitHub Desktop.
Laravel 10: middleware: prevent HTML and Markdown attacks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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