diff --git a/change_log.txt b/change_log.txt index e663dea3f..38bb8ffd6 100644 --- a/change_log.txt +++ b/change_log.txt @@ -1,4 +1,8 @@  ===== 3.1.25-dev===== (xx.xx.2015) + 14.06.2015 + - bugfix a relative sub template path could fail if template_dir path did contain /../ https://github.com/smarty-php/smarty/issues/50 + - optimization rework of path normalization + 13.06.2015 - bugfix a custom cache resource using smarty_cachereource_keyvaluestore.php did fail if php.ini mbstring.func_overload = 2 (forum topic 25568) diff --git a/libs/Smarty.class.php b/libs/Smarty.class.php index 11ebe4600..1ade0c07e 100644 --- a/libs/Smarty.class.php +++ b/libs/Smarty.class.php @@ -111,7 +111,7 @@ class Smarty extends Smarty_Internal_TemplateBase /** * smarty version */ - const SMARTY_VERSION = '3.1.25-dev/6'; + const SMARTY_VERSION = '3.1.25-dev/7'; /** * define variable scopes @@ -206,8 +206,7 @@ class Smarty extends Smarty_Internal_TemplateBase /** * contains directories outside of SMARTY_DIR that are to be muted by muteExpectedErrors() */ - public static $_muted_directories = array('./templates_c/' => null, - './cache/' => null); + public static $_muted_directories = array('./templates_c/' => null, './cache/' => null); /** * Flag denoting if Multibyte String functions are available @@ -617,10 +616,7 @@ class Smarty extends Smarty_Internal_TemplateBase * * @var array */ - public $plugin_search_order = array('function', - 'block', - 'compiler', - 'class'); + public $plugin_search_order = array('function', 'block', 'compiler', 'class'); /** * registered objects @@ -973,7 +969,7 @@ public function setTemplateDir($template_dir) { $this->template_dir = array(); foreach ((array) $template_dir as $k => $v) { - $this->template_dir[$k] = rtrim(strtr($v, '\\', '/'), '/') . '/'; + $this->template_dir[$k] = rtrim($v, '/\\') . DS; } $this->joined_template_dir = join(' # ', $this->template_dir); return $this; @@ -1021,7 +1017,7 @@ public function setConfigDir($config_dir) { $this->config_dir = array(); foreach ((array) $config_dir as $k => $v) { - $this->config_dir[$k] = rtrim(strtr($v, '\\', '/'), '/') . '/'; + $this->config_dir[$k] = rtrim($v, '/\\') . DS; } $this->joined_config_dir = join(' # ', $this->config_dir); return $this; @@ -1067,10 +1063,7 @@ public function getConfigDir($index = null) public function setPluginsDir($plugins_dir) { $this->plugins_dir = array(); - foreach ((array) $plugins_dir as $k => $v) { - $this->plugins_dir[$k] = rtrim(strtr($v, '\\', '/'), '/') . '/'; - } - $this->_is_file_cache = array(); + $this->addPluginsDir($plugins_dir); return $this; } @@ -1083,7 +1076,11 @@ public function setPluginsDir($plugins_dir) */ public function addPluginsDir($plugins_dir) { - $this->_addDir('plugins_dir', $plugins_dir); + // make sure we're dealing with an array + $this->plugins_dir = (array) $this->plugins_dir; + foreach ((array) $plugins_dir as $v) { + $this->plugins_dir[] = rtrim($v, '/\\') . DS; + } $this->plugins_dir = array_unique($this->plugins_dir); $this->_is_file_cache = array(); return $this; @@ -1108,7 +1105,7 @@ public function getPluginsDir() */ public function setCompileDir($compile_dir) { - $this->compile_dir = rtrim(strtr($compile_dir, '\\', '/'), '/') . '/'; + $this->compile_dir = rtrim($compile_dir, '/\\') . DS; if (!isset(Smarty::$_muted_directories[$this->compile_dir])) { Smarty::$_muted_directories[$this->compile_dir] = null; } @@ -1135,11 +1132,10 @@ public function getCompileDir() */ public function setCacheDir($cache_dir) { - $this->cache_dir = rtrim(strtr($cache_dir, '\\', '/'), '/') . '/'; + $this->cache_dir = rtrim($cache_dir, '/\\') . DS; if (!isset(Smarty::$_muted_directories[$this->cache_dir])) { Smarty::$_muted_directories[$this->cache_dir] = null; } - return $this; } @@ -1167,23 +1163,21 @@ private function _addDir($dirName, $dir, $key = null) if (is_array($dir)) { foreach ($dir as $k => $v) { - $v = rtrim(strtr($v, '\\', '/'), '/') . '/'; if (is_int($k)) { // indexes are not merged but appended - $this->{$dirName}[] = $v; + $this->{$dirName}[] = rtrim($v, '/\\') . DS; } else { // string indexes are overridden - $this->{$dirName}[$k] = $v; + $this->{$dirName}[$k] = rtrim($v, '/\\') . DS; } } } else { - $v = rtrim(strtr($dir, '\\', '/'), '/') . '/'; if ($key !== null) { // override directory at specified index - $this->{$dirName}[$key] = $v; + $this->{$dirName}[$key] = rtrim($dir, '/\\') . DS; } else { // append new directory - $this->{$dirName}[] = $v; + $this->{$dirName}[] = rtrim($dir, '/\\') . DS; } } } @@ -1420,8 +1414,7 @@ public function loadPlugin($plugin_name, $check = true) // loop through plugin dirs and find the plugin foreach ($this->getPluginsDir() as $_plugin_dir) { - $names = array($_plugin_dir . $_plugin_filename, - $_plugin_dir . strtolower($_plugin_filename),); + $names = array($_plugin_dir . $_plugin_filename, $_plugin_dir . strtolower($_plugin_filename),); foreach ($names as $file) { if (isset($this->_is_file_cache[$file]) ? $this->_is_file_cache[$file] : $this->_is_file_cache[$file] = is_file($file)) { require_once($file); @@ -1678,10 +1671,8 @@ public function __destruct() */ public function __get($name) { - $allowed = array('template_dir' => 'getTemplateDir', - 'config_dir' => 'getConfigDir', - 'plugins_dir' => 'getPluginsDir', - 'compile_dir' => 'getCompileDir', + $allowed = array('template_dir' => 'getTemplateDir', 'config_dir' => 'getConfigDir', + 'plugins_dir' => 'getPluginsDir', 'compile_dir' => 'getCompileDir', 'cache_dir' => 'getCacheDir',); if (isset($allowed[$name])) { @@ -1701,10 +1692,8 @@ public function __get($name) */ public function __set($name, $value) { - $allowed = array('template_dir' => 'setTemplateDir', - 'config_dir' => 'setConfigDir', - 'plugins_dir' => 'setPluginsDir', - 'compile_dir' => 'setCompileDir', + $allowed = array('template_dir' => 'setTemplateDir', 'config_dir' => 'setConfigDir', + 'plugins_dir' => 'setPluginsDir', 'compile_dir' => 'setCompileDir', 'cache_dir' => 'setCacheDir',); if (isset($allowed[$name])) { @@ -1750,8 +1739,7 @@ public static function mutingErrorHandler($errno, $errstr, $errfile, $errline, $ unset(Smarty::$_muted_directories[$key]); continue; } - $dir = array('file' => $file, - 'length' => strlen($file),); + $dir = array('file' => $file, 'length' => strlen($file),); } if (!strncmp($errfile, $dir['file'], $dir['length'])) { $_is_muted_directory = true; @@ -1793,8 +1781,7 @@ public static function muteExpectedErrors() - between file_exists() and filemtime() a possible race condition is opened, which does not exist using the simple @filemtime() approach. */ - $error_handler = array('Smarty', - 'mutingErrorHandler'); + $error_handler = array('Smarty', 'mutingErrorHandler'); $previous = set_error_handler($error_handler); // avoid dead loops diff --git a/libs/sysplugins/smarty_internal_resource_file.php b/libs/sysplugins/smarty_internal_resource_file.php index 95d229652..1c7051905 100644 --- a/libs/sysplugins/smarty_internal_resource_file.php +++ b/libs/sysplugins/smarty_internal_resource_file.php @@ -18,6 +18,8 @@ class Smarty_Internal_Resource_File extends Smarty_Resource { + private $dsMap = array('/' => array(array('\\', '/./'), '/.'), '\\' => array(array('/', '\\.\\'), '\\.')); + /** * build template filepath by traversing the template_dir array * @@ -29,44 +31,30 @@ class Smarty_Internal_Resource_File extends Smarty_Resource */ protected function buildFilepath(Smarty_Template_Source $source, Smarty_Internal_Template $_template = null) { - $file = str_replace(array('\\', '/./'), '/', $source->name); - if ($source->isConfig) { - $_directories = $source->smarty->getConfigDir(); - } else { - $_directories = $source->smarty->getTemplateDir(); - } - preg_match('#^((?P[\/]|[a-zA-Z]:[\/])|(\[(?P[^\]]+)\])|((?P\.[\/])?(?P(\.\.[\/])*))|(?P[\/]))?(?P.+)$#', $file, $fileMatch); + $file = $source->name; + preg_match('#^(?P[\\\/]|[a-zA-Z]:[\\\/])|(\[(?P[^\]]+)\])|(?P\.[\\\/])#', $file, $fileMatch); // save basename if (!empty($fileMatch['absolute'])) { + $file = $this->normalizePath($file); return is_file($file) ? $file : false; } // go relative to a given template? - if ($_template && $_template->parent instanceof Smarty_Internal_Template && (!empty($fileMatch['rel1']) || !empty($fileMatch['rel2']))) { + if (!empty($fileMatch['rel']) && $_template && $_template->parent instanceof Smarty_Internal_Template) { if ($_template->parent->source->type != 'file' && $_template->parent->source->type != 'extends' && !$_template->parent->allow_relative_path) { throw new SmartyException("Template '{$file}' cannot be relative to template of resource type '{$_template->parent->source->type}'"); } - $path = dirname($_template->parent->source->filepath); - if (!preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $path)) { - // the path gained from the parent template is relative to the current working directory - // as expansions (like include_path) have already been done - $path = str_replace('\\', '/', getcwd()) . '/' . $path; - } + $path = dirname($_template->parent->source->filepath) . DS . $file; // normalize path - $path = str_replace(array('\\', './'), array('/', ''), $path); - // simple relative - if (!empty($fileMatch['rel1'])) { - $file = $path . '/' . $fileMatch['file']; - } else { - for ($i = 1; $i <= substr_count($fileMatch['rel2'], '../'); $i ++) { - $path = substr($path, 0, strrpos($path, '/')); - } - $file = $path . '/' . $fileMatch['file']; - } + $path = $this->normalizePath($path); // files relative to a template only get one shot - return is_file($file) ? $file : false; + return is_file($path) ? $path : false; } - $_filepath = null; + if ($source->isConfig) { + $_directories = $source->smarty->getConfigDir(); + } else { + $_directories = $source->smarty->getTemplateDir(); + } // template_dir index? if (!empty($fileMatch['index'])) { $index = $fileMatch['index']; @@ -86,29 +74,24 @@ protected function buildFilepath(Smarty_Template_Source $source, Smarty_Internal } } if ($_directory) { - $_filepath = $_directory . $fileMatch['file']; - if (is_file($_filepath)) { - return $_filepath; + preg_match('#\](.+)$#', $file, $fileMatch); + $path = $_directory . $fileMatch[1]; + $path = $this->normalizePath($path); + if (is_file($path)) { + return $path; } + } else { + // index not found + return false; } } // relative file name? foreach ($_directories as $_directory) { - if (empty($fileMatch['rel2'])) { - $_filepath = $_directory . $fileMatch['file']; - } else { - if (false === strpos($_directory, '..')) { - for ($i = 1; $i <= substr_count($fileMatch['rel2'], '../') + 1; $i ++) { - $_directory = substr($_directory, 0, strrpos($_directory, '/')); - } - $_filepath = $_directory . '/' . $fileMatch['file']; - } else { - $_filepath = $_directory . $file; - } - } - if (is_file($_filepath)) { - return $_filepath; + $_filepath = $_directory . $file; + $path = $this->normalizePath($_filepath); + if (is_file($path)) { + return $path; } if ($source->smarty->use_include_path && !preg_match('/^([\/\\\\]|[a-zA-Z]:[\/\\\\])/', $_directory)) { // try PHP include_path @@ -117,25 +100,38 @@ protected function buildFilepath(Smarty_Template_Source $source, Smarty_Internal } else { $_filepath = Smarty_Internal_Get_Include_Path::getIncludePath($_filepath); } - if ($_filepath !== false) { - if (is_file($_filepath)) { - return $_filepath; + $path = $this->normalizePath($_filepath); + if (is_file($path)) { + return $path; } } } } // Could be relative to cwd - $path = str_replace('\\', '/', getcwd()); - if (empty($fileMatch['rel2'])) { - $file = $path . '/' . $fileMatch['file']; - } else { - for ($i = 1; $i <= substr_count($fileMatch['rel2'], '../'); $i ++) { - $path = substr($path, 0, strrpos($path, '/')); - } - $file = $path . '/' . $fileMatch['file']; + $path = $this->normalizePath(getcwd() . DS . $file); + return is_file($path) ? $path : false; + } + + /** + * Normalize path + * - remove /./ and /../ + * - make it absolute + * + * @param string $path file path + * + * @return string + */ + public function normalizePath($path) + { + if ($path[0] == '.') { + $path = getcwd() . DS . $path; + } + $path = str_replace($this->dsMap[DS][0], DS, $path); + while (strrpos($path, $this->dsMap[DS][1]) !== false) { + $path = preg_replace('#([\\\/][.][\\\/])|([\\\/][^\\\/]+[\\\/][.][.][\\\/])#', DS, $path); } - return is_file($file) ? $file : false; + return $path; } /** @@ -166,13 +162,10 @@ public function populate(Smarty_Template_Source $source, Smarty_Internal_Templat if (is_object($source->smarty->security_policy)) { $source->smarty->security_policy->isTrustedResourceDir($source->filepath); } - - $source->uid = sha1(getcwd() . $source->filepath); + $source->exists = true; + $source->uid = sha1($source->filepath); if ($source->smarty->compile_check && !isset($source->timestamp)) { - $source->timestamp = $source->exists = is_file($source->filepath); - if ($source->exists) { - $source->timestamp = @filemtime($source->filepath); - } + $source->timestamp = @filemtime($source->filepath); } } else { $source->timestamp = false;