r61911 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r61910‎ | r61911 | r61912 >
Date:06:19, 3 February 2010
Author:mah
Status:ok (Comments)
Tags:
Comment:
follow-up r61655 fill out the rest of the missing messages
Add cookie handling to HttpFunctions.
Modified paths:
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/HttpFunctions.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesEn.php (modified) (history)
  • /trunk/phase3/maintenance/language/messages.inc (modified) (history)
  • /trunk/phase3/tests/HttpTest.php (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/language/messages.inc
@@ -842,7 +842,7 @@
843843 'powersearch-redir',
844844 'powersearch-field',
845845 'powersearch-togglelabel',
846 - 'powersearch-toggleall',
 846+ 'powersearch-toggleall',
847847 'powersearch-togglenone',
848848 'search-external',
849849 'searchdisabled',
@@ -1275,6 +1275,10 @@
12761276 'http-invalid-url',
12771277 'http-invalid-scheme',
12781278 'http-request-error',
 1279+ 'http-read-error',
 1280+ 'http-timed-out',
 1281+ 'http-curl-error',
 1282+ 'http-host-unreachable',
12791283 ),
12801284
12811285 'upload-curl-errors' => array(
Index: trunk/phase3/tests/HttpTest.php
@@ -1,7 +1,9 @@
22 <?php
33
4 -if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
5 - require_once( 'bootstrap.php' );
 4+class MockCookie extends Cookie {
 5+ public function canServeDomain($arg) { return parent::canServeDomain($arg); }
 6+ public function canServePath($arg) { return parent::canServePath($arg); }
 7+ public function isUnExpired() { return parent::isUnExpired(); }
68 }
79
810 class HttpTest extends PhpUnit_Framework_TestCase {
@@ -131,6 +133,8 @@
132134
133135 if($proxy) {
134136 $opt['proxy'] = $proxy;
 137+ } elseif( $proxy === false ) {
 138+ $opt['noProxy'] = true;
135139 }
136140
137141 /* no postData here because the only request I could find in code so far didn't have any */
@@ -208,6 +212,8 @@
209213
210214 if($proxy) {
211215 $opt['proxy'] = $proxy;
 216+ } elseif( $proxy === false ) {
 217+ $opt['noProxy'] = true;
212218 }
213219
214220 foreach ( $this->test_geturl as $u ) {
@@ -241,6 +247,8 @@
242248
243249 if($proxy) {
244250 $opt['proxy'] = $proxy;
 251+ } elseif( $proxy === false ) {
 252+ $opt['noProxy'] = true;
245253 }
246254
247255 foreach ( $this->test_posturl as $u => $postData ) {
@@ -277,6 +285,11 @@
278286 self::runHTTPGets(self::$proxy);
279287 self::runHTTPPosts(self::$proxy);
280288 self::runHTTPRequests(self::$proxy);
 289+
 290+ // Set false here to do noProxy
 291+ self::runHTTPGets(false);
 292+ self::runHTTPPosts(false);
 293+ self::runHTTPRequests(false);
281294 }
282295
283296 function testProxyDefault() {
@@ -308,4 +321,150 @@
309322 function testIsValidUrl() {
310323 }
311324
 325+ function testSetCookie() {
 326+ $c = new MockCookie( "name", "value",
 327+ array(
 328+ "domain" => ".example.com",
 329+ "path" => "/path/",
 330+ ) );
 331+
 332+ $this->assertFalse($c->canServeDomain("example.com"));
 333+ $this->assertFalse($c->canServeDomain("www.example.net"));
 334+ $this->assertTrue($c->canServeDomain("www.example.com"));
 335+
 336+ $this->assertFalse($c->canServePath("/"));
 337+ $this->assertFalse($c->canServePath("/bogus/path/"));
 338+ $this->assertFalse($c->canServePath("/path"));
 339+ $this->assertTrue($c->canServePath("/path/"));
 340+
 341+ $this->assertTrue($c->isUnExpired());
 342+
 343+ $this->assertEquals("", $c->serializeToHttpRequest("/path/", "www.example.net"));
 344+ $this->assertEquals("", $c->serializeToHttpRequest("/", "www.example.com"));
 345+ $this->assertEquals("name=value", $c->serializeToHttpRequest("/path/", "www.example.com"));
 346+
 347+ $c = new MockCookie( "name", "value",
 348+ array(
 349+ "domain" => ".example.com",
 350+ "path" => "/path/",
 351+ "expires" => "January 1, 1990",
 352+ ) );
 353+ $this->assertFalse($c->isUnExpired());
 354+ $this->assertEquals("", $c->serializeToHttpRequest("/path/", "www.example.com"));
 355+
 356+ $c = new MockCookie( "name", "value",
 357+ array(
 358+ "domain" => ".example.com",
 359+ "path" => "/path/",
 360+ "expires" => "January 1, 2999",
 361+ ) );
 362+ $this->assertTrue($c->isUnExpired());
 363+ $this->assertEquals("name=value", $c->serializeToHttpRequest("/path/", "www.example.com"));
 364+
 365+
 366+ }
 367+
 368+ function testCookieJarSetCookie() {
 369+ $cj = new CookieJar;
 370+ $cj->setCookie( "name", "value",
 371+ array(
 372+ "domain" => ".example.com",
 373+ "path" => "/path/",
 374+ ) );
 375+ $cj->setCookie( "name2", "value",
 376+ array(
 377+ "domain" => ".example.com",
 378+ "path" => "/path/sub",
 379+ ) );
 380+ $cj->setCookie( "name3", "value",
 381+ array(
 382+ "domain" => ".example.com",
 383+ "path" => "/",
 384+ ) );
 385+ $cj->setCookie( "name4", "value",
 386+ array(
 387+ "domain" => ".example.net",
 388+ "path" => "/path/",
 389+ ) );
 390+ $cj->setCookie( "name5", "value",
 391+ array(
 392+ "domain" => ".example.net",
 393+ "path" => "/path/",
 394+ "expires" => "January 1, 1999",
 395+ ) );
 396+
 397+ $this->assertEquals("name4=value", $cj->serializeToHttpRequest("/path/", "www.example.net"));
 398+ $this->assertEquals("name3=value", $cj->serializeToHttpRequest("/", "www.example.com"));
 399+ $this->assertEquals("name=value; name3=value", $cj->serializeToHttpRequest("/path/", "www.example.com"));
 400+
 401+ $cj->setCookie( "name5", "value",
 402+ array(
 403+ "domain" => ".example.net",
 404+ "path" => "/path/",
 405+ "expires" => "January 1, 2999",
 406+ ) );
 407+ $this->assertEquals("name4=value; name5=value", $cj->serializeToHttpRequest("/path/", "www.example.net"));
 408+
 409+ $cj->setCookie( "name4", "value",
 410+ array(
 411+ "domain" => ".example.net",
 412+ "path" => "/path/",
 413+ "expires" => "January 1, 1999",
 414+ ) );
 415+ $this->assertEquals("name5=value", $cj->serializeToHttpRequest("/path/", "www.example.net"));
 416+ }
 417+
 418+ function testParseResponseHeader() {
 419+ $cj = new CookieJar;
 420+
 421+ $h[] = "Set-Cookie: name4=value; domain=.example.com; path=/; expires=Mon, 09-Dec-2999 13:46:00 GMT";
 422+ $cj->parseCookieResponseHeader( $h[0] );
 423+ $this->assertEquals("name4=value", $cj->serializeToHttpRequest("/", "www.example.com"));
 424+
 425+ $h[] = "name4=value2; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2999 13:46:00 GMT";
 426+ $cj->parseCookieResponseHeader( $h[1] );
 427+ $this->assertEquals("", $cj->serializeToHttpRequest("/", "www.example.com"));
 428+ $this->assertEquals("name4=value2", $cj->serializeToHttpRequest("/path/", "www.example.com"));
 429+
 430+ $h[] = "name5=value3; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2999 13:46:00 GMT";
 431+ $cj->parseCookieResponseHeader( $h[2] );
 432+ $this->assertEquals("name4=value2; name5=value3", $cj->serializeToHttpRequest("/path/", "www.example.com"));
 433+
 434+ $h[] = "name6=value3; domain=.example.net; path=/path/; expires=Mon, 09-Dec-1999 13:46:00 GMT";
 435+ $cj->parseCookieResponseHeader( $h[3] );
 436+ $this->assertEquals("", $cj->serializeToHttpRequest("/path/", "www.example.net"));
 437+
 438+ $h[] = "name6=value4; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2999 13:46:00 GMT";
 439+ $cj->parseCookieResponseHeader( $h[4] );
 440+ $this->assertEquals("name6=value4", $cj->serializeToHttpRequest("/path/", "www.example.net"));
 441+ }
 442+
 443+ function runCookieRequests() {
 444+ $r = HttpRequest::factory( "http://www.php.net/manual" );
 445+ $r->execute();
 446+
 447+ $jar = $r->getCookieJar();
 448+
 449+ $this->assertThat( $jar, $this->isInstanceOf( 'CookieJar' ) );
 450+ $this->assertRegExp( '/^COUNTRY=.*; LAST_LANG=.*$/', $jar->serializeToHttpRequest( "/search?q=test", "www.php.net" ) );
 451+ $this->assertEquals( '', $jar->serializeToHttpRequest( "/search?q=test", "www.php.com" ) );
 452+ }
 453+
 454+ function testCookieRequestDefault() {
 455+ Http::$httpEngine = false;
 456+ self::runCookieRequests();
 457+ }
 458+ function testCookieRequestPhp() {
 459+ Http::$httpEngine = 'php';
 460+ self::runCookieRequests();
 461+ }
 462+ function testCookieRequestCurl() {
 463+ if (!self::$has_curl ) {
 464+ $this->markTestIncomplete("This test requires curl.");
 465+ }
 466+
 467+ Http::$httpEngine = 'curl';
 468+ self::runCookieRequests();
 469+ }
 470+
312471 }
\ No newline at end of file
Index: trunk/phase3/includes/AutoLoader.php
@@ -36,6 +36,8 @@
3737 'ChangesFeed' => 'includes/ChangesFeed.php',
3838 'ChangeTags' => 'includes/ChangeTags.php',
3939 'ChannelFeed' => 'includes/Feed.php',
 40+ 'Cookie' => 'includes/HttpFunctions.php',
 41+ 'CookieJar' => 'includes/HttpFunctions.php',
4042 'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php',
4143 'ConfEditor' => 'includes/ConfEditor.php',
4244 'ConfEditorParseError' => 'includes/ConfEditor.php',
Index: trunk/phase3/includes/HttpFunctions.php
@@ -15,6 +15,15 @@
1616 * @param $method string HTTP method. Usually GET/POST
1717 * @param $url string Full URL to act on
1818 * @param $options options to pass to HttpRequest object
 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. Will use $wgHTTPProxy (if set) otherwise.
 23+ * noProxy Override $wgHTTPProxy (if set) and don't use any proxy at all.
 24+ * sslVerifyHost (curl only) Verify the SSL certificate
 25+ * caInfo (curl only) Provide CA information
 26+ * maxRedirects Maximum number of redirects to follow (defaults to 5)
 27+ * followRedirects Whether to follow redirects (defaults to true)
1928 * @returns mixed (bool)false on failure or a string on success
2029 */
2130 public static function request( $method, $url, $options = array() ) {
@@ -124,21 +133,21 @@
125134 protected $url;
126135 protected $parsedUrl;
127136 protected $callback;
 137+ protected $maxRedirects = 5;
 138+ protected $followRedirects = true;
 139+
 140+ protected $cookieJar;
 141+
 142+ protected $headerList = array();
 143+ protected $respVersion = "0.9";
 144+ protected $respStatus = "0.1";
 145+ protected $respHeaders = array();
 146+
128147 public $status;
129148
130149 /**
131150 * @param $url string url to use
132 - * @param $options array (optional) extra params to pass
133 - * Possible keys for the array:
134 - * method
135 - * timeout
136 - * targetFilePath
137 - * requestKey
138 - * postData
139 - * proxy
140 - * noProxy
141 - * sslVerifyHost
142 - * caInfo
 151+ * @param $options array (optional) extra params to pass (see Http::request())
143152 */
144153 function __construct( $url, $options = array() ) {
145154 global $wgHTTPTimeout;
@@ -158,8 +167,8 @@
159168 $this->timeout = $wgHTTPTimeout;
160169 }
161170
162 - $members = array( "targetFilePath", "requestKey", "postData",
163 - "proxy", "noProxy", "sslVerifyHost", "caInfo", "method" );
 171+ $members = array( "postData", "proxy", "noProxy", "sslVerifyHost", "caInfo",
 172+ "method", "followRedirects", "maxRedirects" );
164173 foreach ( $members as $o ) {
165174 if ( isset($options[$o]) ) {
166175 $this->$o = $options[$o];
@@ -175,7 +184,8 @@
176185 if ( !Http::$httpEngine ) {
177186 Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
178187 } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
179 - throw new MWException( __METHOD__.': curl (http://php.net/curl) is not installed, but Http::$httpEngine is set to "curl"' );
 188+ throw new MWException( __METHOD__.': curl (http://php.net/curl) is not installed, but'.
 189+ ' Http::$httpEngine is set to "curl"' );
180190 }
181191
182192 switch( Http::$httpEngine ) {
@@ -183,8 +193,8 @@
184194 return new CurlHttpRequest( $url, $options );
185195 case 'php':
186196 if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
187 - throw new MWException( __METHOD__.': allow_url_fopen needs to be enabled for pure PHP http requests to work. '.
188 - 'If possible, curl should be used instead. See http://php.net/curl.' );
 197+ throw new MWException( __METHOD__.': allow_url_fopen needs to be enabled for pure PHP'.
 198+ ' http requests to work. If possible, curl should be used instead. See http://php.net/curl.' );
189199 }
190200 return new PhpHttpRequest( $url, $options );
191201 default:
@@ -208,7 +218,6 @@
209219 public function proxySetup() {
210220 global $wgHTTPProxy;
211221
212 -
213222 if ( $this->proxy ) {
214223 return;
215224 }
@@ -247,6 +256,9 @@
248257 public function getHeaderList() {
249258 $list = array();
250259
 260+ if( $this->cookieJar ) {
 261+ $this->reqHeaders['Cookie'] = $this->cookieJar->serializeToHttpRequest();
 262+ }
251263 foreach($this->reqHeaders as $name => $value) {
252264 $list[] = "$name: $value";
253265 }
@@ -262,7 +274,8 @@
263275 }
264276
265277 /**
266 - * A generic callback to read in the response from a remote server
 278+ * A generic callback to read the body of the response from a remote
 279+ * server.
267280 * @param $fh handle
268281 * @param $content string
269282 */
@@ -302,14 +315,266 @@
303316 $this->setUserAgent(Http::userAgent());
304317 }
305318 }
 319+
 320+ protected function parseHeader() {
 321+ $lastname = "";
 322+ foreach( $this->headerList as $header ) {
 323+ if( preg_match( "#^HTTP/([0-9.]+) (.*)#", $header, $match ) ) {
 324+ $this->respVersion = $match[1];
 325+ $this->respStatus = $match[2];
 326+ } elseif( preg_match( "#^[ \t]#", $header ) ) {
 327+ $last = count($this->respHeaders[$lastname]) - 1;
 328+ $this->respHeaders[$lastname][$last] .= "\r\n$header";
 329+ } elseif( preg_match( "#^([^:]*):[\t ]*(.*)#", $header, $match ) ) {
 330+ $this->respHeaders[strtolower( $match[1] )][] = $match[2];
 331+ $lastname = strtolower( $match[1] );
 332+ }
 333+ }
 334+
 335+ $this->parseCookies();
 336+ }
 337+
 338+ /**
 339+ * Returns an associative array of response headers after the
 340+ * request has been executed. Because some headers
 341+ * (e.g. Set-Cookie) can appear more than once the, each value of
 342+ * the associative array is an array of the values given.
 343+ * @return array
 344+ */
 345+ public function getResponseHeaders() {
 346+ if( !$this->respHeaders ) {
 347+ $this->parseHeader();
 348+ }
 349+ return $this->respHeaders;
 350+ }
 351+
 352+ /**
 353+ * Tells the HttpRequest object to use this pre-loaded CookieJar.
 354+ * @param $jar CookieJar
 355+ */
 356+ public function setCookieJar( $jar ) {
 357+ $this->cookieJar = $jar;
 358+ }
 359+
 360+ /**
 361+ * Returns the cookie jar in use.
 362+ * @returns CookieJar
 363+ */
 364+ public function getCookieJar() {
 365+ if( !$this->respHeaders ) {
 366+ $this->parseHeader();
 367+ }
 368+ return $this->cookieJar;
 369+ }
 370+
 371+ /**
 372+ * Sets a cookie. Used before a request to set up any individual
 373+ * cookies. Used internally after a request to parse the
 374+ * Set-Cookie headers.
 375+ * @see Cookie::set
 376+ */
 377+ public function setCookie( $name, $value = null, $attr = null) {
 378+ if( !$this->cookieJar ) {
 379+ $this->cookieJar = new CookieJar;
 380+ }
 381+ $this->cookieJar->setCookie($name, $value, $attr);
 382+ }
 383+
 384+ /**
 385+ * Parse the cookies in the response headers and store them in the cookie jar.
 386+ */
 387+ protected function parseCookies() {
 388+ if( isset( $this->respHeaders['set-cookie'] ) ) {
 389+ if( !$this->cookieJar ) {
 390+ $this->cookieJar = new CookieJar;
 391+ }
 392+ $url = parse_url( $this->getFinalUrl() );
 393+ foreach( $this->respHeaders['set-cookie'] as $cookie ) {
 394+ $this->cookieJar->parseCookieResponseHeader( $cookie, $url['host'] );
 395+ }
 396+ }
 397+ }
 398+
 399+ /**
 400+ * Returns the final URL after all redirections.
 401+ * @returns string
 402+ */
 403+ public function getFinalUrl() {
 404+ $finalUrl = $this->url;
 405+ if ( isset( $this->respHeaders['location'] ) ) {
 406+ $redir = $this->respHeaders['location'];
 407+ $finalUrl = $redir[count($redir) - 1];
 408+ }
 409+
 410+ return $finalUrl;
 411+ }
306412 }
307413
 414+
 415+class Cookie {
 416+ protected $name;
 417+ protected $value;
 418+ protected $expires;
 419+ protected $path;
 420+ protected $domain;
 421+ protected $isSessionKey = true;
 422+ // TO IMPLEMENT protected $secure
 423+ // TO IMPLEMENT? protected $maxAge (add onto expires)
 424+ // TO IMPLEMENT? protected $version
 425+ // TO IMPLEMENT? protected $comment
 426+
 427+ function __construct( $name, $value, $attr ) {
 428+ $this->name = $name;
 429+ $this->set( $value, $attr );
 430+ }
 431+
 432+ /**
 433+ * Sets a cookie. Used before a request to set up any individual
 434+ * cookies. Used internally after a request to parse the
 435+ * Set-Cookie headers.
 436+ * @param $name string the name of the cookie
 437+ * @param $value string the value of the cookie
 438+ * @param $attr array possible key/values:
 439+ * expires A date string
 440+ * path The path this cookie is used on
 441+ * domain Domain this cookie is used on
 442+ */
 443+ public function set( $value, $attr ) {
 444+ $this->value = $value;
 445+ if( isset( $attr['expires'] ) ) {
 446+ $this->isSessionKey = false;
 447+ $this->expires = strtotime( $attr['expires'] );
 448+ }
 449+ if( isset( $attr['path'] ) ) {
 450+ $this->path = $attr['path'];
 451+ } else {
 452+ $this->path = "/";
 453+ }
 454+ if( isset( $attr['domain'] ) ) {
 455+ $this->domain = $attr['domain'];
 456+ } else {
 457+ throw new MWException("You must specify a domain.");
 458+ }
 459+ }
 460+
 461+ /**
 462+ * Serialize the cookie jar into a format useful for HTTP Request headers.
 463+ * @param $path string the path that will be used. Required.
 464+ * @param $domain string the domain that will be used. Required.
 465+ * @return string
 466+ */
 467+ public function serializeToHttpRequest( $path, $domain ) {
 468+ $ret = "";
 469+
 470+ if( $this->canServeDomain( $domain )
 471+ && $this->canServePath( $path )
 472+ && $this->isUnExpired() ) {
 473+ $ret = $this->name ."=". $this->value;
 474+ }
 475+
 476+ return $ret;
 477+ }
 478+
 479+ protected function canServeDomain( $domain ) {
 480+ if( $this->domain && substr_compare( $domain, $this->domain, -strlen( $this->domain ),
 481+ strlen( $this->domain ), TRUE ) == 0 ) {
 482+ return true;
 483+ }
 484+ return false;
 485+ }
 486+
 487+ protected function canServePath( $path ) {
 488+ if( $this->path && substr_compare( $this->path, $path, 0, strlen( $this->path ) ) == 0 ) {
 489+ return true;
 490+ }
 491+ return false;
 492+ }
 493+
 494+ protected function isUnExpired() {
 495+ if( $this->isSessionKey || $this->expires > time() ) {
 496+ return true;
 497+ }
 498+ return false;
 499+ }
 500+
 501+}
 502+
 503+class CookieJar {
 504+ private $cookie;
 505+
 506+ /**
 507+ * Set a cookie in the cookie jar. Make sure only one cookie per-name exists.
 508+ * @see Cookie::set()
 509+ */
 510+ public function setCookie ($name, $value, $attr) {
 511+ /* cookies: case insensitive, so this should work.
 512+ * We'll still send the cookies back in the same case we got them, though.
 513+ */
 514+ $index = strtoupper($name);
 515+ if( isset( $this->cookie[$index] ) ) {
 516+ $this->cookie[$index]->set( $value, $attr );
 517+ } else {
 518+ $this->cookie[$index] = new Cookie( $name, $value, $attr );
 519+ }
 520+ }
 521+
 522+ /**
 523+ * @see Cookie::serializeToHttpRequest
 524+ */
 525+ public function serializeToHttpRequest( $path, $domain ) {
 526+ $cookies = array();
 527+
 528+ foreach( $this->cookie as $c ) {
 529+ $serialized = $c->serializeToHttpRequest( $path, $domain );
 530+ if ( $serialized ) $cookies[] = $serialized;
 531+ }
 532+
 533+ return implode("; ", $cookies);
 534+ }
 535+
 536+ /**
 537+ * Parse the content of an Set-Cookie HTTP Response header.
 538+ * @param $cookie string
 539+ */
 540+ public function parseCookieResponseHeader ( $cookie, $domain = null ) {
 541+ $len = strlen( "Set-Cookie:" );
 542+ if ( substr_compare( "Set-Cookie:", $cookie, 0, $len, TRUE ) === 0 ) {
 543+ $cookie = substr( $cookie, $len );
 544+ }
 545+
 546+ $bit = array_map( 'trim', explode( ";", $cookie ) );
 547+ list($name, $value) = explode( "=", array_shift( $bit ), 2 );
 548+ $attr = array();
 549+ foreach( $bit as $piece ) {
 550+ $parts = explode( "=", $piece );
 551+ if( count( $parts ) > 1 ) {
 552+ $attr[strtolower( $parts[0] )] = $parts[1];
 553+ } else {
 554+ $attr[strtolower( $parts[0] )] = true;
 555+ }
 556+ }
 557+ $this->setCookie( $name, $value, $attr );
 558+ }
 559+}
 560+
 561+
308562 /**
309563 * HttpRequest implemented using internal curl compiled into PHP
310564 */
311565 class CurlHttpRequest extends HttpRequest {
 566+ static $curlMessageMap = array(
 567+ 6 => 'http-host-unreachable',
 568+ 28 => 'http-timed-out'
 569+ );
 570+
312571 protected $curlOptions = array();
 572+ protected $headerText = "";
313573
 574+ protected function readHeader( $fh, $content ) {
 575+ $this->headerText .= $content;
 576+ return strlen( $content );
 577+ }
 578+
314579 public function execute() {
315580 parent::execute();
316581 if ( !$this->status->isOK() ) {
@@ -319,6 +584,9 @@
320585 $this->curlOptions[CURLOPT_TIMEOUT] = $this->timeout;
321586 $this->curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0;
322587 $this->curlOptions[CURLOPT_WRITEFUNCTION] = $this->callback;
 588+ $this->curlOptions[CURLOPT_HEADERFUNCTION] = array($this, "readHeader");
 589+ $this->curlOptions[CURLOPT_FOLLOWLOCATION] = $this->followRedirects;
 590+ $this->curlOptions[CURLOPT_MAXREDIRS] = $this->maxRedirects;
323591
324592 /* not sure these two are actually necessary */
325593 if(isset($this->reqHeaders['Referer'])) {
@@ -354,8 +622,15 @@
355623 curl_setopt_array( $curlHandle, $this->curlOptions );
356624
357625 if ( false === curl_exec( $curlHandle ) ) {
358 - // re-using already translated error messages
359 - $this->status->fatal( 'upload-curl-error'.curl_errno( $curlHandle ).'-text' );
 626+ $code = curl_error( $curlHandle );
 627+
 628+ if ( isset( self::$curlMessageMap[$code] ) ) {
 629+ $this->status->fatal( self::$curlMessageMap[$code] );
 630+ } else {
 631+ $this->status->fatal( 'http-curl-error', curl_error( $curlHandle ) );
 632+ }
 633+ } else {
 634+ $this->headerList = explode("\r\n", $this->headerText);
360635 }
361636
362637 curl_close( $curlHandle );
@@ -394,6 +669,12 @@
395670 $options['request_fulluri'] = true;
396671 }
397672
 673+ if ( !$this->followRedirects ) {
 674+ $options['max_redirects'] = 0;
 675+ } else {
 676+ $options['max_redirects'] = $this->maxRedirects;
 677+ }
 678+
398679 $options['method'] = $this->method;
399680 $options['timeout'] = $this->timeout;
400681 $options['header'] = implode("\r\n", $this->getHeaderList());
@@ -428,9 +709,8 @@
429710 $this->status->fatal( 'http-timed-out', $this->url );
430711 return $this->status;
431712 }
 713+ $this->headerList = $result['wrapper_data'];
432714
433 - $this->headers = $result['wrapper_data'];
434 -
435715 while ( !feof( $fh ) ) {
436716 $buf = fread( $fh, 8192 );
437717 if ( $buf === false ) {
Index: trunk/phase3/languages/messages/MessagesEn.php
@@ -2059,7 +2059,7 @@
20602060 [[$1|thumb]]",
20612061 'fileexists-extension' => "A file with a similar name exists: [[$2|thumb]]
20622062 * Name of the uploading file: '''<tt>[[:$1]]</tt>'''
2063 -* Name of the existing file: '''<tt>[[:$2]]</tt>'''
 2063+* Name of the existing file: '''<tt>[[:$2]]</tt>'''
20642064 Please choose a different name.",
20652065 'fileexists-thumbnail-yes' => "The file seems to be an image of reduced size ''(thumbnail)''.
20662066 [[$1|thumb]]
@@ -2153,9 +2153,13 @@
21542154 'img-auth-noread' => 'User does not have access to read "$1".',
21552155
21562156 # HTTP errors
2157 -'http-invalid-url' => 'Invalid URL: $1',
 2157+'http-invalid-url' => 'Invalid URL: $1',
21582158 'http-invalid-scheme' => 'URLs with the "$1" scheme are not supported',
2159 -'http-request-error' => 'HTTP request failed due to unknown error.',
 2159+'http-request-error' => 'HTTP request failed due to unknown error.',
 2160+'http-read-error' => 'HTTP read error.',
 2161+'http-timed-out' => 'HTTP request timed out.',
 2162+'http-curl-error' => 'Error fetching URL: $1',
 2163+'http-host-unreachable' => 'Could not reach URL',
21602164
21612165 # Some likely curl errors. More could be added from <http://curl.haxx.se/libcurl/c/libcurl-errors.html>
21622166 'upload-curl-error6' => 'Could not reach URL',
@@ -2417,7 +2421,7 @@
24182422 'ancientpages-summary' => '', # do not translate or duplicate this message to other languages
24192423 'move' => 'Move',
24202424 'movethispage' => 'Move this page',
2421 -'unusedimagestext' => 'The following files exist but are not embedded in any page.
 2425+'unusedimagestext' => 'The following files exist but are not embedded in any page.
24222426 Please note that other web sites may link to a file with a direct URL, and so may still be listed here despite being in active use.',
24232427 'unusedcategoriestext' => 'The following category pages exist, although no other page or category makes use of them.',
24242428 'notargettitle' => 'No target',

Follow-up revisions

RevisionCommit summaryAuthorDate
r61915Follow-up r61911: rebuild MessagesEn.phpsiebrand07:38, 3 February 2010
r62009follow up r61911 - put periods in where appropriatemah04:33, 5 February 2010
r67684Fixes for r61911:...tstarling06:39, 9 June 2010

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r61655follow up r61357...mah07:25, 29 January 2010

Comments

#Comment by Siebrand (talk | contribs)   19:13, 3 February 2010

3 of 5 new messages end with a period. Shouldn't the other two also end with a period?

#Comment by Siebrand (talk | contribs)   19:13, 3 February 2010

Make that 2 of 4.

#Comment by Tim Starling (talk | contribs)   02:54, 9 February 2010

What is this cookie handling for?

#Comment by MarkAHershberger (talk | contribs)   03:00, 9 February 2010

I need it to test the API. I could have put all this in the tests themselves, but I put it here thinking it might be useful otherwise.

#Comment by 😂 (talk | contribs)   13:43, 8 August 2010

Well $_COOKIE is already handled in WebRequest/WebResponse. No need to move it to HttpRequest stuff.

In general, since we use FauxRequest/FauxResponse for testing, I'd think it'd be best to move the FauxCookies there.

#Comment by MarkAHershberger (talk | contribs)   17:03, 8 August 2010

$_COOKIE doesn't hold cookies received in any HTTP Requests that HttpRequest makes.

And you're right that Faux* should be used, in general, for testing.

#Comment by Tim Starling (talk | contribs)   03:43, 9 June 2010

Why does it follow redirects by default? This causes several bugs. It would be easier if it didn't, then we would know which callers are broken by backends that don't support redirects.

#Comment by Skizzerz (talk | contribs)   04:30, 9 June 2010

In addition, users on hosts with safe_mode open_basedir set will get a warning "CURLOPT_FOLLOWLOCATION cannot be activated when in safe_mode or an open_basedir is set"

#Comment by Tim Starling (talk | contribs)   04:50, 9 June 2010

Hence r67663.

#Comment by Hashar (talk | contribs)   09:28, 7 November 2010

Can we now mark this revision ok ?

Status & tagging log