See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 401] [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Custom Moodle helper collection for mustache. 19 * 20 * @copyright 2019 Ryan Wyllie <ryan@moodle.com> 21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 22 */ 23 24 namespace core\output; 25 26 /** 27 * Custom Moodle helper collection for mustache. 28 * 29 * @copyright 2019 Ryan Wyllie <ryan@moodle.com> 30 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 31 */ 32 class mustache_helper_collection extends \Mustache_HelperCollection { 33 34 /** 35 * @var string[] Names of helpers that aren't allowed to be called within other helpers. 36 */ 37 private $disallowednestedhelpers = []; 38 39 /** 40 * Helper Collection constructor. 41 * 42 * Optionally accepts an array (or Traversable) of `$name => $helper` pairs. 43 * 44 * @throws \Mustache_Exception_InvalidArgumentException if the $helpers argument isn't an array or Traversable 45 * 46 * @param array|\Traversable $helpers (default: null) 47 * @param string[] $disallowednestedhelpers Names of helpers that aren't allowed to be called within other helpers. 48 */ 49 public function __construct($helpers = null, array $disallowednestedhelpers = []) { 50 $this->disallowednestedhelpers = $disallowednestedhelpers; 51 parent::__construct($helpers); 52 } 53 54 /** 55 * Add a helper to this collection. 56 * 57 * This function has overridden the parent implementation to provide disallowing 58 * functionality for certain helpers to prevent them being called from within 59 * other helpers. This is because the JavaScript helper can be used in a 60 * security exploit if it can be nested. 61 * 62 * The function will wrap callable helpers in an anonymous function that strips 63 * out the disallowed helpers from the source string before giving it to the 64 * helper function. This prevents the disallowed helper functions from being 65 * called by nested render functions from within other helpers. 66 * 67 * @see \Mustache_HelperCollection::add() 68 * @param string $name 69 * @param mixed $helper 70 */ 71 public function add($name, $helper) { 72 73 $disallowedlist = $this->disallowednestedhelpers; 74 75 if (is_callable($helper) && !empty($disallowedlist)) { 76 $helper = function($source, \Mustache_LambdaHelper $lambdahelper) use ($helper, $disallowedlist) { 77 78 // Temporarily override the disallowed helpers to return nothing 79 // so that they can't be executed from within other helpers. 80 $disabledhelpers = $this->disable_helpers($disallowedlist); 81 // Call the original function with the modified sources. 82 $result = call_user_func($helper, $source, $lambdahelper); 83 // Restore the original disallowed helper implementations now 84 // that this helper has finished executing so that the rest of 85 // the rendering process continues to work correctly. 86 $this->restore_helpers($disabledhelpers); 87 // Lastly parse the returned string to strip out any unwanted helper 88 // tags that were added through variable substitution (or other means). 89 // This is done because a secondary render is called on the result 90 // of a helper function if it still includes mustache tags. See 91 // the section function of Mustache_Compiler for details. 92 return $this->strip_disallowed_helpers($disallowedlist, $result); 93 }; 94 } 95 96 parent::add($name, $helper); 97 } 98 99 /** 100 * Disable a list of helpers (by name) by changing their implementation to 101 * simply return an empty string. 102 * 103 * @param string[] $names List of helper names to disable 104 * @return \Closure[] The original helper functions indexed by name 105 */ 106 private function disable_helpers($names) { 107 $disabledhelpers = []; 108 109 foreach ($names as $name) { 110 if ($this->has($name)) { 111 $function = $this->get($name); 112 // Null out the helper. Must call parent::add here to avoid 113 // a recursion problem. 114 parent::add($name, function() { 115 return ''; 116 }); 117 118 $disabledhelpers[$name] = $function; 119 } 120 } 121 122 return $disabledhelpers; 123 } 124 125 /** 126 * Restore the original helper implementations. Typically used after disabling 127 * a helper. 128 * 129 * @param \Closure[] $helpers The helper functions indexed by name 130 */ 131 private function restore_helpers($helpers) { 132 foreach ($helpers as $name => $function) { 133 // Restore the helper functions. Must call parent::add here to avoid 134 // a recursion problem. 135 parent::add($name, $function); 136 } 137 } 138 139 /** 140 * Parse the given string and remove any reference to disallowed helpers. 141 * 142 * E.g. 143 * $disallowedlist = ['js']; 144 * $string = "core, move, {{#js}} some nasty JS hack {{/js}}" 145 * result: "core, move, {{}}" 146 * 147 * @param string[] $disallowedlist List of helper names to strip 148 * @param string $string String to parse 149 * @return string Parsed string 150 */ 151 public function strip_disallowed_helpers($disallowedlist, $string) { 152 $starttoken = \Mustache_Tokenizer::T_SECTION; 153 $endtoken = \Mustache_Tokenizer::T_END_SECTION; 154 if ($endtoken == '/') { 155 $endtoken = '\/'; 156 } 157 158 $regexes = array_map(function($name) use ($starttoken, $endtoken) { 159 // We only strip out the name of the helper (excluding delimiters) 160 // the user is able to change the delimeters on a per template 161 // basis so they may not be curly braces. 162 return '/\s*' . $starttoken . '\s*'. $name . '\W+.*' . $endtoken . '\s*' . $name . '\s*/'; 163 }, $disallowedlist); 164 165 // This will strip out unwanted helpers from the $source string 166 // before providing it to the original helper function. 167 // E.g. 168 // Before: 169 // "core, move, {{#js}} some nasty JS hack {{/js}}" 170 // After: 171 // "core, move, {{}}". 172 return preg_replace_callback($regexes, function() { 173 return ''; 174 }, $string); 175 } 176 177 /** 178 * Parse the given string and remove any reference to disallowed helpers. 179 * 180 * @deprecated Deprecated since Moodle 3.10 (MDL-69050) - use {@see self::strip_disallowed_helpers()} 181 * @param string[] $disallowedlist List of helper names to strip 182 * @param string $string String to parse 183 * @return string Parsed string 184 */ 185 public function strip_blacklisted_helpers($disallowedlist, $string) { 186 187 debugging('mustache_helper_collection::strip_blacklisted_helpers() is deprecated. ' . 188 'Please use mustache_helper_collection::strip_disallowed_helpers() instead.', DEBUG_DEVELOPER); 189 190 return $this->strip_disallowed_helpers($disallowedlist, $string); 191 } 192 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body