From de23dee26e852fd17968c602dd89cf3582a6eb2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Dufraisse?= Date: Tue, 15 Feb 2022 16:49:53 +0100 Subject: [PATCH] feat(htmlpurifer): use it to clean text inputs which are raw displayed --- composer.json | 1 + includes/services/HtmlPurifierService.php | 43 +++++++++++++++++++++++ tools/bazar/fields/TextField.php | 12 +++++++ tools/bazar/fields/TextareaField.php | 24 +++++++++++++ tools/bazar/services/ListManager.php | 29 ++++++++++++--- tools/bazar/templates/fields/text.twig | 2 ++ 6 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 includes/services/HtmlPurifierService.php diff --git a/composer.json b/composer.json index bbfe167d8..fe7875b46 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "doctrine/annotations": "^1.11", "doctrine/cache": "^1.10", "enshrined/svg-sanitize": "^0.15.0", + "ezyang/htmlpurifier": "^4.14", "oomphinc/composer-installers-extender": "^2.0", "phpmailer/phpmailer": "^6.2", "symfony/config": "^5.1", diff --git a/includes/services/HtmlPurifierService.php b/includes/services/HtmlPurifierService.php new file mode 100644 index 000000000..ca65d3bdd --- /dev/null +++ b/includes/services/HtmlPurifierService.php @@ -0,0 +1,43 @@ +wiki = $wiki; + $this->purifier = null; + } + + /** + * load a HTMLpurifier if needed + * configure it + * then use it to clean HTML + */ + public function cleanHTML(string $dirty_html): string + { + if (is_null($this->purifier)) { + $this->load(); + } + + return $this->purifier->purify($dirty_html); + } + + private function load() + { + $config = HTMLPurifier_Config::createDefault(); + $this->purifier = new HTMLPurifier($config); + } +} diff --git a/tools/bazar/fields/TextField.php b/tools/bazar/fields/TextField.php index b8d93003f..c96d5167b 100644 --- a/tools/bazar/fields/TextField.php +++ b/tools/bazar/fields/TextField.php @@ -3,6 +3,7 @@ namespace YesWiki\Bazar\Field; use Psr\Container\ContainerInterface; +use YesWiki\Core\Service\HtmlPurifierService; /** * @Field({"texte"}) @@ -64,6 +65,17 @@ protected function renderStatic($entry) } } + public function formatValuesBeforeSave($entry) + { + if (empty($this->propertyName)) { + return []; + } + $dirtyHtml = $this->getValue($entry); + $cleanHTML = $this->getService(HtmlPurifierService::class)->cleanHTML($dirtyHtml); + + return [$this->propertyName => $cleanHTML]; + } + public function getPattern() { return $this->pattern; diff --git a/tools/bazar/fields/TextareaField.php b/tools/bazar/fields/TextareaField.php index 5741e7986..d85ae730a 100644 --- a/tools/bazar/fields/TextareaField.php +++ b/tools/bazar/fields/TextareaField.php @@ -7,6 +7,7 @@ use Psr\Container\ContainerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use YesWiki\Core\Service\DbService; +use YesWiki\Core\Service\HtmlPurifierService; /** * @Field({"textelong"}) @@ -140,8 +141,12 @@ public function formatValuesBeforeSave($entry) if ($this->syntax === self::SYNTAX_HTML) { $value = strip_tags($value, self::ACCEPTED_TAGS); $value = $this->sanitizeBase64Img($value, $entry); + $value = $this->sanitizeHTML($value); } elseif ($this->syntax === self::SYNTAX_WIKI) { $value = $this->sanitizeAttach($value, $entry); + $value = $this->sanitizeHTMLInWikiCode($value); + } else { + $value = $this->sanitizeHTML($value); } return [$this->propertyName => $value]; @@ -334,4 +339,23 @@ private function sanitizeFileName(string $inputString):string { return removeAccents(preg_replace('/--+/u', '-', preg_replace('/[[:punct:]]/', '-', $inputString))); } + + /** + * sanitize html to prevent xss + */ + private function sanitizeHTMLInWikiCode(string $value) + { + $preformattedDirtyHTML = str_replace('""', '@@', $value); + $preformattedCleanHTML = $this->getService(HtmlPurifierService::class)->cleanHTML($preformattedDirtyHTML); + $preformattedCleanHTML = str_replace('""', '\'\'', $preformattedCleanHTML); + return str_replace('@@', '""', $preformattedCleanHTML); + } + + /** + * sanitize html to prevent xss + */ + private function sanitizeHTML(string $value) + { + return $this->getService(HtmlPurifierService::class)->cleanHTML($value); + } } diff --git a/tools/bazar/services/ListManager.php b/tools/bazar/services/ListManager.php index 0f98ddf8e..aa3372728 100644 --- a/tools/bazar/services/ListManager.php +++ b/tools/bazar/services/ListManager.php @@ -4,6 +4,7 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; use YesWiki\Core\Service\DbService; +use YesWiki\Core\Service\HtmlPurifierService; use YesWiki\Core\Service\Mailer; use YesWiki\Core\Service\PageManager; use YesWiki\Security\Controller\SecurityController; @@ -14,21 +15,30 @@ class ListManager { protected $wiki; protected $dbService; - protected $tripleStore; - protected $securityController; + protected $htmlPurifierService; protected $pageManager; protected $params; + protected $securityController; + protected $tripleStore; public const TRIPLES_LIST_ID = 'liste'; protected $cachedLists; - public function __construct(Wiki $wiki, DbService $dbService, TripleStore $tripleStore, PageManager $pageManager, ParameterBagInterface $params, SecurityController $securityController) - { + public function __construct( + Wiki $wiki, + DbService $dbService, + HtmlPurifierService $htmlPurifierService, + PageManager $pageManager, + ParameterBagInterface $params, + SecurityController $securityController, + TripleStore $tripleStore + ) { $this->wiki = $wiki; $this->dbService = $dbService; $this->tripleStore = $tripleStore; $this->pageManager = $pageManager; + $this->htmlPurifierService = $htmlPurifierService; $this->params = $params; $this->securityController = $securityController; @@ -78,6 +88,8 @@ public function create($title, $values) } $id = genere_nom_wiki('Liste '.$title); + $values = $this->sanitizeHMTL($values); + if (YW_CHARSET !== 'UTF-8') { $values = array_map('utf8_encode', $values); $title = utf8_encode($title); @@ -96,6 +108,8 @@ public function update($id, $title, $values) if ($this->securityController->isWikiHibernated()) { throw new \Exception(_t('WIKI_IN_HIBERNATION')); } + + $values = $this->sanitizeHMTL($values); if (YW_CHARSET !== 'UTF-8') { $values = array_map('utf8_encode', $values); $title = utf8_encode($title); @@ -124,4 +138,11 @@ public function delete($id) $this->tripleStore->delete($id, TripleStore::TYPE_URI, null, '', ''); } + + private function sanitizeHMTL(array $values) + { + return array_map(function ($value) { + return $this->htmlPurifierService->cleanHTML($value); + }, $values); + } } diff --git a/tools/bazar/templates/fields/text.twig b/tools/bazar/templates/fields/text.twig index 351172d6d..ca02fe9ac 100644 --- a/tools/bazar/templates/fields/text.twig +++ b/tools/bazar/templates/fields/text.twig @@ -1 +1,3 @@ {% extends "@bazar/layouts/field.twig" %} + +{% block value %}{{ value|raw }}{% endblock %} \ No newline at end of file