1 |
efrain |
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 |
* @deprecated Deprecated since Moodle 3.10 (MDL-69050) - use {@see strip_disallowed_helpers}
|
|
|
179 |
*/
|
|
|
180 |
public function strip_blacklisted_helpers() {
|
|
|
181 |
throw new \coding_exception('\core\output\mustache_helper_collection::strip_blacklisted_helpers() has been removed.');
|
|
|
182 |
}
|
|
|
183 |
}
|