Index: trunk/phase3/maintenance/language/messages.inc |
— | — | @@ -842,7 +842,7 @@ |
843 | 843 | 'powersearch-redir', |
844 | 844 | 'powersearch-field', |
845 | 845 | 'powersearch-togglelabel', |
846 | | - 'powersearch-toggleall', |
| 846 | + 'powersearch-toggleall', |
847 | 847 | 'powersearch-togglenone', |
848 | 848 | 'search-external', |
849 | 849 | 'searchdisabled', |
— | — | @@ -1275,6 +1275,10 @@ |
1276 | 1276 | 'http-invalid-url', |
1277 | 1277 | 'http-invalid-scheme', |
1278 | 1278 | 'http-request-error', |
| 1279 | + 'http-read-error', |
| 1280 | + 'http-timed-out', |
| 1281 | + 'http-curl-error', |
| 1282 | + 'http-host-unreachable', |
1279 | 1283 | ), |
1280 | 1284 | |
1281 | 1285 | 'upload-curl-errors' => array( |
Index: trunk/phase3/tests/HttpTest.php |
— | — | @@ -1,7 +1,9 @@ |
2 | 2 | <?php |
3 | 3 | |
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(); } |
6 | 8 | } |
7 | 9 | |
8 | 10 | class HttpTest extends PhpUnit_Framework_TestCase { |
— | — | @@ -131,6 +133,8 @@ |
132 | 134 | |
133 | 135 | if($proxy) { |
134 | 136 | $opt['proxy'] = $proxy; |
| 137 | + } elseif( $proxy === false ) { |
| 138 | + $opt['noProxy'] = true; |
135 | 139 | } |
136 | 140 | |
137 | 141 | /* no postData here because the only request I could find in code so far didn't have any */ |
— | — | @@ -208,6 +212,8 @@ |
209 | 213 | |
210 | 214 | if($proxy) { |
211 | 215 | $opt['proxy'] = $proxy; |
| 216 | + } elseif( $proxy === false ) { |
| 217 | + $opt['noProxy'] = true; |
212 | 218 | } |
213 | 219 | |
214 | 220 | foreach ( $this->test_geturl as $u ) { |
— | — | @@ -241,6 +247,8 @@ |
242 | 248 | |
243 | 249 | if($proxy) { |
244 | 250 | $opt['proxy'] = $proxy; |
| 251 | + } elseif( $proxy === false ) { |
| 252 | + $opt['noProxy'] = true; |
245 | 253 | } |
246 | 254 | |
247 | 255 | foreach ( $this->test_posturl as $u => $postData ) { |
— | — | @@ -277,6 +285,11 @@ |
278 | 286 | self::runHTTPGets(self::$proxy); |
279 | 287 | self::runHTTPPosts(self::$proxy); |
280 | 288 | self::runHTTPRequests(self::$proxy); |
| 289 | + |
| 290 | + // Set false here to do noProxy |
| 291 | + self::runHTTPGets(false); |
| 292 | + self::runHTTPPosts(false); |
| 293 | + self::runHTTPRequests(false); |
281 | 294 | } |
282 | 295 | |
283 | 296 | function testProxyDefault() { |
— | — | @@ -308,4 +321,150 @@ |
309 | 322 | function testIsValidUrl() { |
310 | 323 | } |
311 | 324 | |
| 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 | + |
312 | 471 | } |
\ No newline at end of file |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -36,6 +36,8 @@ |
37 | 37 | 'ChangesFeed' => 'includes/ChangesFeed.php', |
38 | 38 | 'ChangeTags' => 'includes/ChangeTags.php', |
39 | 39 | 'ChannelFeed' => 'includes/Feed.php', |
| 40 | + 'Cookie' => 'includes/HttpFunctions.php', |
| 41 | + 'CookieJar' => 'includes/HttpFunctions.php', |
40 | 42 | 'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php', |
41 | 43 | 'ConfEditor' => 'includes/ConfEditor.php', |
42 | 44 | 'ConfEditorParseError' => 'includes/ConfEditor.php', |
Index: trunk/phase3/includes/HttpFunctions.php |
— | — | @@ -15,6 +15,15 @@ |
16 | 16 | * @param $method string HTTP method. Usually GET/POST |
17 | 17 | * @param $url string Full URL to act on |
18 | 18 | * @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) |
19 | 28 | * @returns mixed (bool)false on failure or a string on success |
20 | 29 | */ |
21 | 30 | public static function request( $method, $url, $options = array() ) { |
— | — | @@ -124,21 +133,21 @@ |
125 | 134 | protected $url; |
126 | 135 | protected $parsedUrl; |
127 | 136 | 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 | + |
128 | 147 | public $status; |
129 | 148 | |
130 | 149 | /** |
131 | 150 | * @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()) |
143 | 152 | */ |
144 | 153 | function __construct( $url, $options = array() ) { |
145 | 154 | global $wgHTTPTimeout; |
— | — | @@ -158,8 +167,8 @@ |
159 | 168 | $this->timeout = $wgHTTPTimeout; |
160 | 169 | } |
161 | 170 | |
162 | | - $members = array( "targetFilePath", "requestKey", "postData", |
163 | | - "proxy", "noProxy", "sslVerifyHost", "caInfo", "method" ); |
| 171 | + $members = array( "postData", "proxy", "noProxy", "sslVerifyHost", "caInfo", |
| 172 | + "method", "followRedirects", "maxRedirects" ); |
164 | 173 | foreach ( $members as $o ) { |
165 | 174 | if ( isset($options[$o]) ) { |
166 | 175 | $this->$o = $options[$o]; |
— | — | @@ -175,7 +184,8 @@ |
176 | 185 | if ( !Http::$httpEngine ) { |
177 | 186 | Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php'; |
178 | 187 | } 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"' ); |
180 | 190 | } |
181 | 191 | |
182 | 192 | switch( Http::$httpEngine ) { |
— | — | @@ -183,8 +193,8 @@ |
184 | 194 | return new CurlHttpRequest( $url, $options ); |
185 | 195 | case 'php': |
186 | 196 | 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.' ); |
189 | 199 | } |
190 | 200 | return new PhpHttpRequest( $url, $options ); |
191 | 201 | default: |
— | — | @@ -208,7 +218,6 @@ |
209 | 219 | public function proxySetup() { |
210 | 220 | global $wgHTTPProxy; |
211 | 221 | |
212 | | - |
213 | 222 | if ( $this->proxy ) { |
214 | 223 | return; |
215 | 224 | } |
— | — | @@ -247,6 +256,9 @@ |
248 | 257 | public function getHeaderList() { |
249 | 258 | $list = array(); |
250 | 259 | |
| 260 | + if( $this->cookieJar ) { |
| 261 | + $this->reqHeaders['Cookie'] = $this->cookieJar->serializeToHttpRequest(); |
| 262 | + } |
251 | 263 | foreach($this->reqHeaders as $name => $value) { |
252 | 264 | $list[] = "$name: $value"; |
253 | 265 | } |
— | — | @@ -262,7 +274,8 @@ |
263 | 275 | } |
264 | 276 | |
265 | 277 | /** |
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. |
267 | 280 | * @param $fh handle |
268 | 281 | * @param $content string |
269 | 282 | */ |
— | — | @@ -302,14 +315,266 @@ |
303 | 316 | $this->setUserAgent(Http::userAgent()); |
304 | 317 | } |
305 | 318 | } |
| 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 | + } |
306 | 412 | } |
307 | 413 | |
| 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 | + |
308 | 562 | /** |
309 | 563 | * HttpRequest implemented using internal curl compiled into PHP |
310 | 564 | */ |
311 | 565 | class CurlHttpRequest extends HttpRequest { |
| 566 | + static $curlMessageMap = array( |
| 567 | + 6 => 'http-host-unreachable', |
| 568 | + 28 => 'http-timed-out' |
| 569 | + ); |
| 570 | + |
312 | 571 | protected $curlOptions = array(); |
| 572 | + protected $headerText = ""; |
313 | 573 | |
| 574 | + protected function readHeader( $fh, $content ) { |
| 575 | + $this->headerText .= $content; |
| 576 | + return strlen( $content ); |
| 577 | + } |
| 578 | + |
314 | 579 | public function execute() { |
315 | 580 | parent::execute(); |
316 | 581 | if ( !$this->status->isOK() ) { |
— | — | @@ -319,6 +584,9 @@ |
320 | 585 | $this->curlOptions[CURLOPT_TIMEOUT] = $this->timeout; |
321 | 586 | $this->curlOptions[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; |
322 | 587 | $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; |
323 | 591 | |
324 | 592 | /* not sure these two are actually necessary */ |
325 | 593 | if(isset($this->reqHeaders['Referer'])) { |
— | — | @@ -354,8 +622,15 @@ |
355 | 623 | curl_setopt_array( $curlHandle, $this->curlOptions ); |
356 | 624 | |
357 | 625 | 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); |
360 | 635 | } |
361 | 636 | |
362 | 637 | curl_close( $curlHandle ); |
— | — | @@ -394,6 +669,12 @@ |
395 | 670 | $options['request_fulluri'] = true; |
396 | 671 | } |
397 | 672 | |
| 673 | + if ( !$this->followRedirects ) { |
| 674 | + $options['max_redirects'] = 0; |
| 675 | + } else { |
| 676 | + $options['max_redirects'] = $this->maxRedirects; |
| 677 | + } |
| 678 | + |
398 | 679 | $options['method'] = $this->method; |
399 | 680 | $options['timeout'] = $this->timeout; |
400 | 681 | $options['header'] = implode("\r\n", $this->getHeaderList()); |
— | — | @@ -428,9 +709,8 @@ |
429 | 710 | $this->status->fatal( 'http-timed-out', $this->url ); |
430 | 711 | return $this->status; |
431 | 712 | } |
| 713 | + $this->headerList = $result['wrapper_data']; |
432 | 714 | |
433 | | - $this->headers = $result['wrapper_data']; |
434 | | - |
435 | 715 | while ( !feof( $fh ) ) { |
436 | 716 | $buf = fread( $fh, 8192 ); |
437 | 717 | if ( $buf === false ) { |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -2059,7 +2059,7 @@ |
2060 | 2060 | [[$1|thumb]]", |
2061 | 2061 | 'fileexists-extension' => "A file with a similar name exists: [[$2|thumb]] |
2062 | 2062 | * 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>''' |
2064 | 2064 | Please choose a different name.", |
2065 | 2065 | 'fileexists-thumbnail-yes' => "The file seems to be an image of reduced size ''(thumbnail)''. |
2066 | 2066 | [[$1|thumb]] |
— | — | @@ -2153,9 +2153,13 @@ |
2154 | 2154 | 'img-auth-noread' => 'User does not have access to read "$1".', |
2155 | 2155 | |
2156 | 2156 | # HTTP errors |
2157 | | -'http-invalid-url' => 'Invalid URL: $1', |
| 2157 | +'http-invalid-url' => 'Invalid URL: $1', |
2158 | 2158 | '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', |
2160 | 2164 | |
2161 | 2165 | # Some likely curl errors. More could be added from <http://curl.haxx.se/libcurl/c/libcurl-errors.html> |
2162 | 2166 | 'upload-curl-error6' => 'Could not reach URL', |
— | — | @@ -2417,7 +2421,7 @@ |
2418 | 2422 | 'ancientpages-summary' => '', # do not translate or duplicate this message to other languages |
2419 | 2423 | 'move' => 'Move', |
2420 | 2424 | '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. |
2422 | 2426 | 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.', |
2423 | 2427 | 'unusedcategoriestext' => 'The following category pages exist, although no other page or category makes use of them.', |
2424 | 2428 | 'notargettitle' => 'No target', |