Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | 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
namespace core\output;
18
 
19
/**
20
 * Custom Moodle helper collection for mustache.
21
 *
1441 ariadna 22
 * @package core
1 efrain 23
 * @copyright  2019 Ryan Wyllie <ryan@moodle.com>
24
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25
 */
26
class mustache_helper_collection extends \Mustache_HelperCollection {
27
    /**
28
     * @var string[] Names of helpers that aren't allowed to be called within other helpers.
29
     */
30
    private $disallowednestedhelpers = [];
31
 
32
    /**
33
     * Helper Collection constructor.
34
     *
35
     * Optionally accepts an array (or Traversable) of `$name => $helper` pairs.
36
     *
37
     * @throws \Mustache_Exception_InvalidArgumentException if the $helpers argument isn't an array or Traversable
38
     *
39
     * @param array|\Traversable $helpers (default: null)
40
     * @param string[] $disallowednestedhelpers Names of helpers that aren't allowed to be called within other helpers.
41
     */
42
    public function __construct($helpers = null, array $disallowednestedhelpers = []) {
43
        $this->disallowednestedhelpers = $disallowednestedhelpers;
44
        parent::__construct($helpers);
45
    }
46
 
47
    /**
48
     * Add a helper to this collection.
49
     *
50
     * This function has overridden the parent implementation to provide disallowing
51
     * functionality for certain helpers to prevent them being called from within
52
     * other helpers. This is because the JavaScript helper can be used in a
53
     * security exploit if it can be nested.
54
     *
55
     * The function will wrap callable helpers in an anonymous function that strips
56
     * out the disallowed helpers from the source string before giving it to the
57
     * helper function. This prevents the disallowed helper functions from being
58
     * called by nested render functions from within other helpers.
59
     *
60
     * @see \Mustache_HelperCollection::add()
61
     * @param string $name
62
     * @param mixed  $helper
63
     */
64
    public function add($name, $helper) {
65
 
66
        $disallowedlist = $this->disallowednestedhelpers;
67
 
68
        if (is_callable($helper) && !empty($disallowedlist)) {
1441 ariadna 69
            $helper = function ($source, \Mustache_LambdaHelper $lambdahelper) use ($helper, $disallowedlist) {
1 efrain 70
 
71
                // Temporarily override the disallowed helpers to return nothing
72
                // so that they can't be executed from within other helpers.
73
                $disabledhelpers = $this->disable_helpers($disallowedlist);
74
                // Call the original function with the modified sources.
75
                $result = call_user_func($helper, $source, $lambdahelper);
76
                // Restore the original disallowed helper implementations now
77
                // that this helper has finished executing so that the rest of
78
                // the rendering process continues to work correctly.
79
                $this->restore_helpers($disabledhelpers);
80
                // Lastly parse the returned string to strip out any unwanted helper
81
                // tags that were added through variable substitution (or other means).
82
                // This is done because a secondary render is called on the result
83
                // of a helper function if it still includes mustache tags. See
84
                // the section function of Mustache_Compiler for details.
85
                return $this->strip_disallowed_helpers($disallowedlist, $result);
86
            };
87
        }
88
 
89
        parent::add($name, $helper);
90
    }
91
 
92
    /**
93
     * Disable a list of helpers (by name) by changing their implementation to
94
     * simply return an empty string.
95
     *
96
     * @param  string[] $names List of helper names to disable
97
     * @return \Closure[] The original helper functions indexed by name
98
     */
99
    private function disable_helpers($names) {
100
        $disabledhelpers = [];
101
 
102
        foreach ($names as $name) {
103
            if ($this->has($name)) {
104
                $function = $this->get($name);
105
                // Null out the helper. Must call parent::add here to avoid
106
                // a recursion problem.
1441 ariadna 107
                parent::add($name, function () {
1 efrain 108
                    return '';
109
                });
110
 
111
                $disabledhelpers[$name] = $function;
112
            }
113
        }
114
 
115
        return $disabledhelpers;
116
    }
117
 
118
    /**
119
     * Restore the original helper implementations. Typically used after disabling
120
     * a helper.
121
     *
122
     * @param  \Closure[] $helpers The helper functions indexed by name
123
     */
124
    private function restore_helpers($helpers) {
125
        foreach ($helpers as $name => $function) {
126
            // Restore the helper functions. Must call parent::add here to avoid
127
            // a recursion problem.
128
            parent::add($name, $function);
129
        }
130
    }
131
 
132
    /**
133
     * Parse the given string and remove any reference to disallowed helpers.
134
     *
135
     * E.g.
136
     * $disallowedlist = ['js'];
137
     * $string = "core, move, {{#js}} some nasty JS hack {{/js}}"
138
     * result: "core, move, {{}}"
139
     *
140
     * @param  string[] $disallowedlist List of helper names to strip
141
     * @param  string $string String to parse
142
     * @return string Parsed string
143
     */
144
    public function strip_disallowed_helpers($disallowedlist, $string) {
145
        $starttoken = \Mustache_Tokenizer::T_SECTION;
146
        $endtoken = \Mustache_Tokenizer::T_END_SECTION;
147
        if ($endtoken == '/') {
148
            $endtoken = '\/';
149
        }
150
 
1441 ariadna 151
        $regexes = array_map(function ($name) use ($starttoken, $endtoken) {
1 efrain 152
            // We only strip out the name of the helper (excluding delimiters)
153
            // the user is able to change the delimeters on a per template
154
            // basis so they may not be curly braces.
1441 ariadna 155
            return '/\s*' . $starttoken . '\s*' . $name . '\W+.*' . $endtoken . '\s*' . $name . '\s*/';
1 efrain 156
        }, $disallowedlist);
157
 
158
        // This will strip out unwanted helpers from the $source string
159
        // before providing it to the original helper function.
160
        // E.g.
161
        // Before:
162
        // "core, move, {{#js}} some nasty JS hack {{/js}}"
163
        // After:
164
        // "core, move, {{}}".
1441 ariadna 165
        return preg_replace_callback($regexes, function () {
1 efrain 166
            return '';
167
        }, $string);
168
    }
169
 
170
    /**
171
     * @deprecated Deprecated since Moodle 3.10 (MDL-69050) - use {@see strip_disallowed_helpers}
172
     */
173
    public function strip_blacklisted_helpers() {
174
        throw new \coding_exception('\core\output\mustache_helper_collection::strip_blacklisted_helpers() has been removed.');
175
    }
176
}