Index: trunk/phase3/includes/resourceloader/ResourceLoaderFileModule.php |
— | — | @@ -495,6 +495,7 @@ |
496 | 496 | if ( $contents === false ) { |
497 | 497 | throw new MWException( __METHOD__.": script file not found: \"$localPath\"" ); |
498 | 498 | } |
| 499 | + $contents = $this->validateScriptFile( $fileName, $contents ); |
499 | 500 | $js .= $contents . "\n"; |
500 | 501 | } |
501 | 502 | return $js; |
Index: trunk/phase3/includes/resourceloader/ResourceLoaderModule.php |
— | — | @@ -304,4 +304,54 @@ |
305 | 305 | public function isKnownEmpty( ResourceLoaderContext $context ) { |
306 | 306 | return false; |
307 | 307 | } |
| 308 | + |
| 309 | + |
| 310 | + /** @var JSParser lazy-initialized; use self::javaScriptParser() */ |
| 311 | + private static $jsParser; |
| 312 | + private static $parseCacheVersion = 1; |
| 313 | + |
| 314 | + /** |
| 315 | + * Validate a given script file; if valid returns the original source. |
| 316 | + * If invalid, returns replacement JS source that throws an exception. |
| 317 | + * |
| 318 | + * @param string $fileName |
| 319 | + * @param string $contents |
| 320 | + * @return string JS with the original, or a replacement error |
| 321 | + */ |
| 322 | + protected function validateScriptFile( $fileName, $contents ) { |
| 323 | + global $wgResourceLoaderValidateJS; |
| 324 | + if ( $wgResourceLoaderValidateJS ) { |
| 325 | + // Try for cache hit |
| 326 | + // Use CACHE_ANYTHING since filtering is very slow compared to DB queries |
| 327 | + $key = wfMemcKey( 'resourceloader', 'jsparse', self::$parseCacheVersion, md5( $contents ) ); |
| 328 | + $cache = wfGetCache( CACHE_ANYTHING ); |
| 329 | + $cacheEntry = $cache->get( $key ); |
| 330 | + if ( is_string( $cacheEntry ) ) { |
| 331 | + return $cacheEntry; |
| 332 | + } |
| 333 | + |
| 334 | + $parser = self::javaScriptParser(); |
| 335 | + try { |
| 336 | + $parser->parse( $contents, $fileName, 1 ); |
| 337 | + $result = $contents; |
| 338 | + } catch (Exception $e) { |
| 339 | + // We'll save this to cache to avoid having to validate broken JS over and over... |
| 340 | + $err = $e->getMessage(); |
| 341 | + $result = "throw new Error(" . Xml::encodeJsVar("JavaScript parse error: $err") . ");"; |
| 342 | + } |
| 343 | + |
| 344 | + $cache->set( $key, $result ); |
| 345 | + return $result; |
| 346 | + } else { |
| 347 | + return $contents; |
| 348 | + } |
| 349 | + } |
| 350 | + |
| 351 | + protected static function javaScriptParser() { |
| 352 | + if ( !self::$jsParser ) { |
| 353 | + self::$jsParser = new JSParser(); |
| 354 | + } |
| 355 | + return self::$jsParser; |
| 356 | + } |
| 357 | + |
308 | 358 | } |
Index: trunk/phase3/includes/resourceloader/ResourceLoaderWikiModule.php |
— | — | @@ -82,6 +82,7 @@ |
83 | 83 | } |
84 | 84 | $script = $this->getContent( $title ); |
85 | 85 | if ( strval( $script ) !== '' ) { |
| 86 | + $script = $this->validateScriptFile( $titleText, $script ); |
86 | 87 | if ( strpos( $titleText, '*/' ) === false ) { |
87 | 88 | $scripts .= "/* $titleText */\n"; |
88 | 89 | } |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -2519,6 +2519,12 @@ |
2520 | 2520 | */ |
2521 | 2521 | $wgResourceLoaderMaxQueryLength = -1; |
2522 | 2522 | |
| 2523 | +/** |
| 2524 | + * If set to true, JavaScript will be parsed prior to minification to validate it. |
| 2525 | + * Parse errors will result in a JS exception being thrown during module load. |
| 2526 | + */ |
| 2527 | +$wgResourceLoaderValidateJS = true; |
| 2528 | + |
2523 | 2529 | /** @} */ # End of resource loader settings } |
2524 | 2530 | |
2525 | 2531 | |