This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Handles compiling Mustache templates into PHP rendering functions |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | * @since 1.25 |
||
22 | */ |
||
23 | class TemplateParser { |
||
24 | /** |
||
25 | * @var string The path to the Mustache templates |
||
26 | */ |
||
27 | protected $templateDir; |
||
28 | |||
29 | /** |
||
30 | * @var callable[] Array of cached rendering functions |
||
31 | */ |
||
32 | protected $renderers; |
||
33 | |||
34 | /** |
||
35 | * @var bool Always compile template files |
||
36 | */ |
||
37 | protected $forceRecompile = false; |
||
38 | |||
39 | /** |
||
40 | * @param string $templateDir |
||
41 | * @param bool $forceRecompile |
||
42 | */ |
||
43 | public function __construct( $templateDir = null, $forceRecompile = false ) { |
||
44 | $this->templateDir = $templateDir ?: __DIR__ . '/templates'; |
||
45 | $this->forceRecompile = $forceRecompile; |
||
46 | } |
||
47 | |||
48 | /** |
||
49 | * Constructs the location of the the source Mustache template |
||
50 | * @param string $templateName The name of the template |
||
51 | * @return string |
||
52 | * @throws UnexpectedValueException If $templateName attempts upwards directory traversal |
||
53 | */ |
||
54 | protected function getTemplateFilename( $templateName ) { |
||
55 | // Prevent upwards directory traversal using same methods as Title::secureAndSplit |
||
56 | View Code Duplication | if ( |
|
57 | strpos( $templateName, '.' ) !== false && |
||
58 | ( |
||
59 | $templateName === '.' || $templateName === '..' || |
||
60 | strpos( $templateName, './' ) === 0 || |
||
61 | strpos( $templateName, '../' ) === 0 || |
||
62 | strpos( $templateName, '/./' ) !== false || |
||
63 | strpos( $templateName, '/../' ) !== false || |
||
64 | substr( $templateName, -2 ) === '/.' || |
||
65 | substr( $templateName, -3 ) === '/..' |
||
66 | ) |
||
67 | ) { |
||
68 | throw new UnexpectedValueException( "Malformed \$templateName: $templateName" ); |
||
69 | } |
||
70 | |||
71 | return "{$this->templateDir}/{$templateName}.mustache"; |
||
72 | } |
||
73 | |||
74 | /** |
||
75 | * Returns a given template function if found, otherwise throws an exception. |
||
76 | * @param string $templateName The name of the template (without file suffix) |
||
77 | * @return callable |
||
78 | * @throws RuntimeException |
||
79 | */ |
||
80 | protected function getTemplate( $templateName ) { |
||
81 | // If a renderer has already been defined for this template, reuse it |
||
82 | if ( isset( $this->renderers[$templateName] ) && |
||
83 | is_callable( $this->renderers[$templateName] ) |
||
84 | ) { |
||
85 | return $this->renderers[$templateName]; |
||
86 | } |
||
87 | |||
88 | $filename = $this->getTemplateFilename( $templateName ); |
||
89 | |||
90 | if ( !file_exists( $filename ) ) { |
||
91 | throw new RuntimeException( "Could not locate template: {$filename}" ); |
||
92 | } |
||
93 | |||
94 | // Read the template file |
||
95 | $fileContents = file_get_contents( $filename ); |
||
96 | |||
97 | // Generate a quick hash for cache invalidation |
||
98 | $fastHash = md5( $fileContents ); |
||
99 | |||
100 | // Fetch a secret key for building a keyed hash of the PHP code |
||
101 | $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' ); |
||
102 | $secretKey = $config->get( 'SecretKey' ); |
||
103 | |||
104 | if ( $secretKey ) { |
||
105 | // See if the compiled PHP code is stored in cache. |
||
106 | $cache = ObjectCache::getLocalServerInstance( CACHE_ANYTHING ); |
||
107 | $key = $cache->makeKey( 'template', $templateName, $fastHash ); |
||
108 | $code = $this->forceRecompile ? null : $cache->get( $key ); |
||
109 | |||
110 | if ( !$code ) { |
||
111 | $code = $this->compileForEval( $fileContents, $filename ); |
||
112 | |||
113 | // Prefix the cached code with a keyed hash (64 hex chars) as an integrity check |
||
114 | $cache->set( $key, hash_hmac( 'sha256', $code, $secretKey ) . $code ); |
||
115 | } else { |
||
116 | // Verify the integrity of the cached PHP code |
||
117 | $keyedHash = substr( $code, 0, 64 ); |
||
118 | $code = substr( $code, 64 ); |
||
119 | if ( $keyedHash !== hash_hmac( 'sha256', $code, $secretKey ) ) { |
||
120 | // Generate a notice if integrity check fails |
||
121 | trigger_error( "Template failed integrity check: {$filename}" ); |
||
122 | } |
||
123 | } |
||
124 | // If there is no secret key available, don't use cache |
||
125 | } else { |
||
126 | $code = $this->compileForEval( $fileContents, $filename ); |
||
127 | } |
||
128 | |||
129 | $renderer = eval( $code ); |
||
130 | if ( !is_callable( $renderer ) ) { |
||
131 | throw new RuntimeException( "Requested template, {$templateName}, is not callable" ); |
||
132 | } |
||
133 | $this->renderers[$templateName] = $renderer; |
||
134 | return $renderer; |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * Wrapper for compile() function that verifies successful compilation and strips |
||
139 | * out the '<?php' part so that the code is ready for eval() |
||
140 | * @param string $fileContents Mustache code |
||
141 | * @param string $filename Name of the template |
||
142 | * @return string PHP code (without '<?php') |
||
143 | * @throws RuntimeException |
||
144 | */ |
||
145 | protected function compileForEval( $fileContents, $filename ) { |
||
146 | // Compile the template into PHP code |
||
147 | $code = $this->compile( $fileContents ); |
||
148 | |||
149 | if ( !$code ) { |
||
0 ignored issues
–
show
|
|||
150 | throw new RuntimeException( "Could not compile template: {$filename}" ); |
||
151 | } |
||
152 | |||
153 | // Strip the "<?php" added by lightncandy so that it can be eval()ed |
||
154 | if ( substr( $code, 0, 5 ) === '<?php' ) { |
||
155 | $code = substr( $code, 5 ); |
||
156 | } |
||
157 | |||
158 | return $code; |
||
159 | } |
||
160 | |||
161 | /** |
||
162 | * Compile the Mustache code into PHP code using LightnCandy |
||
163 | * @param string $code Mustache code |
||
164 | * @return string PHP code (with '<?php') |
||
165 | * @throws RuntimeException |
||
166 | */ |
||
167 | protected function compile( $code ) { |
||
168 | if ( !class_exists( 'LightnCandy' ) ) { |
||
169 | throw new RuntimeException( 'LightnCandy class not defined' ); |
||
170 | } |
||
171 | return LightnCandy::compile( |
||
172 | $code, |
||
173 | [ |
||
174 | // Do not add more flags here without discussion. |
||
175 | // If you do add more flags, be sure to update unit tests as well. |
||
176 | 'flags' => LightnCandy::FLAG_ERROR_EXCEPTION, |
||
177 | 'basedir' => $this->templateDir, |
||
178 | 'fileext' => '.mustache', |
||
179 | ] |
||
180 | ); |
||
181 | } |
||
182 | |||
183 | /** |
||
184 | * Returns HTML for a given template by calling the template function with the given args |
||
185 | * |
||
186 | * @code |
||
187 | * echo $templateParser->processTemplate( |
||
188 | * 'ExampleTemplate', |
||
189 | * [ |
||
190 | * 'username' => $user->getName(), |
||
191 | * 'message' => 'Hello!' |
||
192 | * ] |
||
193 | * ); |
||
194 | * @endcode |
||
195 | * @param string $templateName The name of the template |
||
196 | * @param mixed $args |
||
197 | * @param array $scopes |
||
198 | * @return string |
||
199 | */ |
||
200 | public function processTemplate( $templateName, $args, array $scopes = [] ) { |
||
201 | $template = $this->getTemplate( $templateName ); |
||
202 | return call_user_func( $template, $args, $scopes ); |
||
203 | } |
||
204 | } |
||
205 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: