From e4e401f315717b840bce6b0e6e68571b26f25c8e Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 21 Jun 2022 17:12:07 +0200 Subject: [PATCH] implemented mandatory escaping (cannot be disabled using |noescape) --- src/Latte/Compiler/Escaper.php | 17 ++++++ src/Latte/Compiler/Nodes/Php/ModifierNode.php | 6 +-- src/Latte/Runtime/Filters.php | 13 +++++ tests/common/Compiler.noescape.phpt | 54 +++++++++++++++++++ tests/common/contentType.html.css.phpt | 6 +++ tests/common/contentType.html.html.phpt | 6 +++ tests/common/contentType.html.javascript.phpt | 6 +++ tests/common/contentType.html.unknown.phpt | 6 +++ tests/common/expected/contentType.xml.html | 2 + tests/common/expected/contentType.xml.php | 4 ++ tests/common/templates/contentType.xml.latte | 2 + tests/filters/escapeHtmlChar.phpt | 22 ++++++++ 12 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 tests/common/Compiler.noescape.phpt create mode 100644 tests/filters/escapeHtmlChar.phpt diff --git a/src/Latte/Compiler/Escaper.php b/src/Latte/Compiler/Escaper.php index 6698008771..e53c05d1b2 100644 --- a/src/Latte/Compiler/Escaper.php +++ b/src/Latte/Compiler/Escaper.php @@ -229,6 +229,23 @@ public function escape(string $str): string } + public function escapeMandatory(string $str): string + { + return match ($this->contentType) { + ContentType::Html => match ($this->state) { + self::HtmlAttributeQuoted => 'LR\Filters::escapeHtmlChar(' . $str . ', ' . var_export($this->quote, true) . ')', + self::HtmlRawText => 'LR\Filters::escapeHtmlRawText(' . $str . ')', + default => $str, + }, + ContentType::Xml => match ($this->state) { + self::HtmlAttributeQuoted => 'LR\Filters::escapeHtmlChar(' . $str . ', ' . var_export($this->quote, true) . ')', + default => $str, + }, + default => $str, + }; + } + + public function check(string $str): string { if ($this->isHtmlAttribute() && $this->subState === self::Url) { diff --git a/src/Latte/Compiler/Nodes/Php/ModifierNode.php b/src/Latte/Compiler/Nodes/Php/ModifierNode.php index 3ecd661ff5..128ab2c021 100644 --- a/src/Latte/Compiler/Nodes/Php/ModifierNode.php +++ b/src/Latte/Compiler/Nodes/Php/ModifierNode.php @@ -68,9 +68,9 @@ public function printSimple(PrintContext $context, string $expr): string $expr = $escaper->check($expr); } - if ($escape) { - $expr = $escaper->escape($expr); - } + $expr = $escape + ? $escaper->escape($expr) + : $escaper->escapeMandatory($expr); return $expr; } diff --git a/src/Latte/Runtime/Filters.php b/src/Latte/Runtime/Filters.php index 75609d2966..18f7e0b854 100644 --- a/src/Latte/Runtime/Filters.php +++ b/src/Latte/Runtime/Filters.php @@ -100,6 +100,19 @@ public static function escapeHtmlComment($s): string } + /** + * Escapes a certain special character. + */ + public static function escapeHtmlChar($s, string $char): string + { + return str_replace( + $char, + htmlspecialchars($char, ENT_QUOTES | ENT_HTML5), + (string) $s, + ); + } + + /** * Escapes string for use everywhere inside XML (except for comments). */ diff --git a/tests/common/Compiler.noescape.phpt b/tests/common/Compiler.noescape.phpt new file mode 100644 index 0000000000..c12296e459 --- /dev/null +++ b/tests/common/Compiler.noescape.phpt @@ -0,0 +1,54 @@ +setLoader(new Latte\Loaders\StringLoader); + +// html text +Assert::match( + '

', + $latte->renderToString('

{=""|noescape}

'), +); + +// in tag +Assert::match( + '

>

', + $latte->renderToString('

"|noescape}>

'), +); + +// attribute unquoted values +Assert::match( + '

>

', + $latte->renderToString('

"|noescape}>

'), +); + +// attribute quoted values +Assert::match( + '

', + $latte->renderToString('

"|noescape}">

'), +); + +Assert::match( + '

\'>

', + $latte->renderToString('

"|noescape}\'>

'), +); + +Assert::match( + '

', + $latte->renderToString('

"|noescape}">

'), +); + +Assert::match( + '

', + $latte->renderToString('

"|noescape}">

'), +); diff --git a/tests/common/contentType.html.css.phpt b/tests/common/contentType.html.css.phpt index 336f5f1048..2950a08806 100644 --- a/tests/common/contentType.html.css.phpt +++ b/tests/common/contentType.html.css.phpt @@ -54,3 +54,9 @@ Assert::match( '', $latte->renderToString(''), ); + +// no escape +Assert::match( + '', + $latte->renderToString('"|noescape}'), +); diff --git a/tests/common/contentType.html.html.phpt b/tests/common/contentType.html.html.phpt index 3c076b3a88..d39e29ed8c 100644 --- a/tests/common/contentType.html.html.phpt +++ b/tests/common/contentType.html.html.phpt @@ -28,6 +28,12 @@ Assert::match( ), ); +// no escape +Assert::match( + '', + $latte->renderToString('"|noescape}'), +); + // content of '>")], ), ); + +// no escape +Assert::match( + '', + $latte->renderToString('"|noescape}'), +); diff --git a/tests/common/contentType.html.unknown.phpt b/tests/common/contentType.html.unknown.phpt index 8d0db6a8ec..bc2a4104e5 100644 --- a/tests/common/contentType.html.unknown.phpt +++ b/tests/common/contentType.html.unknown.phpt @@ -28,6 +28,12 @@ Assert::match( ), ); +// no escape +Assert::match( + '', + $latte->renderToString('"|noescape}'), +); + // content of