Index: trunk/phase3/maintenance/tests/HttpTest.php |
— | — | @@ -529,7 +529,7 @@ |
530 | 530 | } |
531 | 531 | |
532 | 532 | function runCookieRequests() { |
533 | | - $r = HttpRequest::factory( "http://www.php.net/manual" ); |
| 533 | + $r = HttpRequest::factory( "http://www.php.net/manual", array( 'followRedirects' => true ) ); |
534 | 534 | $r->execute(); |
535 | 535 | |
536 | 536 | $jar = $r->getCookieJar(); |
— | — | @@ -564,4 +564,4 @@ |
565 | 565 | Http::$httpEngine = 'curl'; |
566 | 566 | self::runCookieRequests(); |
567 | 567 | } |
568 | | -} |
\ No newline at end of file |
| 568 | +} |
Index: trunk/phase3/includes/HttpFunctions.php |
— | — | @@ -16,16 +16,18 @@ |
17 | 17 | * @param $url string Full URL to act on |
18 | 18 | * @param $options options to pass to HttpRequest object |
19 | 19 | * Possible keys for the array: |
20 | | - * timeout Timeout length in seconds |
21 | | - * postData An array of key-value pairs or a url-encoded form data |
22 | | - * proxy The proxy to use. |
23 | | - * Will use $wgHTTPProxy (if set) otherwise. |
24 | | - * noProxy Override $wgHTTPProxy (if set) and don't use any proxy at all. |
25 | | - * sslVerifyHost (curl only) Verify hostname against certificate |
26 | | - * sslVerifyCert (curl only) Verify SSL certificate |
27 | | - * caInfo (curl only) Provide CA information |
28 | | - * maxRedirects Maximum number of redirects to follow (defaults to 5) |
29 | | - * followRedirects Whether to follow redirects (defaults to true) |
| 20 | + * timeout Timeout length in seconds |
| 21 | + * postData An array of key-value pairs or a url-encoded form data |
| 22 | + * proxy The proxy to use. |
| 23 | + * Will use $wgHTTPProxy (if set) otherwise. |
| 24 | + * noProxy Override $wgHTTPProxy (if set) and don't use any proxy at all. |
| 25 | + * sslVerifyHost (curl only) Verify hostname against certificate |
| 26 | + * sslVerifyCert (curl only) Verify SSL certificate |
| 27 | + * caInfo (curl only) Provide CA information |
| 28 | + * maxRedirects Maximum number of redirects to follow (defaults to 5) |
| 29 | + * followRedirects Whether to follow redirects (defaults to false). |
| 30 | + * Note: this should only be used when the target URL is trusted, |
| 31 | + * to avoid attacks on intranet services accessible by HTTP. |
30 | 32 | * @returns mixed (bool)false on failure or a string on success |
31 | 33 | */ |
32 | 34 | public static function request( $method, $url, $options = array() ) { |
— | — | @@ -138,7 +140,7 @@ |
139 | 141 | protected $parsedUrl; |
140 | 142 | protected $callback; |
141 | 143 | protected $maxRedirects = 5; |
142 | | - protected $followRedirects = true; |
| 144 | + protected $followRedirects = false; |
143 | 145 | |
144 | 146 | protected $cookieJar; |
145 | 147 | |
— | — | @@ -386,7 +388,7 @@ |
387 | 389 | } |
388 | 390 | |
389 | 391 | $status = (int)$this->respStatus; |
390 | | - if ( $status >= 300 && $status < 400 ) { |
| 392 | + if ( $status >= 300 && $status <= 303 ) { |
391 | 393 | return true; |
392 | 394 | } |
393 | 395 | return false; |
— | — | @@ -481,6 +483,14 @@ |
482 | 484 | |
483 | 485 | return $this->url; |
484 | 486 | } |
| 487 | + |
| 488 | + /** |
| 489 | + * Returns true if the backend can follow redirects. Overridden by the |
| 490 | + * child classes. |
| 491 | + */ |
| 492 | + public function canFollowRedirects() { |
| 493 | + return true; |
| 494 | + } |
485 | 495 | } |
486 | 496 | |
487 | 497 | |
— | — | @@ -793,11 +803,21 @@ |
794 | 804 | $this->setStatus(); |
795 | 805 | return $this->status; |
796 | 806 | } |
| 807 | + |
| 808 | + public function canFollowRedirects() { |
| 809 | + if ( strval( ini_get( 'open_basedir' ) ) !== '' || wfIniGetBool( 'safe_mode' ) ) { |
| 810 | + wfDebug( "Cannot follow redirects in safe mode\n" ); |
| 811 | + return false; |
| 812 | + } |
| 813 | + if ( !defined( 'CURLOPT_REDIR_PROTOCOLS' ) ) { |
| 814 | + wfDebug( "Cannot follow redirects with libcurl < 7.19.4 due to CVE-2009-0037\n" ); |
| 815 | + return false; |
| 816 | + } |
| 817 | + return true; |
| 818 | + } |
797 | 819 | } |
798 | 820 | |
799 | 821 | class PhpHttpRequest extends HttpRequest { |
800 | | - protected $manuallyRedirect = false; |
801 | | - |
802 | 822 | protected function urlToTcp( $url ) { |
803 | 823 | $parsedUrl = parse_url( $url ); |
804 | 824 | |
— | — | @@ -809,9 +829,7 @@ |
810 | 830 | |
811 | 831 | // At least on Centos 4.8 with PHP 5.1.6, using max_redirects to follow redirects |
812 | 832 | // causes a segfault |
813 | | - if ( version_compare( '5.1.7', phpversion(), '>' ) ) { |
814 | | - $this->manuallyRedirect = true; |
815 | | - } |
| 833 | + $manuallyRedirect = version_compare( phpversion(), '5.1.7', '<' ); |
816 | 834 | |
817 | 835 | if ( $this->parsedUrl['scheme'] != 'http' ) { |
818 | 836 | $this->status->fatal( 'http-invalid-scheme', $this->parsedUrl['scheme'] ); |
— | — | @@ -830,7 +848,7 @@ |
831 | 849 | $options['request_fulluri'] = true; |
832 | 850 | } |
833 | 851 | |
834 | | - if ( !$this->followRedirects || $this->manuallyRedirect ) { |
| 852 | + if ( !$this->followRedirects || $manuallyRedirect ) { |
835 | 853 | $options['max_redirects'] = 0; |
836 | 854 | } else { |
837 | 855 | $options['max_redirects'] = $this->maxRedirects; |
— | — | @@ -864,21 +882,32 @@ |
865 | 883 | $reqCount = 0; |
866 | 884 | $url = $this->url; |
867 | 885 | do { |
868 | | - $again = false; |
869 | 886 | $reqCount++; |
870 | 887 | wfSuppressWarnings(); |
871 | 888 | $fh = fopen( $url, "r", false, $context ); |
872 | 889 | wfRestoreWarnings(); |
873 | | - if ( $fh ) { |
874 | | - $result = stream_get_meta_data( $fh ); |
875 | | - $this->headerList = $result['wrapper_data']; |
876 | | - $this->parseHeader(); |
877 | | - $url = $this->getResponseHeader("Location"); |
878 | | - $again = $this->manuallyRedirect && $this->followRedirects && $url |
879 | | - && $this->isRedirect() && $this->maxRedirects > $reqCount; |
| 890 | + if ( !$fh ) { |
| 891 | + break; |
880 | 892 | } |
881 | | - } while ( $again ); |
| 893 | + $result = stream_get_meta_data( $fh ); |
| 894 | + $this->headerList = $result['wrapper_data']; |
| 895 | + $this->parseHeader(); |
| 896 | + if ( !$manuallyRedirect || !$this->followRedirects ) { |
| 897 | + break; |
| 898 | + } |
882 | 899 | |
| 900 | + # Handle manual redirection |
| 901 | + if ( !$this->isRedirect() || $reqCount > $this->maxRedirects ) { |
| 902 | + break; |
| 903 | + } |
| 904 | + # Check security of URL |
| 905 | + $url = $this->getResponseHeader("Location"); |
| 906 | + if ( substr( $url, 0, 7 ) !== 'http://' ) { |
| 907 | + wfDebug( __METHOD__.": insecure redirection\n" ); |
| 908 | + break; |
| 909 | + } |
| 910 | + } while ( true ); |
| 911 | + |
883 | 912 | if ( $oldTimeout !== false ) { |
884 | 913 | ini_set('default_socket_timeout', $oldTimeout); |
885 | 914 | } |