Compare commits

..

10 Commits

Author SHA1 Message Date
Simon Wisselink 2272e4d819 Add change in regex for PRCE (PHP < 7.3) 2022-09-12 12:09:41 +02:00
Simon Wisselink f3c991962b Fixed use of rand() without a parameter in math function
Fixes #794
2022-09-12 12:03:31 +02:00
Simon Wisselink f8f97b4e2d Fixed PHP8.1 deprecation errors in upper modifier #788 2022-09-10 12:34:20 +02:00
Mark Fettig 1bc7c722a3 address PHP 8.1 'explode', 'number_format', and 'replace' deprecations (#755) 2022-09-09 22:39:24 +02:00
Simon Wisselink 02968a82b5 Update SECURITY.md
Removed comment that still indicated v4 wasn’t released yet.
2022-08-16 22:43:47 +02:00
Simon Wisselink 97aeb14c6f Merge branch 'release/4.2.0' 2022-08-01 23:57:38 +02:00
Simon Wisselink ed454551a3 version bump 2022-08-01 23:57:36 +02:00
Simon Wisselink f4b26a3cbf Fixed second param of unescape modifier (#778)
Fixes #777
2022-08-01 23:49:19 +02:00
Simon Wisselink 20a8026ccd Merge branch 'fix-issue-549-v3' of github.com:AnrDaemon/smarty into AnrDaemon-fix-issue-549-v3 (#771) 2022-07-19 22:55:07 +02:00
Simon Wisselink 27910bf2c1 git push origin masterMerge branch 'JonisoftGermany-master' 2022-07-17 00:54:29 +02:00
18 changed files with 381 additions and 15 deletions
+10
View File
@@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- Fixed PHP8.1 deprecation errors in modifiers (upper, explode, number_format and replace) [#755](https://github.com/smarty-php/smarty/pull/755) and [#788](https://github.com/smarty-php/smarty/pull/788)
- Fixed use of `rand()` without a parameter in math function [#794](https://github.com/smarty-php/smarty/issues/794)
## [4.2.0] - 2022-08-01
### Fixed
- Fixed problems with smarty_mb_str_replace [#549](https://github.com/smarty-php/smarty/issues/549)
- Fixed second parameter of unescape modifier not working [#777](https://github.com/smarty-php/smarty/issues/777)
### Changed
- Updated HTML of the debug template [#599](https://github.com/smarty-php/smarty/pull/599)
+1 -1
View File
@@ -2,7 +2,7 @@
## Supported Versions
Smarty currently supports the latest minor version of Smarty 3 and Smarty 4. (Smarty 4 has not been released yet.)
Smarty currently supports the latest minor version of Smarty 3 and Smarty 4.
| Version | Supported |
| ------- | ------------------ |
+1 -1
View File
@@ -98,7 +98,7 @@ class Smarty extends Smarty_Internal_TemplateBase
/**
* smarty version
*/
const SMARTY_VERSION = '4.1.1';
const SMARTY_VERSION = '4.2.0';
/**
* define variable scopes
*/
+1 -1
View File
@@ -70,7 +70,7 @@ function smarty_function_math($params, $template)
$number = '(?:\d+(?:[,.]\d+)?|pi|π)'; // What is a number
$functionsOrVars = '((?:0x[a-fA-F0-9]+)|([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*))';
$operators = '[,+\/*\^%-]'; // Allowed math operators
$regexp = '/^(('.$number.'|'.$functionsOrVars.'|('.$functionsOrVars.'\s*\((?1)+\)|\((?1)+\)))(?:'.$operators.'(?1))?)+$/';
$regexp = '/^(('.$number.'|'.$functionsOrVars.'|('.$functionsOrVars.'\s*\((?1)*\)|\((?1)*\)))(?:'.$operators.'(?1))?)+$/';
if (!preg_match($regexp, $equation)) {
trigger_error("math: illegal characters", E_USER_WARNING);
+25
View File
@@ -0,0 +1,25 @@
<?php
/**
* Smarty plugin
*
* @package Smarty
* @subpackage PluginsModifier
*/
/**
* Smarty explode modifier plugin
* Type: modifier
* Name: explode
* Purpose: split a string by a string
*
* @param string $separator
* @param string $string
* @param int|null $limit
*
* @return array
*/
function smarty_modifier_explode($separator, $string, ?int $limit = null)
{
// provide $string default to prevent deprecation errors in PHP >=8.1
return explode($separator, $string ?? '', $limit ?? PHP_INT_MAX);
}
+26
View File
@@ -0,0 +1,26 @@
<?php
/**
* Smarty plugin
*
* @package Smarty
* @subpackage PluginsModifier
*/
/**
* Smarty number_format modifier plugin
* Type: modifier
* Name: number_format
* Purpose: Format a number with grouped thousands
*
* @param float|null $num
* @param int $decimals
* @param string|null $decimal_separator
* @param string|null $thousands_separator
*
* @return string
*/
function smarty_modifier_number_format(?float $num, int $decimals = 0, ?string $decimal_separator = ".", ?string $thousands_separator = ",")
{
// provide $num default to prevent deprecation errors in PHP >=8.1
return number_format($num ?? 0.0, $decimals, $decimal_separator, $thousands_separator);
}
+15 -7
View File
@@ -14,20 +14,28 @@
* @author Rodney Rehm
*
* @param array $params parameters
* @param Smarty_Internal_TemplateCompilerBase $compiler
*
* @return string with compiled code
*/
function smarty_modifiercompiler_unescape($params)
function smarty_modifiercompiler_unescape($params, Smarty_Internal_TemplateCompilerBase $compiler)
{
if (!isset($params[ 1 ])) {
$params[ 1 ] = 'html';
}
$compiler->template->_checkPlugins(
array(
array(
'function' => 'smarty_literal_compiler_param',
'file' => SMARTY_PLUGINS_DIR . 'shared.literal_compiler_param.php'
)
)
);
$esc_type = smarty_literal_compiler_param($params, 1, 'html');
if (!isset($params[ 2 ])) {
$params[ 2 ] = '\'' . addslashes(Smarty::$_CHARSET) . '\'';
} else {
$params[ 2 ] = "'{$params[ 2 ]}'";
}
switch (trim($params[ 1 ], '"\'')) {
switch ($esc_type) {
case 'entity':
case 'htmlall':
if (Smarty::$_MBSTRING) {
+2 -2
View File
@@ -21,8 +21,8 @@
function smarty_modifiercompiler_upper($params)
{
if (Smarty::$_MBSTRING) {
return 'mb_strtoupper(' . $params[ 0 ] . ', \'' . addslashes(Smarty::$_CHARSET) . '\')';
return 'mb_strtoupper(' . $params[ 0 ] . ' ?? \'\', \'' . addslashes(Smarty::$_CHARSET) . '\')';
}
// no MBString fallback
return 'strtoupper(' . $params[ 0 ] . ')';
return 'strtoupper(' . $params[ 0 ] . ' ?? \'\')';
}
+35 -1
View File
@@ -44,9 +44,43 @@ if (!function_exists('smarty_mb_str_replace')) {
}
}
} else {
$parts = mb_split(preg_quote($search), $subject) ?: array();
$mb_reg_charset = mb_regex_encoding();
// Check if mbstring regex is using UTF-8
$reg_is_unicode = !strcasecmp($mb_reg_charset, "UTF-8");
if(!$reg_is_unicode) {
// ...and set to UTF-8 if not
mb_regex_encoding("UTF-8");
}
// See if charset used by Smarty is matching one used by regex...
$current_charset = mb_regex_encoding();
$convert_result = (bool)strcasecmp(Smarty::$_CHARSET, $current_charset);
if($convert_result) {
// ...convert to it if not.
$subject = mb_convert_encoding($subject, $current_charset, Smarty::$_CHARSET);
$search = mb_convert_encoding($search, $current_charset, Smarty::$_CHARSET);
$replace = mb_convert_encoding($replace, $current_charset, Smarty::$_CHARSET);
}
$parts = mb_split(preg_quote($search), $subject ?? "") ?: array();
// If original regex encoding was not unicode...
if(!$reg_is_unicode) {
// ...restore original regex encoding to avoid breaking the system.
mb_regex_encoding($mb_reg_charset);
}
if($parts === false) {
// This exception is thrown if call to mb_split failed.
// Usually it happens, when $search or $replace are not valid for given mb_regex_encoding().
// There may be other cases for it to fail, please file an issue if you find a reproducible one.
throw new SmartyException("Source string is not a valid $current_charset sequence (probably)");
}
$count = count($parts) - 1;
$subject = implode($replace, $parts);
// Convert results back to charset used by Smarty, if needed.
if($convert_result) {
$subject = mb_convert_encoding($subject, Smarty::$_CHARSET, $current_charset);
}
}
return $subject;
}
@@ -210,6 +210,7 @@ class PluginFunctionHtmlSelectDateTest extends PHPUnit_Smarty
public function setUp(): void
{
$this->setUpSmarty(dirname(__FILE__));
$this->smarty->setErrorReporting(E_ALL & ~E_DEPRECATED);
$year = date('Y');
$this->now = mktime(15, 0, 0, 2, 20, $year);
@@ -0,0 +1,55 @@
<?php
namespace UnitTests\TemplateSource\TagTests\PluginModifier;
/**
* class for modifier tests
*
* @runTestsInSeparateProcess
* @preserveGlobalState disabled
* @backupStaticAttributes enabled
*/
class PluginModifierExplodeTest extends \PHPUnit_Smarty
{
public function setUp(): void
{
$this->setUpSmarty(__DIR__);
}
/**
* @return void
* @throws \SmartyException
*
* @dataProvider explodeDataProvider
*/
public function testExplode($template, $subject, $expectedString)
{
$this->smarty->assign('subject', $subject);
$tpl = $this->smarty->createTemplate($template);
$res = $this->smarty->fetch($tpl);
$this->assertEquals($expectedString, $res);
}
public function explodeDataProvider()
{
return [
'default' => [
'template' => 'string:{","|explode:$subject|json_encode}',
'subject' => 'a,b,c,d',
'expectedString' => '["a","b","c","d"]',
],
'withNoDelimiterFound' => [
'template' => 'string:{","|explode:$subject|json_encode}',
'subject' => 'abcd',
'expectedString' => '["abcd"]',
],
'withNull' => [
'template' => 'string:{","|explode:$subject|json_encode}',
'subject' => null,
'expectedString' => '[""]',
],
];
}
}
@@ -0,0 +1,59 @@
<?php
namespace UnitTests\TemplateSource\TagTests\PluginModifier;
/**
* class for modifier tests
*
* @runTestsInSeparateProcess
* @preserveGlobalState disabled
* @backupStaticAttributes enabled
*/
class PluginModifierNumberFormatTest extends \PHPUnit_Smarty
{
public function setUp(): void
{
$this->setUpSmarty(__DIR__);
}
/**
* @return void
* @throws \SmartyException
*
* @dataProvider numberFormatDataProvider
*/
public function testNumberFormat($template, $subject, $expectedString)
{
$this->smarty->assign('subject', $subject);
$tpl = $this->smarty->createTemplate($template);
$this->assertEquals($expectedString, $this->smarty->fetch($tpl));
}
public function numberFormatDataProvider()
{
return [
'default' => [
'template' => 'string:{$subject|number_format}',
'subject' => 12345,
'expectedString' => "12,345",
],
'withDecimalDefault' => [
'template' => 'string:{$subject|number_format}',
'subject' => 12345.6789,
'expectedString' => "12,346",
],
'withDecimalAndExtras' => [
'template' => 'string:{$subject|number_format:2:"-":"~"}',
'subject' => 12345.6789,
'expectedString' => "12~345-68",
],
'withNull' => [
'template' => 'string:{$subject|number_format}',
'subject' => null,
'expectedString' => 0,
],
];
}
}
@@ -0,0 +1,54 @@
<?php
namespace UnitTests\TemplateSource\TagTests\PluginModifier;
/**
* class for modifier tests
*
* @runTestsInSeparateProcess
* @preserveGlobalState disabled
* @backupStaticAttributes enabled
*/
class PluginModifierReplaceTest extends \PHPUnit_Smarty
{
public function setUp(): void
{
$this->setUpSmarty(__DIR__);
}
/**
* @return void
* @throws \SmartyException
*
* @dataProvider replaceDataProvider
*/
public function testReplace($template, $subject, $expectedString)
{
$this->smarty->assign('subject', $subject);
$tpl = $this->smarty->createTemplate($template);
$this->assertEquals($expectedString, $this->smarty->fetch($tpl));
}
public function replaceDataProvider()
{
return [
'default' => [
'template' => 'string:{$subject|replace:",":"-"}',
'subject' => "a,b,c,d",
'expectedString' => "a-b-c-d",
],
'doNothing' => [
'template' => 'string:{$subject|replace:"":""}',
'subject' => "a,b,c,d",
'expectedString' => "a,b,c,d",
],
'withNull' => [
'template' => 'string:{$subject|replace:"":""}',
'subject' => null,
'expectedString' => "",
],
];
}
}
@@ -63,6 +63,11 @@ class PluginModifierUnescapeTest extends PHPUnit_Smarty
$tpl = $this->smarty->createTemplate('string:{"' . $encoded . '"|unescape:"url"}');
$this->assertEquals($result, $this->smarty->fetch($tpl));
}
}
?>
public function testCharset()
{
$tpl = $this->smarty->createTemplate("string:{'&#039;Stiff Opposition Expected to Casketless Funeral Plan&#039;'|unescape:'htmlall':'utf-8'}");
$this->assertEquals("'Stiff Opposition Expected to Casketless Funeral Plan'", $this->smarty->fetch($tpl));
}
}
@@ -156,4 +156,12 @@ class MathTest extends PHPUnit_Smarty
$this->assertEquals($expected, $this->smarty->fetch($tpl));
}
public function testRand()
{
$tpl = $this->smarty->createTemplate('eval:{$x = "0"}{math equation="x * rand()" x=$x}');
// this assertion may seem silly, but it serves to prove that using rand() without a parameter
// will not trigger a security error (see https://github.com/smarty-php/smarty/issues/794)
$this->assertEquals("0", $this->smarty->fetch($tpl));
}
}
@@ -0,0 +1,77 @@
<?php
/**
* Smarty PHPunit tests - issue #549 regression tests
*
* @package PHPunit
* @author Andrey Repin <anrdaemon@yandex.ru>
*/
/**
* class for compiler tests
*
* @runTestsInSeparateProcess
* @preserveGlobalState disabled
* @backupStaticAttributes enabled
*
* mb_split breaks if Smarty encoding is not the same as mbstring regex encoding.
*/
class MbSplitEncodingIssue549Test extends PHPUnit_Smarty
{
/** @var string Saved Smarty charset */
private $charset;
/** @var array Source data for tests, hexed to protect from accidental reencoding */
private $data = array(
"subject" => '4772c3bc6e6577616c64', // "Grünewald"
"pattern" => '77616c64', // "wald"
"replacement" => '7374c3bc726d', // "stürm"
"result" => '4772c3bc6e657374c3bc726d', // "Grünestürm"
);
public function setUp(): void
{
if(!\Smarty::$_MBSTRING)
{
$this->markTestSkipped("mbstring extension is not in use by Smarty");
}
$this->charset = \Smarty::$_CHARSET;
$this->setUpSmarty(dirname(__FILE__));
}
protected function tearDown(): void
{
\Smarty::$_CHARSET = $this->charset ?: \Smarty::$_CHARSET;
$this->cleanDirs();
}
/** Provider for testReplaceModifier
*/
public function encodingPairsProvider()
{
return array(
"with non-UNICODE src/non-UNICODE regex (PHP < 5.6 default)" => array("Windows-1252", "EUC-JP"),
"with UTF-8 src/non-UNICODE regex (PHP < 5.6 default)" => array("UTF-8", "EUC-JP"),
"with UTF-8 src/UTF-8 regex (PHP >= 5.6)" => array("UTF-8", "UTF-8"),
"with non-UNICODE src/UTF-8 regex" => array("Windows-1252", "UTF-8"),
);
}
/** Test behavior of `replace` modifier with different source and regex encodings
*
* @dataProvider encodingPairsProvider
*/
public function testReplaceModifier($mb_int_encoding, $mb_regex_encoding)
{
$data = $this->data;
\array_walk($data, function(&$value, $key) use($mb_int_encoding) {
$value = \mb_convert_encoding(pack("H*", $value), $mb_int_encoding, "UTF-8");
});
\extract($data, \EXTR_SKIP);
\mb_regex_encoding($mb_regex_encoding);
\Smarty::$_CHARSET = $mb_int_encoding;
$this->assertEquals($result, $this->smarty->fetch("string:{\"$subject\"|replace:\"$pattern\":\"$replacement\"}"));
}
}
@@ -0,0 +1,2 @@
# Ignore anything in here, but keep this directory
*
@@ -0,0 +1,2 @@
# Ignore anything in here, but keep this directory
*