From fcbb645671d948de1489bebd18d9738c59af2a52 Mon Sep 17 00:00:00 2001 From: Lars Bonczek Date: Fri, 19 Aug 2022 14:32:01 +0800 Subject: [PATCH] MDL-68066 output: Mustache - Add option to disable lambda rendering This commit is based on an outstanding pull request to the Mustache repo --- lib/mustache/src/Mustache/Compiler.php | 79 +++++++++++++++++--------- lib/mustache/src/Mustache/Engine.php | 29 +++++++--- 2 files changed, 73 insertions(+), 35 deletions(-) diff --git a/lib/mustache/src/Mustache/Compiler.php b/lib/mustache/src/Mustache/Compiler.php index 93a295ae5d2..766e3e10a45 100644 --- a/lib/mustache/src/Mustache/Compiler.php +++ b/lib/mustache/src/Mustache/Compiler.php @@ -26,31 +26,34 @@ class Mustache_Compiler private $entityFlags; private $charset; private $strictCallables; + private $disableLambdaRendering; /** * Compile a Mustache token parse tree into PHP source code. * - * @param string $source Mustache Template source code - * @param string $tree Parse tree of Mustache tokens - * @param string $name Mustache Template class name - * @param bool $customEscape (default: false) - * @param string $charset (default: 'UTF-8') - * @param bool $strictCallables (default: false) - * @param int $entityFlags (default: ENT_COMPAT) + * @param string $source Mustache Template source code + * @param array $tree Parse tree of Mustache tokens + * @param string $name Mustache Template class name + * @param bool $customEscape (default: false) + * @param string $charset (default: 'UTF-8') + * @param bool $strictCallables (default: false) + * @param int $entityFlags (default: ENT_COMPAT) + * @param bool $disableLambdaRendering (default: false) * * @return string Generated PHP source code */ - public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8', $strictCallables = false, $entityFlags = ENT_COMPAT) + public function compile($source, array $tree, $name, $customEscape = false, $charset = 'UTF-8', $strictCallables = false, $entityFlags = ENT_COMPAT, $disableLambdaRendering = false) { - $this->pragmas = $this->defaultPragmas; - $this->sections = array(); - $this->blocks = array(); - $this->source = $source; - $this->indentNextLine = true; - $this->customEscape = $customEscape; - $this->entityFlags = $entityFlags; - $this->charset = $charset; - $this->strictCallables = $strictCallables; + $this->pragmas = $this->defaultPragmas; + $this->sections = array(); + $this->blocks = array(); + $this->source = $source; + $this->indentNextLine = true; + $this->customEscape = $customEscape; + $this->entityFlags = $entityFlags; + $this->charset = $charset; + $this->strictCallables = $strictCallables; + $this->disableLambdaRendering = $disableLambdaRendering; return $this->writeCode($tree, $name); } @@ -331,14 +334,8 @@ class Mustache_Compiler if (%s) { $source = %s; - $result = (string) call_user_func($value, $source, %s); - if (strpos($result, \'{{\') === false) { - $buffer .= $result; - } else { - $buffer .= $this->mustache - ->loadLambda($result%s) - ->renderInternal($context); - } + $result = (string) call_user_func($value, $source, %s);%s + $buffer .= $result; } elseif (!empty($value)) { $values = $this->isIterable($value) ? $value : array($value); foreach ($values as $value) { @@ -352,6 +349,36 @@ class Mustache_Compiler } '; + const SECTION_RENDER_LAMBDA = ' + if (strpos($result, \'{{\') !== false) { + $result = $this->mustache + ->loadLambda($result%s) + ->renderInternal($context); + } + '; + + /** + * Helper function to compile section with and without lambda rendering. + * + * @param string $key + * @param string $callable + * @param string $source + * @param string $helper + * @param string $delims + * @param string $content + * + * @return string section code + */ + private function getSection($key, $callable, $source, $helper, $delims, $content) + { + $render = ''; + if (!$this->disableLambdaRendering) { + $render = sprintf($this->prepare(self::SECTION_RENDER_LAMBDA, 2), $delims); + } + + return sprintf($this->prepare(self::SECTION), $key, $callable, $source, $helper, $render, $content); + } + /** * Generate Mustache Template section PHP source. * @@ -383,7 +410,7 @@ class Mustache_Compiler $key = ucfirst(md5($delims . "\n" . $source)); if (!isset($this->sections[$key])) { - $this->sections[$key] = sprintf($this->prepare(self::SECTION), $key, $callable, $source, $helper, $delims, $this->walk($nodes, 2)); + $this->sections[$key] = $this->getSection($key, $callable, $source, $helper, $delims, $this->walk($nodes, 2)); } $method = $this->getFindMethod($id); diff --git a/lib/mustache/src/Mustache/Engine.php b/lib/mustache/src/Mustache/Engine.php index 0604f6d18ee..8e8bc1010e5 100644 --- a/lib/mustache/src/Mustache/Engine.php +++ b/lib/mustache/src/Mustache/Engine.php @@ -53,6 +53,7 @@ class Mustache_Engine private $charset = 'UTF-8'; private $logger; private $strictCallables = false; + private $disableLambdaRendering = false; private $pragmas = array(); private $delimiters; @@ -130,6 +131,11 @@ class Mustache_Engine * // This currently defaults to false, but will default to true in v3.0. * 'strict_callables' => true, * + * // Do not render the output of lambdas. Use this to prevent repeated rendering if the lambda already + * // takes care of rendering its content. This helps protect against mustache code injection when user + * // input is passed directly into the template. Defaults to false. + * 'disable_lambda_rendering' => true, + * * // Enable pragmas across all templates, regardless of the presence of pragma tags in the individual * // templates. * 'pragmas' => [Mustache_Engine::PRAGMA_FILTERS], @@ -204,6 +210,10 @@ class Mustache_Engine $this->strictCallables = $options['strict_callables']; } + if (isset($options['disable_lambda_rendering'])) { + $this->disableLambdaRendering = $options['disable_lambda_rendering']; + } + if (isset($options['delimiters'])) { $this->delimiters = $options['delimiters']; } @@ -624,14 +634,15 @@ class Mustache_Engine // // Keep this list in alphabetical order :) $chunks = array( - 'charset' => $this->charset, - 'delimiters' => $this->delimiters ? $this->delimiters : '{{ }}', - 'entityFlags' => $this->entityFlags, - 'escape' => isset($this->escape) ? 'custom' : 'default', - 'key' => ($source instanceof Mustache_Source) ? $source->getKey() : 'source', - 'pragmas' => $this->getPragmas(), - 'strictCallables' => $this->strictCallables, - 'version' => self::VERSION, + 'charset' => $this->charset, + 'delimiters' => $this->delimiters ? $this->delimiters : '{{ }}', + 'entityFlags' => $this->entityFlags, + 'escape' => isset($this->escape) ? 'custom' : 'default', + 'key' => ($source instanceof Mustache_Source) ? $source->getKey() : 'source', + 'pragmas' => $this->getPragmas(), + 'strictCallables' => $this->strictCallables, + 'disableLambdaRendering' => $this->disableLambdaRendering, + 'version' => self::VERSION, ); $key = json_encode($chunks); @@ -810,7 +821,7 @@ class Mustache_Engine $compiler = $this->getCompiler(); $compiler->setPragmas($this->getPragmas()); - return $compiler->compile($source, $tree, $name, isset($this->escape), $this->charset, $this->strictCallables, $this->entityFlags); + return $compiler->compile($source, $tree, $name, isset($this->escape), $this->charset, $this->strictCallables, $this->entityFlags, $this->disableLambdaRendering); } /**