Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvement of auto-escaping #1030

Merged
merged 5 commits into from
Jun 30, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/api/configuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/designers/language-modifiers/language-modifier-escape.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
8 changes: 8 additions & 0 deletions docs/designers/language-modifiers/language-modifier-raw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# raw
wisskid marked this conversation as resolved.
Show resolved Hide resolved

Prevents variable escaping when [auto-escaping](../../api/configuring.md#enabling-auto-escaping) is activated.

## Basic usage
```smarty
{$myVar|raw}
```
12 changes: 11 additions & 1 deletion src/Compile/Modifier/EscapeModifierCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,32 @@ public function compile($params, \Smarty\Compiler\Template $compiler) {
}
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->getSmarty()->raw_output = 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->getSmarty()->raw_output = true;
return 'rawurlencode((string)' . $params[ 0 ] . ')';
case 'urlpathinfo':
$compiler->getSmarty()->raw_output = true;
return 'str_replace("%2F", "/", rawurlencode((string)' . $params[ 0 ] . '))';
case 'quotes':
$compiler->getSmarty()->raw_output = true;
// escape unescaped single quotes
return 'preg_replace("%(?<!\\\\\\\\)\'%", "\\\'", (string)' . $params[ 0 ] . ')';
case 'javascript':
$compiler->getSmarty()->raw_output = 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)' .
Expand All @@ -53,4 +63,4 @@ public function compile($params, \Smarty\Compiler\Template $compiler) {
}
return '$_smarty_tpl->getSmarty()->getModifierCallback(\'escape\')(' . join(', ', $params) . ')';
}
}
}
21 changes: 21 additions & 0 deletions src/Compile/Modifier/RawModifierCompiler.php
Original file line number Diff line number Diff line change
@@ -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->getSmarty()->raw_output = true;
return ($params[0]);
}
}
2 changes: 1 addition & 1 deletion src/Compile/ModifierCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter =
}
}
}
return $output;
return (string)$output;
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/Compile/PrintExpressionCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,13 @@ public function compile($args, \Smarty\Compiler\Template $compiler, $parameter =
$output = $compiler->compileModifier($modifierlist, $output);
}

if ($compiler->getTemplate()->getSmarty()->escape_html) {
if ($compiler->getTemplate()->getSmarty()->escape_html && !$compiler->getTemplate()->getSmarty()->raw_output) {
$output = "htmlspecialchars((string) ({$output}), ENT_QUOTES, '" . addslashes(\Smarty\Smarty::$_CHARSET) . "')";
}

}
$output = "<?php echo {$output};?>\n";
$compiler->getTemplate()->getSmarty()->raw_output = false;
}
return $output;
}
Expand Down
3 changes: 2 additions & 1 deletion src/Extension/DefaultExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public function getModifierCompiler(string $modifier): ?\Smarty\Compile\Modifier
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;
Expand Down Expand Up @@ -753,4 +754,4 @@ public function smarty_modifier_truncate($string, $length = 80, $etc = '...', $b
return $string;
}

}
}
7 changes: 7 additions & 0 deletions src/Smarty.php
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,13 @@ class Smarty extends \Smarty\TemplateBase {
*/
public $escape_html = false;

/**
* disabled autoescape (when set to true, the next variable output is not escaped)
*
* @var boolean
*/
public $raw_output = false;
wisskid marked this conversation as resolved.
Show resolved Hide resolved

/**
* start time for execution time calculation
*
Expand Down
35 changes: 35 additions & 0 deletions tests/UnitTests/A_Core/AutoEscape/AutoEscapeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,39 @@ function ($params, $content) { return $content == null ? null : "<p>".$content.
$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', '<[email protected]>');
$this->assertEquals("<[email protected]>", $this->smarty->fetch($tpl));
}

/**
* test autoescape + escape modifier = no double-escaping
*/
public function testAutoEscapeNoDoubleEscape() {
$tpl = $this->smarty->createTemplate('eval:{$foo|escape}');
$tpl->assign('foo', '<[email protected]>');
$this->assertEquals("&lt;[email protected]&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', '<[email protected]>');
$this->assertEquals("&amp;lt;[email protected]&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));
}
}
Loading