wikimedia /
mediawiki
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!=, orswitchconditions), values of different types might be equal.For
stringvalues, the empty string''is a special case, in particular the following results might be unexpected: