See Release Notes
Long Term Support Release
<?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Custom Moodle helper collection for mustache. * * @copyright 2019 Ryan Wyllie <ryan@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\output; /** * Custom Moodle helper collection for mustache. * * @copyright 2019 Ryan Wyllie <ryan@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class mustache_helper_collection extends \Mustache_HelperCollection { /** * @var string[] Names of helpers that aren't allowed to be called within other helpers. */< private $blacklistednestedhelpers = [];> private $disallowednestedhelpers = [];/** * Helper Collection constructor. * * Optionally accepts an array (or Traversable) of `$name => $helper` pairs. * * @throws \Mustache_Exception_InvalidArgumentException if the $helpers argument isn't an array or Traversable * * @param array|\Traversable $helpers (default: null)< * @param string[] $blacklistednestedhelpers Names of helpers that aren't allowed to be called within other helpers.> * @param string[] $disallowednestedhelpers Names of helpers that aren't allowed to be called within other helpers.*/< public function __construct($helpers = null, array $blacklistednestedhelpers = []) { < $this->blacklistednestedhelpers = $blacklistednestedhelpers;> public function __construct($helpers = null, array $disallowednestedhelpers = []) { > $this->disallowednestedhelpers = $disallowednestedhelpers;parent::__construct($helpers); } /** * Add a helper to this collection. *< * This function has overridden the parent implementation to provide blacklist> * This function has overridden the parent implementation to provide disallowing* functionality for certain helpers to prevent them being called from within * other helpers. This is because the JavaScript helper can be used in a * security exploit if it can be nested. * * The function will wrap callable helpers in an anonymous function that strips< * out the blacklisted helpers from the source string before giving it to the < * helper function. This prevents the blacklisted helper functions from being> * out the disallowed helpers from the source string before giving it to the > * helper function. This prevents the disallowed helper functions from being* called by nested render functions from within other helpers. * * @see \Mustache_HelperCollection::add() * @param string $name * @param mixed $helper */< public function add($name, $helper) < { < $blacklist = $this->blacklistednestedhelpers;> public function add($name, $helper) {< if (is_callable($helper) && !empty($blacklist)) { < $helper = function($source, \Mustache_LambdaHelper $lambdahelper) use ($helper, $blacklist) {> $disallowedlist = $this->disallowednestedhelpers;< // Temporarily override the blacklisted helpers to return nothing> if (is_callable($helper) && !empty($disallowedlist)) { > $helper = function($source, \Mustache_LambdaHelper $lambdahelper) use ($helper, $disallowedlist) { > > // Temporarily override the disallowed helpers to return nothing// so that they can't be executed from within other helpers.< $disabledhelpers = $this->disable_helpers($blacklist);> $disabledhelpers = $this->disable_helpers($disallowedlist);// Call the original function with the modified sources. $result = call_user_func($helper, $source, $lambdahelper);< // Restore the original blacklisted helper implementations now> // Restore the original disallowed helper implementations now// that this helper has finished executing so that the rest of // the rendering process continues to work correctly. $this->restore_helpers($disabledhelpers); // Lastly parse the returned string to strip out any unwanted helper // tags that were added through variable substitution (or other means). // This is done because a secondary render is called on the result // of a helper function if it still includes mustache tags. See // the section function of Mustache_Compiler for details.< return $this->strip_blacklisted_helpers($blacklist, $result);> return $this->strip_disallowed_helpers($disallowedlist, $result);}; } parent::add($name, $helper); } /** * Disable a list of helpers (by name) by changing their implementation to * simply return an empty string. * * @param string[] $names List of helper names to disable * @return \Closure[] The original helper functions indexed by name */ private function disable_helpers($names) { $disabledhelpers = []; foreach ($names as $name) { if ($this->has($name)) { $function = $this->get($name); // Null out the helper. Must call parent::add here to avoid // a recursion problem. parent::add($name, function() { return ''; }); $disabledhelpers[$name] = $function; } } return $disabledhelpers; } /** * Restore the original helper implementations. Typically used after disabling * a helper. * * @param \Closure[] $helpers The helper functions indexed by name */ private function restore_helpers($helpers) { foreach ($helpers as $name => $function) { // Restore the helper functions. Must call parent::add here to avoid // a recursion problem. parent::add($name, $function); } } /**< * Parse the given string and remove any reference to blacklisted helpers.> * Parse the given string and remove any reference to disallowed helpers.* * E.g.< * $blacklist = ['js'];> * $disallowedlist = ['js'];* $string = "core, move, {{#js}} some nasty JS hack {{/js}}" * result: "core, move, {{}}" *< * @param string[] $blacklist List of helper names to strip> * @param string[] $disallowedlist List of helper names to strip* @param string $string String to parse * @return string Parsed string */< public function strip_blacklisted_helpers($blacklist, $string) {> public function strip_disallowed_helpers($disallowedlist, $string) {$starttoken = \Mustache_Tokenizer::T_SECTION; $endtoken = \Mustache_Tokenizer::T_END_SECTION; if ($endtoken == '/') { $endtoken = '\/'; } $regexes = array_map(function($name) use ($starttoken, $endtoken) { // We only strip out the name of the helper (excluding delimiters) // the user is able to change the delimeters on a per template // basis so they may not be curly braces. return '/\s*' . $starttoken . '\s*'. $name . '\W+.*' . $endtoken . '\s*' . $name . '\s*/';< }, $blacklist);> }, $disallowedlist);// This will strip out unwanted helpers from the $source string // before providing it to the original helper function. // E.g. // Before: // "core, move, {{#js}} some nasty JS hack {{/js}}" // After:< // "core, move, {{}}"> // "core, move, {{}}".return preg_replace_callback($regexes, function() { return ''; }, $string);> } } > } > /** > * Parse the given string and remove any reference to disallowed helpers. > * > * @deprecated Deprecated since Moodle 3.10 (MDL-69050) - use {@see self::strip_disallowed_helpers()} > * @param string[] $disallowedlist List of helper names to strip > * @param string $string String to parse > * @return string Parsed string > */ > public function strip_blacklisted_helpers($disallowedlist, $string) { > > debugging('mustache_helper_collection::strip_blacklisted_helpers() is deprecated. ' . > 'Please use mustache_helper_collection::strip_disallowed_helpers() instead.', DEBUG_DEVELOPER); > > return $this->strip_disallowed_helpers($disallowedlist, $string);