HTML Purifier
HTML Purifier Implementation
## Introduction
---
In order to protect your application from XSS attacks and ensuring serving standards-compliant output,
you can decide to implement a purifier for your application or project. A purifier will remove all malicious
code with a thoroughly audited, secure yet permissive whitelist.
In this chapter we will give you an example on how to implement HTML Purifier into your application.
### Configure a purifier
To implement HTML purifier within your application a few steps need to be taken.
* First of all; choose a purifier according to your preference and add it to your project (e.g. by using Composer).
In this case we use ``ezyang/htmlpurifier`` (see: [github.com/ezyang/htmlpurifier](https://github.com/ezyang/htmlpurifier)) . * Write an ABC Manager plugin for the purifier. An example of the plugin's class for HTML Purifier looks as follows: ```php // File: ./src/Demo/PublicationBundle/Templating/Plugin/HtmlPurifyPlugin.php namespace Demo\PublicationBundle\Templating\Plugin; use Abc\Component\FileSystem\FileSystem; use Abc\AbcBundle\Project\Directories as ProjectLayout; final class HtmlPurifyPlugin extends \Abc\TemplatingBundle\Plugin { private ProjectLayout $directoryLayout; private FileSystem $fileSystem; private ?string $cacheDir = null; /** @var mixed[] */ private array $config; /** * Inject DirectoryLayout and FileSystem */ public function __construct( FileSystem $fileSystem, ProjectLayout $directoryLayout ) { $this->fileSystem = $fileSystem; $this->directoryLayout = $directoryLayout; } /** * Return the purified text */ public function execute() : string { $text = $this->getParam('text', '', false); if (empty($text)) { return $text; } $config = $this->getParam('config', [], false); if (!is_array($config)) { throw new \InvalidArgumentException( 'Invalid value config provided; must be an array' ); } // Add allowed tags to config array $allowedTags = $this->getParam('allowed', null, false); if (is_string($allowedTags)) { $config['HTML.Allowed'] = $allowedTags; } // Render purified text return $this->getFilter($config)->purify($text); } /** * Get default config * * @return mixed[] */ public function getConfig(): array { return $this->config; } /** * Set default config */ public function setConfig(string $allowedTags): self { $this->config = [ 'HTML.Allowed' => $allowedTags ]; return $this; } /** * Get the tag name for this plugin */ public function getTagName() : string { return 'purify_html'; } /** * Get the HTML filter * * @param mixed[] $config */ private function getFilter(array $config = []): \HTMLPurifier { return new \HTMLPurifier( $this->parseConfig($config) ); } /** * Parse the config * * Fetches the default config and merges the provided config * with the defaults * * @param mixed[] $config */ private function parseConfig(array $config = []): \HTMLPurifier_Config { // Merge default config with config from template $config = array_merge($this->getConfig(), $config); // Create configuration $purifierConfig = \HTMLPurifier_Config::createDefault(); $purifierConfig->set('Cache.SerializerPath', $this->getCacheDir()); $purifierConfig->set('URI.Base', $this->application->getUrl()); // Set remaining config options foreach ($config as $configDirective => $configValue) { $purifierConfig->set((string) $configDirective, $configValue); } return $purifierConfig; } /** * Get the cache directory */ private function getCacheDir(): string { if ($this->cacheDir === null) { $this->cacheDir = sprintf( '%s%shtmlpurifier', $this->directoryLayout->getTemp(), DIRECTORY_SEPARATOR ); $this->fileSystem->createDirectory($this->cacheDir); } return $this->cacheDir; } } ``` * Determine which HTML tags are allowed during the purify process. This can be done by setting a parameter definition within your ``services.yml`` or ``services-application.yml`` file. An example of how to set this parameter for the HTML Purifier looks as follows: ```yaml // File: ./src/Demo/PublicationBundle/Resources/config/services.yml parameters: foo.bar.purify_html.config.allowed_tags: 'p, b, i, em, a, span, h1, h2, h3' ``` * Register the plugin as a service. An example of how to register the HTML Purifier as a plugin looks as follows: ```yaml // File: ./src/Demo/PublicationBundle/Resources/config/services.yml services: foo.bar.templating.plugin.purify_html: class: Foo\BarBundle\Templating\Plugin\HtmlPurifyPlugin arguments: - '@file_system' - '@project_layout' calls: - [setConfig, [ '%demo.publication.purify_html.config.allowed_tags%', ]] - [setApplication, ['@demo']] tags: - name: foo.templating.plugin tag: 'purify_html' ``` * Apply the plugin within your Twig template. An example of how to implement the HTML Purifier within a Twig template looks as follows: ```twig # HTML Purifier plugin usage: # # purify_html({ # text' : 'string', # allowed : 'p,b,a[href],i', # config : 'HTML.foo : bar, ...' # })
In this case we use ``ezyang/htmlpurifier`` (see: [github.com/ezyang/htmlpurifier](https://github.com/ezyang/htmlpurifier)) . * Write an ABC Manager plugin for the purifier. An example of the plugin's class for HTML Purifier looks as follows: ```php // File: ./src/Demo/PublicationBundle/Templating/Plugin/HtmlPurifyPlugin.php namespace Demo\PublicationBundle\Templating\Plugin; use Abc\Component\FileSystem\FileSystem; use Abc\AbcBundle\Project\Directories as ProjectLayout; final class HtmlPurifyPlugin extends \Abc\TemplatingBundle\Plugin { private ProjectLayout $directoryLayout; private FileSystem $fileSystem; private ?string $cacheDir = null; /** @var mixed[] */ private array $config; /** * Inject DirectoryLayout and FileSystem */ public function __construct( FileSystem $fileSystem, ProjectLayout $directoryLayout ) { $this->fileSystem = $fileSystem; $this->directoryLayout = $directoryLayout; } /** * Return the purified text */ public function execute() : string { $text = $this->getParam('text', '', false); if (empty($text)) { return $text; } $config = $this->getParam('config', [], false); if (!is_array($config)) { throw new \InvalidArgumentException( 'Invalid value config provided; must be an array' ); } // Add allowed tags to config array $allowedTags = $this->getParam('allowed', null, false); if (is_string($allowedTags)) { $config['HTML.Allowed'] = $allowedTags; } // Render purified text return $this->getFilter($config)->purify($text); } /** * Get default config * * @return mixed[] */ public function getConfig(): array { return $this->config; } /** * Set default config */ public function setConfig(string $allowedTags): self { $this->config = [ 'HTML.Allowed' => $allowedTags ]; return $this; } /** * Get the tag name for this plugin */ public function getTagName() : string { return 'purify_html'; } /** * Get the HTML filter * * @param mixed[] $config */ private function getFilter(array $config = []): \HTMLPurifier { return new \HTMLPurifier( $this->parseConfig($config) ); } /** * Parse the config * * Fetches the default config and merges the provided config * with the defaults * * @param mixed[] $config */ private function parseConfig(array $config = []): \HTMLPurifier_Config { // Merge default config with config from template $config = array_merge($this->getConfig(), $config); // Create configuration $purifierConfig = \HTMLPurifier_Config::createDefault(); $purifierConfig->set('Cache.SerializerPath', $this->getCacheDir()); $purifierConfig->set('URI.Base', $this->application->getUrl()); // Set remaining config options foreach ($config as $configDirective => $configValue) { $purifierConfig->set((string) $configDirective, $configValue); } return $purifierConfig; } /** * Get the cache directory */ private function getCacheDir(): string { if ($this->cacheDir === null) { $this->cacheDir = sprintf( '%s%shtmlpurifier', $this->directoryLayout->getTemp(), DIRECTORY_SEPARATOR ); $this->fileSystem->createDirectory($this->cacheDir); } return $this->cacheDir; } } ``` * Determine which HTML tags are allowed during the purify process. This can be done by setting a parameter definition within your ``services.yml`` or ``services-application.yml`` file. An example of how to set this parameter for the HTML Purifier looks as follows: ```yaml // File: ./src/Demo/PublicationBundle/Resources/config/services.yml parameters: foo.bar.purify_html.config.allowed_tags: 'p, b, i, em, a, span, h1, h2, h3' ``` * Register the plugin as a service. An example of how to register the HTML Purifier as a plugin looks as follows: ```yaml // File: ./src/Demo/PublicationBundle/Resources/config/services.yml services: foo.bar.templating.plugin.purify_html: class: Foo\BarBundle\Templating\Plugin\HtmlPurifyPlugin arguments: - '@file_system' - '@project_layout' calls: - [setConfig, [ '%demo.publication.purify_html.config.allowed_tags%', ]] - [setApplication, ['@demo']] tags: - name: foo.templating.plugin tag: 'purify_html' ``` * Apply the plugin within your Twig template. An example of how to implement the HTML Purifier within a Twig template looks as follows: ```twig # HTML Purifier plugin usage: # # purify_html({ # text' : 'string', # allowed : 'p,b,a[href],i', # config : 'HTML.foo : bar, ...' # })
# Rte text
{{ purify_html({ text: foo.bar }) }}
```
The HTML Purifier implementation is now **finished and ready for use**!