Index: trunk/phase3/tests/phpunit/includes/IPTest.php |
— | — | @@ -482,4 +482,27 @@ |
483 | 483 | ); |
484 | 484 | } |
485 | 485 | |
| 486 | + /** |
| 487 | + * Test for IP::sanitizeRange() |
| 488 | + * @dataProvider provideIPCIDRs |
| 489 | + */ |
| 490 | + function testSanitizeRange( $input, $expected, $description ) { |
| 491 | + $this->assertEquals( $expected, IP::sanitizeRange( $input ), $description ); |
| 492 | + } |
| 493 | + |
| 494 | + /** |
| 495 | + * Provider for IP::testSanitizeRange() |
| 496 | + */ |
| 497 | + function provideIPCIDRs() { |
| 498 | + return array( |
| 499 | + array( '35.56.31.252/16', '35.56.0.0/16', 'IPv4 range' ), |
| 500 | + array( '135.16.21.252/24', '135.16.21.0/24', 'IPv4 range' ), |
| 501 | + array( '5.36.71.252/32', '5.36.71.252/32', 'IPv4 silly range' ), |
| 502 | + array( '5.36.71.252', '5.36.71.252', 'IPv4 non-range' ), |
| 503 | + array( '0:1:2:3:4:c5:f6:7/96', '0:1:2:3:4:C5:0:0/96', 'IPv6 range' ), |
| 504 | + array( '0:1:2:3:4:5:6:7/120', '0:1:2:3:4:5:6:0/120', 'IPv6 range' ), |
| 505 | + array( '0:e1:2:3:4:5:e6:7/128', '0:E1:2:3:4:5:E6:7/128', 'IPv6 silly range' ), |
| 506 | + array( '0:1:A2:3:4:5:c6:7', '0:1:A2:3:4:5:c6:7', 'IPv6 non range' ), |
| 507 | + ); |
| 508 | + } |
486 | 509 | } |
Index: trunk/phase3/includes/resourceloader/ResourceLoader.php |
— | — | @@ -353,8 +353,18 @@ |
354 | 354 | * @param $context ResourceLoaderContext: Context in which a response should be formed |
355 | 355 | */ |
356 | 356 | public function respond( ResourceLoaderContext $context ) { |
357 | | - global $wgCacheEpoch; |
| 357 | + global $wgCacheEpoch, $wgUseFileCache; |
358 | 358 | |
| 359 | + // Use file cache if enabled and available... |
| 360 | + if ( $wgUseFileCache ) { |
| 361 | + $type = 'resources-' . ( $context->getOnly() === 'styles' ? 'css' : 'js' ); |
| 362 | + $hash = sha1( $context->getHash() . implode( ',', $context->getModules() ) ); |
| 363 | + $fileCache = ObjectFileCache::newFromKey( $hash, $type ); |
| 364 | + if ( $this->tryRespondFromFileCache( $fileCache, $context ) ) { |
| 365 | + return; // output handled |
| 366 | + } |
| 367 | + } |
| 368 | + |
359 | 369 | // Buffer output to catch warnings. Normally we'd use ob_clean() on the |
360 | 370 | // top-level output buffer to clear warnings, but that breaks when ob_gzhandler |
361 | 371 | // is used: ob_clean() will clear the GZIP header in that case and it won't come |
— | — | @@ -432,6 +442,15 @@ |
433 | 443 | ob_end_clean(); |
434 | 444 | echo $response; |
435 | 445 | |
| 446 | + // Save response to file cache if enabled |
| 447 | + if ( isset( $fileCache ) && !$private && !$exceptions && !$missing ) { |
| 448 | + $request = $context->getRequest(); |
| 449 | + // Don't cache URLs that the user was not given by site |
| 450 | + if ( $request->getVal( 'fckey' ) == self::fileCacheKey( $request->getRequestURL() ) ) { |
| 451 | + $fileCache->saveText( $response ); |
| 452 | + } |
| 453 | + } |
| 454 | + |
436 | 455 | wfProfileOut( __METHOD__ ); |
437 | 456 | } |
438 | 457 | |
— | — | @@ -520,6 +539,50 @@ |
521 | 540 | } |
522 | 541 | |
523 | 542 | /** |
| 543 | + * Send out code for a response from file cache if possible |
| 544 | + * |
| 545 | + * @param $fileCache ObjectFileCache: Cache object for this request URL |
| 546 | + * @param $context ResourceLoaderContext: Context in which to generate a response |
| 547 | + * @return bool If this found a cache file and handled the response |
| 548 | + */ |
| 549 | + protected function tryRespondFromFileCache( |
| 550 | + ObjectFileCache $fileCache, ResourceLoaderContext $context |
| 551 | + ) { |
| 552 | + global $wgResourceLoaderMaxage; |
| 553 | + // Buffer output to catch warnings. |
| 554 | + ob_start(); |
| 555 | + // Get the maximum age the cache can be |
| 556 | + $maxage = is_null( $context->getVersion() ) |
| 557 | + ? $wgResourceLoaderMaxage['unversioned']['server'] |
| 558 | + : $wgResourceLoaderMaxage['versioned']['server']; |
| 559 | + // Minimum timestamp the cache file must have |
| 560 | + $good = $fileCache->isCacheGood( wfTimestamp( TS_MW, time() - $maxage ) ); |
| 561 | + if ( !$good ) { |
| 562 | + try { // RL always hits the DB on file cache miss... |
| 563 | + wfGetDB( DB_SLAVE ); |
| 564 | + } catch( DBConnectionError $e ) { // ...check if we need to fallback to cache |
| 565 | + $good = $fileCache->isCacheGood(); // cache existence check |
| 566 | + } |
| 567 | + } |
| 568 | + if ( $good ) { |
| 569 | + $ts = $fileCache->cacheTimestamp(); |
| 570 | + // Send content type and cache headers |
| 571 | + $this->sendResponseHeaders( $context, $ts, false ); |
| 572 | + // If there's an If-Modified-Since header, respond with a 304 appropriately |
| 573 | + if ( $this->tryRespondLastModified( $context, $ts ) ) { |
| 574 | + return; // output handled (buffers cleared) |
| 575 | + } |
| 576 | + $response = $fileCache->fetchText(); |
| 577 | + // Remove the output buffer and output the response |
| 578 | + ob_end_clean(); |
| 579 | + echo $response . "\n/* Cached {$ts} */"; |
| 580 | + return true; // cache hit |
| 581 | + } |
| 582 | + ob_end_clean(); |
| 583 | + return false; // cache miss |
| 584 | + } |
| 585 | + |
| 586 | + /** |
524 | 587 | * Generates code for a response |
525 | 588 | * |
526 | 589 | * @param $context ResourceLoaderContext: Context in which to generate a response |
— | — | @@ -912,17 +975,32 @@ |
913 | 976 | */ |
914 | 977 | public static function makeLoaderURL( $modules, $lang, $skin, $user = null, $version = null, $debug = false, $only = null, |
915 | 978 | $printable = false, $handheld = false, $extraQuery = array() ) { |
916 | | - global $wgLoadScript; |
| 979 | + global $wgLoadScript, $wgUseFileCache; |
917 | 980 | $query = self::makeLoaderQuery( $modules, $lang, $skin, $user, $version, $debug, |
918 | 981 | $only, $printable, $handheld, $extraQuery |
919 | 982 | ); |
920 | | - |
| 983 | + |
| 984 | + $url = wfAppendQuery( $wgLoadScript, $query ); |
| 985 | + // Avoid deliberate FS pollution with hand-made URLs |
| 986 | + if ( $wgUseFileCache ) { |
| 987 | + $url .= '&fckey=' . self::fileCacheKey( $url . '&*' ); |
| 988 | + } |
| 989 | + |
921 | 990 | // Prevent the IE6 extension check from being triggered (bug 28840) |
922 | | - // by appending a character that's invalid in Windows extensions ('*') |
923 | | - return wfExpandUrl( wfAppendQuery( $wgLoadScript, $query ) . '&*', PROTO_RELATIVE ); |
| 991 | + // by appending a character that's invalid in Windows extensions ('*') |
| 992 | + return $url . '&*'; |
924 | 993 | } |
925 | 994 | |
926 | 995 | /** |
| 996 | + * Get a filecache key for a load.php URL |
| 997 | + * @return string |
| 998 | + */ |
| 999 | + protected static function fileCacheKey( $url ) { |
| 1000 | + global $wgSecretKey; |
| 1001 | + return sha1( preg_replace( '/&fckey=[^&]*/', '', $url ) . $wgSecretKey . wfWikiID() ); |
| 1002 | + } |
| 1003 | + |
| 1004 | + /** |
927 | 1005 | * Build a query array (array representation of query string) for load.php. Helper |
928 | 1006 | * function for makeLoaderURL(). |
929 | 1007 | * @return array |
Index: trunk/phase3/includes/IP.php |
— | — | @@ -705,8 +705,11 @@ |
706 | 706 | * @param $range String: IP address to normalize |
707 | 707 | * @return string |
708 | 708 | */ |
709 | | - public static function sanitizeRange( $range ){ |
| 709 | + public static function sanitizeRange( $range ) { |
710 | 710 | list( /*...*/, $bits ) = self::parseCIDR( $range ); |
| 711 | + if ( $bits === false ) { |
| 712 | + return $range; // wasn't actually a range |
| 713 | + } |
711 | 714 | list( $start, /*...*/ ) = self::parseRange( $range ); |
712 | 715 | $start = self::formatHex( $start ); |
713 | 716 | return "$start/$bits"; |