summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAmaury Bouchard <amaury@amaury.net>2024-06-30 13:25:30 +0200
committerGitHub <noreply@github.com>2024-06-30 13:25:30 +0200
commit2289fa69f1b5b9be48bf6f821265f1fef79cca40 (patch)
tree26f8796ac39b17e5dc65921e3b5dcd72647fd024
parent3cb35854326a94120cd415b81db28c53d95d0d5d (diff)
downloadsmarty-2289fa69f1b5b9be48bf6f821265f1fef79cca40.tar.gz
smarty-2289fa69f1b5b9be48bf6f821265f1fef79cca40.tar.bz2
smarty-2289fa69f1b5b9be48bf6f821265f1fef79cca40.zip
Improvement of auto-escaping (#1030)
* Evolution of auto-escaping: no double-escaping when using the 'escape' modifier; add the 'force' mode to the 'escape' modifier; add the 'raw' modifier. * Add 'raw' modifier's documentation --------- Co-authored-by: Simon Wisselink <s.wisselink@iwink.nl>
-rw-r--r--changelog/1030.md1
-rw-r--r--docs/api/configuring.md29
-rw-r--r--docs/designers/language-modifiers/language-modifier-escape.md4
-rw-r--r--docs/designers/language-modifiers/language-modifier-raw.md8
-rw-r--r--mkdocs.yml1
-rw-r--r--src/Compile/Modifier/EscapeModifierCompiler.php12
-rw-r--r--src/Compile/Modifier/RawModifierCompiler.php21
-rw-r--r--src/Compile/ModifierCompiler.php2
-rw-r--r--src/Compile/PrintExpressionCompiler.php3
-rw-r--r--src/Compiler/Template.php23
-rw-r--r--src/Extension/DefaultExtension.php3
-rw-r--r--tests/UnitTests/A_Core/AutoEscape/AutoEscapeTest.php64
12 files changed, 165 insertions, 6 deletions
diff --git a/changelog/1030.md b/changelog/1030.md
new file mode 100644
index 00000000..f7cba021
--- /dev/null
+++ b/changelog/1030.md
@@ -0,0 +1 @@
+- Improvement of auto-escaping [#1030](https://github.com/smarty-php/smarty/pull/1030) \ No newline at end of file
diff --git a/docs/api/configuring.md b/docs/api/configuring.md
index ee2ebf7e..540f6906 100644
--- a/docs/api/configuring.md
+++ b/docs/api/configuring.md
@@ -143,6 +143,35 @@ Enable auto-escaping for HTML as follows:
$smarty->setEscapeHtml(true);
```
+When auto-escaping is enabled, the `|escape` modifier's default mode (`html`) has no effect,
+to avoid double-escaping. It is possible to force it with the `force` mode.
+Other modes (`htmlall`, `url`, `urlpathinfo`, `quotes`, `javascript`) may be used
+with the result you might expect, without double-escaping.
+
+Even when auto-escaping is enabled, you might want to display the content of a variable without
+escaping it. To do so, use the `|raw` modifier.
+
+Examples (with auto-escaping enabled):
+```smarty
+{* these three statements are identical *}
+{$myVar}
+{$myVar|escape}
+{$myVar|escape:'html'}
+
+{* no double-escaping on these statements *}
+{$var|escape:'htmlall'}
+{$myVar|escape:'url'}
+{$myVar|escape:'urlpathinfo'}
+{$myVar|escape:'quotes'}
+{$myVar|escape:'javascript'}
+
+{* no escaping at all *}
+{$myVar|raw}
+
+{* force double-escaping *}
+{$myVar|escape:'force'}
+```
+
## Disabling compile check
By default, Smarty tests to see if the
current template has changed since the last time
diff --git a/docs/designers/language-modifiers/language-modifier-escape.md b/docs/designers/language-modifiers/language-modifier-escape.md
index 6fd5dd2b..18c98f1c 100644
--- a/docs/designers/language-modifiers/language-modifier-escape.md
+++ b/docs/designers/language-modifiers/language-modifier-escape.md
@@ -73,6 +73,6 @@ This snippet is useful for emails, but see also
<a href="mailto:{$EmailAddress|escape:'hex'}">{$EmailAddress|escape:'mail'}</a>
```
-See also [escaping smarty parsing](../language-basic-syntax/language-escaping.md),
+See also [auto-escaping](../../api/configuring.md#enabling-auto-escaping), [escaping smarty parsing](../language-basic-syntax/language-escaping.md),
[`{mailto}`](../language-custom-functions/language-function-mailto.md) and the [obfuscating email
-addresses](../../appendixes/tips.md#obfuscating-e-mail-addresses) page.
+addresses](../../appendixes/tips.md#obfuscating-e-mail-addresses) pages.
diff --git a/docs/designers/language-modifiers/language-modifier-raw.md b/docs/designers/language-modifiers/language-modifier-raw.md
new file mode 100644
index 00000000..e9cce97d
--- /dev/null
+++ b/docs/designers/language-modifiers/language-modifier-raw.md
@@ -0,0 +1,8 @@
+# raw
+
+Prevents variable escaping when [auto-escaping](../../api/configuring.md#enabling-auto-escaping) is activated.
+
+## Basic usage
+```smarty
+{$myVar|raw}
+```
diff --git a/mkdocs.yml b/mkdocs.yml
index 74ad349c..4fffe7d9 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -68,6 +68,7 @@ nav:
- 'noprint': 'designers/language-modifiers/language-modifier-noprint.md'
- 'number_format': 'designers/language-modifiers/language-modifier-number-format.md'
- 'nl2br': 'designers/language-modifiers/language-modifier-nl2br.md'
+ - 'raw': 'designers/language-modifiers/language-modifier-raw.md'
- 'regex_replace': 'designers/language-modifiers/language-modifier-regex-replace.md'
- 'replace': 'designers/language-modifiers/language-modifier-replace.md'
- 'round': 'designers/language-modifiers/language-modifier-round.md'
diff --git a/src/Compile/Modifier/EscapeModifierCompiler.php b/src/Compile/Modifier/EscapeModifierCompiler.php
index b600e08c..4352359f 100644
--- a/src/Compile/Modifier/EscapeModifierCompiler.php
+++ b/src/Compile/Modifier/EscapeModifierCompiler.php
@@ -24,22 +24,32 @@ class EscapeModifierCompiler extends Base {
}
switch ($esc_type) {
case 'html':
+ case 'force':
+ // in case of auto-escaping, and without the 'force' option, no double-escaping
+ if ($compiler->getSmarty()->escape_html && $esc_type != 'force')
+ return $params[0];
+ // otherwise, escape the variable
return 'htmlspecialchars((string)' . $params[ 0 ] . ', ENT_QUOTES, ' . var_export($char_set, true) . ', ' .
var_export($double_encode, true) . ')';
// no break
case 'htmlall':
+ $compiler->setRawOutput(true);
return 'htmlentities(mb_convert_encoding((string)' . $params[ 0 ] . ', \'UTF-8\', ' .
var_export($char_set, true) . '), ENT_QUOTES, \'UTF-8\', ' .
var_export($double_encode, true) . ')';
// no break
case 'url':
+ $compiler->setRawOutput(true);
return 'rawurlencode((string)' . $params[ 0 ] . ')';
case 'urlpathinfo':
+ $compiler->setRawOutput(true);
return 'str_replace("%2F", "/", rawurlencode((string)' . $params[ 0 ] . '))';
case 'quotes':
+ $compiler->setRawOutput(true);
// escape unescaped single quotes
return 'preg_replace("%(?<!\\\\\\\\)\'%", "\\\'", (string)' . $params[ 0 ] . ')';
case 'javascript':
+ $compiler->setRawOutput(true);
// escape quotes and backslashes, newlines, etc.
// see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
return 'strtr((string)' .
@@ -53,4 +63,4 @@ class EscapeModifierCompiler extends Base {
}
return '$_smarty_tpl->getSmarty()->getModifierCallback(\'escape\')(' . join(', ', $params) . ')';
}
-} \ No newline at end of file
+}
diff --git a/src/Compile/Modifier/RawModifierCompiler.php b/src/Compile/Modifier/RawModifierCompiler.php
new file mode 100644
index 00000000..c001bb1d
--- /dev/null
+++ b/src/Compile/Modifier/RawModifierCompiler.php
@@ -0,0 +1,21 @@
+<?php
+namespace Smarty\Compile\Modifier;
+
+use Smarty\Exception;
+
+/**
+ * Smarty raw modifier plugin
+ * Type: modifier
+ * Name: raw
+ * Purpose: when escaping is enabled by default, generates a raw output of a variable
+ *
+ * @author Amaury Bouchard
+ */
+
+class RawModifierCompiler extends Base {
+
+ public function compile($params, \Smarty\Compiler\Template $compiler) {
+ $compiler->setRawOutput(true);
+ return ($params[0]);
+ }
+}
diff --git a/src/Compile/ModifierCompiler.php b/src/Compile/ModifierCompiler.php
index 4e623224..dfec3d77 100644
--- a/src/Compile/ModifierCompiler.php
+++ b/src/Compile/ModifierCompiler.php
@@ -75,7 +75,7 @@ class ModifierCompiler extends Base {
}
}
}
- return $output;
+ return (string)$output;
}
/**
diff --git a/src/Compile/PrintExpressionCompiler.php b/src/Compile/PrintExpressionCompiler.php
index 3302254f..3642551e 100644
--- a/src/Compile/PrintExpressionCompiler.php
+++ b/src/Compile/PrintExpressionCompiler.php
@@ -82,12 +82,13 @@ class PrintExpressionCompiler extends Base {
$output = $compiler->compileModifier($modifierlist, $output);
}
- if ($compiler->getTemplate()->getSmarty()->escape_html) {
+ if ($compiler->getTemplate()->getSmarty()->escape_html && !$compiler->isRawOutput()) {
$output = "htmlspecialchars((string) ({$output}), ENT_QUOTES, '" . addslashes(\Smarty\Smarty::$_CHARSET) . "')";
}
}
$output = "<?php echo {$output};?>\n";
+ $compiler->setRawOutput(false);
}
return $output;
}
diff --git a/src/Compiler/Template.php b/src/Compiler/Template.php
index 1d4aa244..9b2c1a1f 100644
--- a/src/Compiler/Template.php
+++ b/src/Compiler/Template.php
@@ -313,6 +313,12 @@ class Template extends BaseCompiler {
*/
private $noCacheStackDepth = 0;
+ /**
+ * disabled auto-escape (when set to true, the next variable output is not auto-escaped)
+ *
+ * @var boolean
+ */
+ private $raw_output = false;
/**
* Initialize compiler
@@ -1486,4 +1492,21 @@ class Template extends BaseCompiler {
public function getTagStack(): array {
return $this->_tag_stack;
}
+
+ /**
+ * Should the next variable output be raw (true) or auto-escaped (false)
+ * @return bool
+ */
+ public function isRawOutput(): bool {
+ return $this->raw_output;
+ }
+
+ /**
+ * Should the next variable output be raw (true) or auto-escaped (false)
+ * @param bool $raw_output
+ * @return void
+ */
+ public function setRawOutput(bool $raw_output): void {
+ $this->raw_output = $raw_output;
+ }
}
diff --git a/src/Extension/DefaultExtension.php b/src/Extension/DefaultExtension.php
index cecc4a46..88390b94 100644
--- a/src/Extension/DefaultExtension.php
+++ b/src/Extension/DefaultExtension.php
@@ -35,6 +35,7 @@ class DefaultExtension extends Base {
case 'lower': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\LowerModifierCompiler(); break;
case 'nl2br': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\Nl2brModifierCompiler(); break;
case 'noprint': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\NoPrintModifierCompiler(); break;
+ case 'raw': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\RawModifierCompiler(); break;
case 'round': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\RoundModifierCompiler(); break;
case 'str_repeat': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\StrRepeatModifierCompiler(); break;
case 'string_format': $this->modifiers[$modifier] = new \Smarty\Compile\Modifier\StringFormatModifierCompiler(); break;
@@ -753,4 +754,4 @@ class DefaultExtension extends Base {
return $string;
}
-} \ No newline at end of file
+}
diff --git a/tests/UnitTests/A_Core/AutoEscape/AutoEscapeTest.php b/tests/UnitTests/A_Core/AutoEscape/AutoEscapeTest.php
index f26f0f93..4a4ef066 100644
--- a/tests/UnitTests/A_Core/AutoEscape/AutoEscapeTest.php
+++ b/tests/UnitTests/A_Core/AutoEscape/AutoEscapeTest.php
@@ -61,4 +61,68 @@ class AutoEscapeTest extends PHPUnit_Smarty
$this->assertEquals("<p>hi</p>", $this->smarty->fetch($tpl));
}
+ /**
+ * test autoescape + raw modifier
+ */
+ public function testAutoEscapeRaw() {
+ $tpl = $this->smarty->createTemplate('eval:{$foo|raw}');
+ $tpl->assign('foo', '<a@b.c>');
+ $this->assertEquals("<a@b.c>", $this->smarty->fetch($tpl));
+ }
+
+ /**
+ * test autoescape + escape modifier = no double-escaping
+ */
+ public function testAutoEscapeNoDoubleEscape() {
+ $tpl = $this->smarty->createTemplate('eval:{$foo|escape}');
+ $tpl->assign('foo', '<a@b.c>');
+ $this->assertEquals("&lt;a@b.c&gt;", $this->smarty->fetch($tpl));
+ }
+
+ /**
+ * test autoescape + escape modifier = force double-escaping
+ */
+ public function testAutoEscapeForceDoubleEscape() {
+ $tpl = $this->smarty->createTemplate('eval:{$foo|escape:\'force\'}');
+ $tpl->assign('foo', '<a@b.c>');
+ $this->assertEquals("&amp;lt;a@b.c&amp;gt;", $this->smarty->fetch($tpl));
+ }
+
+ /**
+ * test autoescape + escape modifier = special escape
+ */
+ public function testAutoEscapeSpecialEscape() {
+ $tpl = $this->smarty->createTemplate('eval:{$foo|escape:\'url\'}');
+ $tpl->assign('foo', 'aa bb');
+ $this->assertEquals("aa%20bb", $this->smarty->fetch($tpl));
+ }
+
+ /**
+ * test autoescape + escape modifier = special escape
+ */
+ public function testAutoEscapeSpecialEscape2() {
+ $tpl = $this->smarty->createTemplate('eval:{$foo|escape:\'url\'}');
+ $tpl->assign('foo', '<BR>');
+ $this->assertEquals("%3CBR%3E", $this->smarty->fetch($tpl));
+ }
+
+ /**
+ * test autoescape + escape modifier = special escape
+ */
+ public function testAutoEscapeSpecialEscape3() {
+ $tpl = $this->smarty->createTemplate('eval:{$foo|escape:\'htmlall\'}');
+ $tpl->assign('foo', '<BR>');
+ $this->assertEquals("&lt;BR&gt;", $this->smarty->fetch($tpl));
+ }
+
+
+ /**
+ * test autoescape + escape modifier = special escape
+ */
+ public function testAutoEscapeSpecialEscape4() {
+ $tpl = $this->smarty->createTemplate('eval:{$foo|escape:\'javascript\'}');
+ $tpl->assign('foo', '<\'');
+ $this->assertEquals("<\\'", $this->smarty->fetch($tpl));
+ }
+
}