From 41fc03c63bad2dcdf76786128031a7ba638d79ba Mon Sep 17 00:00:00 2001 From: Laurent Destailleur Date: Mon, 1 Dec 2025 13:54:27 +0100 Subject: [PATCH] Fix: Do not transform [__XXX__] string when MAIN_RESTRICTHTML_ONLY_VALID_HTML is on. --- htdocs/core/customreports.php | 4 ++-- htdocs/core/lib/functions.lib.php | 28 +++++++++++++++++++++++++--- test/phpunit/SecurityGETPOSTTest.php | 10 ++++++++-- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/htdocs/core/customreports.php b/htdocs/core/customreports.php index c5cb56bc25a..85b3f7e3349 100644 --- a/htdocs/core/customreports.php +++ b/htdocs/core/customreports.php @@ -984,8 +984,8 @@ if (!empty($search_measures) && !empty($search_xaxis)) { $sql = preg_replace_callback( "/(\w+)\.(\w+)\s*(=|!=|<>|<|>|<=|>=)\s*'(\d{4})-(\d{2})-(\d{2})'/", /** - * @param array $matches - * @return string SQL filter condition + * @param array $matches + * @return string SQL filter condition */ function (array $matches): string { global $db; diff --git a/htdocs/core/lib/functions.lib.php b/htdocs/core/lib/functions.lib.php index a44357ecd92..83e320ff103 100644 --- a/htdocs/core/lib/functions.lib.php +++ b/htdocs/core/lib/functions.lib.php @@ -9392,19 +9392,41 @@ function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = ' //$out = '
'.dol_nl2br($out).'
'; } + // Note: is transformed into + // We don't want that, so we protect [__xxx__] by replacing [ and ] before loadHTML and restore them after saveHTML + $out = preg_replace_callback( + '/\[__([0-9a-zA-Z_]+)__\]/', + /** + * @param array $m Array of matches + * @return string Translated string for the key + */ + function ($m) { + return 'BRACKETSTART__' . $m[1] . '__BRACKETEND'; }, + $out); + $dom->loadHTML($out, LIBXML_HTML_NODEFDTD | LIBXML_ERR_NONE | LIBXML_HTML_NOIMPLIED | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOERROR | LIBXML_NOXMLDECL); $dom->encoding = 'UTF-8'; $out = trim($dom->saveHTML()); + // Restore [ and ] that were protected before loadHTML + $out = preg_replace_callback( + '/BRACKETSTART__([0-9a-zA-Z_]+)__BRACKETEND/', + /** + * @param array $m Array of matches + * @return string Translated string for the key + */ + function ($m) { + return '[__' . $m[1] . '__]'; }, + $out); + // Remove the trick added to solve pb with text in utf8 and text without parent tag //$out = preg_replace('/^'.preg_quote('', '/').'/', '', $out); $out = preg_replace('/^' . preg_quote('<', '/') . '[^<>]+' . preg_quote('>
', '/') . '/', '', $out); $out = preg_replace('/' . preg_quote('
', '/') . '$/', '', trim($out)); - // $out = preg_replace('/^<\?xml encoding="UTF-8">
/', '', $out); - // $out = preg_replace('/<\/div>$/', '', $out); - // var_dump('rrrrrrrrrrrrrrrrrrrrrrrrrrrrr'.$out); + //$out = preg_replace('/^<\?xml encoding="UTF-8">
/', '', $out); + //$out = preg_replace('/<\/div>$/', '', $out); if (!$outishtml) { // If $out was not HTML content we made before a dol_nl2br so we must do the opposite operation now $out = str_replace('
', '', $out); diff --git a/test/phpunit/SecurityGETPOSTTest.php b/test/phpunit/SecurityGETPOSTTest.php index ed423c6cdea..888d8e00ef3 100644 --- a/test/phpunit/SecurityGETPOSTTest.php +++ b/test/phpunit/SecurityGETPOSTTest.php @@ -129,7 +129,6 @@ class SecurityGETPOSTTest extends CommonClassTest $_GET["param20"] = ''; - $result = GETPOST('id', 'int'); // Must return nothing print __METHOD__." result=".$result."\n"; $this->assertEquals('', $result); @@ -316,6 +315,13 @@ class SecurityGETPOSTTest extends CommonClassTest $conf->global->MAIN_RESTRICTHTML_ONLY_VALID_HTML = 1; $conf->global->MAIN_RESTRICTHTML_ONLY_VALID_HTML_TIDY = 0; + $_POST["pagecontentwithaconstantvarinurl"] = '
https://[__aaa__]/aaa.html'; + $result = GETPOST("pagecontentwithaconstantvarinurl", 'restricthtml'); + print __METHOD__." result=".$result."\n"; + $this->assertEquals('https://[__aaa__]/aaa.html', $result, 'Test on HTML content with url with constant'); + + + //$_POST["param0"] = 'A real string with aaa and " inside content'; $result = GETPOST("param0", 'restricthtml'); $resultexpected = 'A real string with aaa and " and \' and & inside content'; @@ -456,6 +462,7 @@ class SecurityGETPOSTTest extends CommonClassTest print __METHOD__." result=".$result."\n"; $this->assertEquals('x3aalert(1)', $result, 'Test for backtopage param'); + // Test with restricthtml + MAIN_SECURITY_MAX_IMG_IN_HTML_CONTENT to test limit of external links $conf->global->MAIN_SECURITY_MAX_IMG_IN_HTML_CONTENT = 3; $_POST["pagecontentwithlinks"] = ''; @@ -498,7 +505,6 @@ class SecurityGETPOSTTest extends CommonClassTest print __METHOD__." result=".$result."\n"; $this->assertEquals('ErrorHTMLExternalLinksNotAllowed (Example: http://ddd)', $result, 'Test on MAIN_DISALLOW_URL_INTO_DESCRIPTIONS = 1 (no links to http allowed)'); - // Test substitution in GET url $user->fk_user = 999; $mysoc->country_id = 1;