Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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
}