HttpTest regression fix: test URL on example.com no longer returns a 404 (now a 302...
[lhc/web/wiklou.git] / tests / phpunit / includes / HttpTest.php
1 <?php
2
3 class MockCookie extends Cookie {
4 public function canServeDomain( $arg ) { return parent::canServeDomain( $arg ); }
5 public function canServePath( $arg ) { return parent::canServePath( $arg ); }
6 public function isUnExpired() { return parent::isUnExpired(); }
7 }
8
9 /**
10 * @group Broken
11 */
12 class HttpTest extends MediaWikiTestCase {
13 static $content;
14 static $headers;
15 static $has_curl;
16 static $has_fopen;
17 static $has_proxy = false;
18 static $proxy = "http://hulk:8080/";
19 var $test_geturl = array(
20 "ftp://ftp.kernel.org/",
21 "http://www.example.com/",
22 "https://secure.wikimedia.org/",
23 "http://pecl.php.net/feeds/pkg_apc.rss",
24 "http://toolserver.org/~jan/poll/dev/main.php?page=wiki_output&id=3",
25 "http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw",
26 "http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&format=php",
27 );
28 var $test_requesturl = array( "http://en.wikipedia.org/wiki/Special:Export/User:MarkAHershberger" );
29
30 var $test_posturl = array( "http://www.comp.leeds.ac.uk/cgi-bin/Perl/environment-example" => "review=test" );
31
32 function setUp() {
33 putenv( "http_proxy" ); /* Remove any proxy env var, so curl doesn't get confused */
34 if ( is_array( self::$content ) ) {
35 return;
36 }
37 self::$has_curl = function_exists( 'curl_init' );
38 self::$has_fopen = wfIniGetBool( 'allow_url_fopen' );
39
40 if ( !file_exists( "/usr/bin/curl" ) ) {
41 $this->markTestIncomplete( "This test requires the curl binary at /usr/bin/curl. If you have curl, please file a bug on this test, or, better yet, provide a patch." );
42 }
43
44 $content = tempnam( wfTempDir(), "" );
45 $headers = tempnam( wfTempDir(), "" );
46 if ( !$content && !$headers ) {
47 die( "Couldn't create temp file!" );
48 }
49
50 // This probably isn't the best test for a proxy, but it works on my system!
51 system( "curl -0 -o $content -s " . self::$proxy );
52 $out = file_get_contents( $content );
53 if ( $out ) {
54 self::$has_proxy = true;
55 }
56
57 /* Maybe use wget instead of curl here ... just to use a different codebase? */
58 foreach ( $this->test_geturl as $u ) {
59 system( "curl -0 -s -D $headers '$u' -o $content" );
60 self::$content["GET $u"] = file_get_contents( $content );
61 self::$headers["GET $u"] = file_get_contents( $headers );
62 }
63 foreach ( $this->test_requesturl as $u ) {
64 system( "curl -0 -s -X POST -H 'Content-Length: 0' -D $headers '$u' -o $content" );
65 self::$content["POST $u"] = file_get_contents( $content );
66 self::$headers["POST $u"] = file_get_contents( $headers );
67 }
68 foreach ( $this->test_posturl as $u => $postData ) {
69 system( "curl -0 -s -X POST -d '$postData' -D $headers '$u' -o $content" );
70 self::$content["POST $u => $postData"] = file_get_contents( $content );
71 self::$headers["POST $u => $postData"] = file_get_contents( $headers );
72 }
73 unlink( $content );
74 unlink( $headers );
75 }
76
77
78 function testInstantiation() {
79 Http::$httpEngine = false;
80
81 $r = MWHttpRequest::factory( "http://www.example.com/" );
82 if ( self::$has_curl ) {
83 $this->assertThat( $r, $this->isInstanceOf( 'CurlHttpRequest' ) );
84 } else {
85 $this->assertThat( $r, $this->isInstanceOf( 'PhpHttpRequest' ) );
86 }
87 unset( $r );
88
89 if ( !self::$has_fopen ) {
90 $this->setExpectedException( 'MWException' );
91 }
92 Http::$httpEngine = 'php';
93 $r = MWHttpRequest::factory( "http://www.example.com/" );
94 $this->assertThat( $r, $this->isInstanceOf( 'PhpHttpRequest' ) );
95 unset( $r );
96
97 if ( !self::$has_curl ) {
98 $this->setExpectedException( 'MWException' );
99 }
100 Http::$httpEngine = 'curl';
101 $r = MWHttpRequest::factory( "http://www.example.com/" );
102 if ( self::$has_curl ) {
103 $this->assertThat( $r, $this->isInstanceOf( 'CurlHttpRequest' ) );
104 }
105 }
106
107 function runHTTPFailureChecks() {
108 // Each of the following requests should result in a failure.
109
110 $timeout = 1;
111 $start_time = time();
112 $r = Http::get( "http://www.example.com:1/", $timeout );
113 $end_time = time();
114 $this->assertLessThan( $timeout + 2, $end_time - $start_time,
115 "Request took less than {$timeout}s via " . Http::$httpEngine );
116 $this->assertEquals( $r, false, "false -- what we get on error from Http::get()" );
117
118 $r = Http::get( "http://www.mediawiki.org/xml/made-up-url", $timeout );
119 $this->assertFalse( $r, "False on 404s" );
120
121
122 $r = MWHttpRequest::factory( "http://www.mediawiki.org/xml/made-up-url" );
123 $er = $r->execute();
124 if ( $r instanceof PhpHttpRequest && version_compare( '5.2.10', phpversion(), '>' ) ) {
125 $this->assertRegexp( "/HTTP request failed/", $er->getWikiText() );
126 } else {
127 $this->assertRegexp( "/404 Not Found/", $er->getWikiText() );
128 }
129 }
130
131 function testFailureDefault() {
132 Http::$httpEngine = false;
133 $this->runHTTPFailureChecks();
134 }
135
136 function testFailurePhp() {
137 if ( !self::$has_fopen ) {
138 $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
139 }
140
141 Http::$httpEngine = "php";
142 $this->runHTTPFailureChecks();
143 }
144
145 function testFailureCurl() {
146 if ( !self::$has_curl ) {
147 $this->markTestIncomplete( "This test requires curl." );
148 }
149
150 Http::$httpEngine = "curl";
151 $this->runHTTPFailureChecks();
152 }
153
154 /* ./phase3/includes/Import.php:1108: $data = Http::request( $method, $url ); */
155 /* ./includes/Import.php:1124: $link = Title::newFromText( "$interwiki:Special:Export/$page" ); */
156 /* ./includes/Import.php:1134: return ImportStreamSource::newFromURL( $url, "POST" ); */
157 function runHTTPRequests( $proxy = null ) {
158 $opt = array();
159
160 if ( $proxy ) {
161 $opt['proxy'] = $proxy;
162 } elseif ( $proxy === false ) {
163 $opt['noProxy'] = true;
164 }
165
166 /* no postData here because the only request I could find in code so far didn't have any */
167 foreach ( $this->test_requesturl as $u ) {
168 $r = Http::request( "POST", $u, $opt );
169 $this->assertEquals( self::$content["POST $u"], "$r", "POST $u with " . Http::$httpEngine );
170 }
171 }
172
173 function testRequestDefault() {
174 Http::$httpEngine = false;
175 $this->runHTTPRequests();
176 }
177
178 function testRequestPhp() {
179 if ( !self::$has_fopen ) {
180 $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
181 }
182
183 Http::$httpEngine = "php";
184 $this->runHTTPRequests();
185 }
186
187 function testRequestCurl() {
188 if ( !self::$has_curl ) {
189 $this->markTestIncomplete( "This test requires curl." );
190 }
191
192 Http::$httpEngine = "curl";
193 $this->runHTTPRequests();
194 }
195
196 function runHTTPGets( $proxy = null ) {
197 $opt = array();
198
199 if ( $proxy ) {
200 $opt['proxy'] = $proxy;
201 } elseif ( $proxy === false ) {
202 $opt['noProxy'] = true;
203 }
204
205 foreach ( $this->test_geturl as $u ) {
206 $r = Http::get( $u, 30, $opt ); /* timeout of 30s */
207 $this->assertEquals( self::$content["GET $u"], "$r", "Get $u with " . Http::$httpEngine );
208 }
209 }
210
211 function testGetDefault() {
212 Http::$httpEngine = false;
213 $this->runHTTPGets();
214 }
215
216 function testGetPhp() {
217 if ( !self::$has_fopen ) {
218 $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
219 }
220
221 Http::$httpEngine = "php";
222 $this->runHTTPGets();
223 }
224
225 function testGetCurl() {
226 if ( !self::$has_curl ) {
227 $this->markTestIncomplete( "This test requires curl." );
228 }
229
230 Http::$httpEngine = "curl";
231 $this->runHTTPGets();
232 }
233
234 function runHTTPPosts( $proxy = null ) {
235 $opt = array();
236
237 if ( $proxy ) {
238 $opt['proxy'] = $proxy;
239 } elseif ( $proxy === false ) {
240 $opt['noProxy'] = true;
241 }
242
243 foreach ( $this->test_posturl as $u => $postData ) {
244 $opt['postData'] = $postData;
245 $r = Http::post( $u, $opt );
246 $this->assertEquals( self::$content["POST $u => $postData"], "$r",
247 "POST $u (postData=$postData) with " . Http::$httpEngine );
248 }
249 }
250
251 function testPostDefault() {
252 Http::$httpEngine = false;
253 $this->runHTTPPosts();
254 }
255
256 function testPostPhp() {
257 if ( !self::$has_fopen ) {
258 $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
259 }
260
261 Http::$httpEngine = "php";
262 $this->runHTTPPosts();
263 }
264
265 function testPostCurl() {
266 if ( !self::$has_curl ) {
267 $this->markTestIncomplete( "This test requires curl." );
268 }
269
270 Http::$httpEngine = "curl";
271 $this->runHTTPPosts();
272 }
273
274 function runProxyRequests() {
275 if ( !self::$has_proxy ) {
276 $this->markTestIncomplete( "This test requires a proxy." );
277 }
278 $this->runHTTPGets( self::$proxy );
279 $this->runHTTPPosts( self::$proxy );
280 $this->runHTTPRequests( self::$proxy );
281
282 // Set false here to do noProxy
283 $this->runHTTPGets( false );
284 $this->runHTTPPosts( false );
285 $this->runHTTPRequests( false );
286 }
287
288 function testProxyDefault() {
289 Http::$httpEngine = false;
290 $this->runProxyRequests();
291 }
292
293 function testProxyPhp() {
294 if ( !self::$has_fopen ) {
295 $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
296 }
297
298 Http::$httpEngine = 'php';
299 $this->runProxyRequests();
300 }
301
302 function testProxyCurl() {
303 if ( !self::$has_curl ) {
304 $this->markTestIncomplete( "This test requires curl." );
305 }
306
307 Http::$httpEngine = 'curl';
308 $this->runProxyRequests();
309 }
310
311 function testIsLocalUrl() {
312 }
313
314 /* ./extensions/DonationInterface/payflowpro_gateway/payflowpro_gateway.body.php:559: $user_agent = Http::userAgent(); */
315 function testUserAgent() {
316 }
317
318 function testIsValidUrl() {
319 }
320
321 function testValidateCookieDomain() {
322 $this->assertFalse( Cookie::validateCookieDomain( "co.uk" ) );
323 $this->assertFalse( Cookie::validateCookieDomain( ".co.uk" ) );
324 $this->assertFalse( Cookie::validateCookieDomain( "gov.uk" ) );
325 $this->assertFalse( Cookie::validateCookieDomain( ".gov.uk" ) );
326 $this->assertTrue( Cookie::validateCookieDomain( "supermarket.uk" ) );
327 $this->assertFalse( Cookie::validateCookieDomain( "uk" ) );
328 $this->assertFalse( Cookie::validateCookieDomain( ".uk" ) );
329 $this->assertFalse( Cookie::validateCookieDomain( "127.0.0." ) );
330 $this->assertFalse( Cookie::validateCookieDomain( "127." ) );
331 $this->assertFalse( Cookie::validateCookieDomain( "127.0.0.1." ) );
332 $this->assertTrue( Cookie::validateCookieDomain( "127.0.0.1" ) );
333 $this->assertFalse( Cookie::validateCookieDomain( "333.0.0.1" ) );
334 $this->assertTrue( Cookie::validateCookieDomain( "example.com" ) );
335 $this->assertFalse( Cookie::validateCookieDomain( "example.com." ) );
336 $this->assertTrue( Cookie::validateCookieDomain( ".example.com" ) );
337
338 $this->assertTrue( Cookie::validateCookieDomain( ".example.com", "www.example.com" ) );
339 $this->assertFalse( Cookie::validateCookieDomain( "example.com", "www.example.com" ) );
340 $this->assertTrue( Cookie::validateCookieDomain( "127.0.0.1", "127.0.0.1" ) );
341 $this->assertFalse( Cookie::validateCookieDomain( "127.0.0.1", "localhost" ) );
342
343
344 }
345
346 function testSetCooke() {
347 $c = new MockCookie( "name", "value",
348 array(
349 "domain" => "ac.th",
350 "path" => "/path/",
351 ) );
352 $this->assertFalse( $c->canServeDomain( "ac.th" ) );
353
354 $c = new MockCookie( "name", "value",
355 array(
356 "domain" => "example.com",
357 "path" => "/path/",
358 ) );
359
360 $this->assertTrue( $c->canServeDomain( "example.com" ) );
361 $this->assertFalse( $c->canServeDomain( "www.example.com" ) );
362
363 $c = new MockCookie( "name", "value",
364 array(
365 "domain" => ".example.com",
366 "path" => "/path/",
367 ) );
368
369 $this->assertFalse( $c->canServeDomain( "www.example.net" ) );
370 $this->assertFalse( $c->canServeDomain( "example.com" ) );
371 $this->assertTrue( $c->canServeDomain( "www.example.com" ) );
372
373 $this->assertFalse( $c->canServePath( "/" ) );
374 $this->assertFalse( $c->canServePath( "/bogus/path/" ) );
375 $this->assertFalse( $c->canServePath( "/path" ) );
376 $this->assertTrue( $c->canServePath( "/path/" ) );
377
378 $this->assertTrue( $c->isUnExpired() );
379
380 $this->assertEquals( "", $c->serializeToHttpRequest( "/path/", "www.example.net" ) );
381 $this->assertEquals( "", $c->serializeToHttpRequest( "/", "www.example.com" ) );
382 $this->assertEquals( "name=value", $c->serializeToHttpRequest( "/path/", "www.example.com" ) );
383
384 $c = new MockCookie( "name", "value",
385 array(
386 "domain" => "www.example.com",
387 "path" => "/path/",
388 ) );
389 $this->assertFalse( $c->canServeDomain( "example.com" ) );
390 $this->assertFalse( $c->canServeDomain( "www.example.net" ) );
391 $this->assertTrue( $c->canServeDomain( "www.example.com" ) );
392
393 $c = new MockCookie( "name", "value",
394 array(
395 "domain" => ".example.com",
396 "path" => "/path/",
397 "expires" => "-1 day",
398 ) );
399 $this->assertFalse( $c->isUnExpired() );
400 $this->assertEquals( "", $c->serializeToHttpRequest( "/path/", "www.example.com" ) );
401
402 $c = new MockCookie( "name", "value",
403 array(
404 "domain" => ".example.com",
405 "path" => "/path/",
406 "expires" => "+1 day",
407 ) );
408 $this->assertTrue( $c->isUnExpired() );
409 $this->assertEquals( "name=value", $c->serializeToHttpRequest( "/path/", "www.example.com" ) );
410 }
411
412 function testCookieJarSetCookie() {
413 $cj = new CookieJar;
414 $cj->setCookie( "name", "value",
415 array(
416 "domain" => ".example.com",
417 "path" => "/path/",
418 ) );
419 $cj->setCookie( "name2", "value",
420 array(
421 "domain" => ".example.com",
422 "path" => "/path/sub",
423 ) );
424 $cj->setCookie( "name3", "value",
425 array(
426 "domain" => ".example.com",
427 "path" => "/",
428 ) );
429 $cj->setCookie( "name4", "value",
430 array(
431 "domain" => ".example.net",
432 "path" => "/path/",
433 ) );
434 $cj->setCookie( "name5", "value",
435 array(
436 "domain" => ".example.net",
437 "path" => "/path/",
438 "expires" => "-1 day",
439 ) );
440
441 $this->assertEquals( "name4=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
442 $this->assertEquals( "name3=value", $cj->serializeToHttpRequest( "/", "www.example.com" ) );
443 $this->assertEquals( "name=value; name3=value", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) );
444
445 $cj->setCookie( "name5", "value",
446 array(
447 "domain" => ".example.net",
448 "path" => "/path/",
449 "expires" => "+1 day",
450 ) );
451 $this->assertEquals( "name4=value; name5=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
452
453 $cj->setCookie( "name4", "value",
454 array(
455 "domain" => ".example.net",
456 "path" => "/path/",
457 "expires" => "-1 day",
458 ) );
459 $this->assertEquals( "name5=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
460 }
461
462 function testParseResponseHeader() {
463 $cj = new CookieJar;
464
465 $h[] = "Set-Cookie: name4=value; domain=.example.com; path=/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
466 $cj->parseCookieResponseHeader( $h[0], "www.example.com" );
467 $this->assertEquals( "name4=value", $cj->serializeToHttpRequest( "/", "www.example.com" ) );
468
469 $h[] = "name4=value2; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
470 $cj->parseCookieResponseHeader( $h[1], "www.example.com" );
471 $this->assertEquals( "", $cj->serializeToHttpRequest( "/", "www.example.com" ) );
472 $this->assertEquals( "name4=value2", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) );
473
474 $h[] = "name5=value3; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
475 $cj->parseCookieResponseHeader( $h[2], "www.example.com" );
476 $this->assertEquals( "name4=value2; name5=value3", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) );
477
478 $h[] = "name6=value3; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
479 $cj->parseCookieResponseHeader( $h[3], "www.example.com" );
480 $this->assertEquals( "", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
481
482 $h[] = "name6=value0; domain=.example.net; path=/path/; expires=Mon, 09-Dec-1999 13:46:00 GMT";
483 $cj->parseCookieResponseHeader( $h[4], "www.example.net" );
484 $this->assertEquals( "", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
485
486 $h[] = "name6=value4; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
487 $cj->parseCookieResponseHeader( $h[5], "www.example.net" );
488 $this->assertEquals( "name6=value4", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
489 }
490
491 function runCookieRequests() {
492 $r = MWHttpRequest::factory( "http://www.php.net/manual", array( 'followRedirects' => true ) );
493 $r->execute();
494
495 $jar = $r->getCookieJar();
496 $this->assertThat( $jar, $this->isInstanceOf( 'CookieJar' ) );
497
498 $serialized = $jar->serializeToHttpRequest( "/search?q=test", "www.php.net" );
499 $this->assertRegExp( '/\bCOUNTRY=[^=;]+/', $serialized );
500 $this->assertRegExp( '/\bLAST_LANG=[^=;]+/', $serialized );
501 $this->assertEquals( '', $jar->serializeToHttpRequest( "/search?q=test", "www.php.com" ) );
502 }
503
504 function testCookieRequestDefault() {
505 Http::$httpEngine = false;
506 $this->runCookieRequests();
507 }
508 function testCookieRequestPhp() {
509 if ( !self::$has_fopen ) {
510 $this->markTestIncomplete( "This test requires allow_url_fopen=true." );
511 }
512
513 Http::$httpEngine = 'php';
514 $this->runCookieRequests();
515 }
516 function testCookieRequestCurl() {
517 if ( !self::$has_curl ) {
518 $this->markTestIncomplete( "This test requires curl." );
519 }
520
521 Http::$httpEngine = 'curl';
522 $this->runCookieRequests();
523 }
524
525 /**
526 * Test Http::isValidURI()
527 * @bug 27854 : Http::isValidURI is to lax
528 *@dataProvider provideURI */
529 function testIsValidUri( $expect, $URI, $message = '' ) {
530 $this->assertEquals(
531 $expect,
532 (bool) Http::isValidURI( $URI ),
533 $message
534 );
535 }
536
537 /**
538 * Feeds URI to test a long regular expression in Http::isValidURI
539 */
540 function provideURI() {
541 /** Format: 'boolean expectation', 'URI to test', 'Optional message' */
542 return array(
543 array( false, '¿non sens before!! http://a', 'Allow anything before URI' ),
544
545 # (ftp|http|https) - only three schemes allowed
546 array( true, 'http://www.example.org/' ),
547 array( true, 'https://www.example.org/' ),
548 array( true, 'ftp://www.example.org/' ),
549 array( true, 'http://www.example.org', 'URI without directory' ),
550 array( true, 'http://a', 'Short name' ),
551 array( true, 'http://étoile', 'Allow UTF-8 in hostname' ), # 'étoile' is french for 'star'
552 array( false, '\\host\directory', 'CIFS share' ),
553 array( false, 'gopher://host/dir', 'Reject gopher scheme' ),
554 array( false, 'telnet://host', 'Reject telnet scheme' ),
555
556 # :\/\/ - double slashes
557 array( false, 'http//example.org', 'Reject missing colon in protocol' ),
558 array( false, 'http:/example.org', 'Reject missing slash in protocol' ),
559 array( false, 'http:example.org', 'Must have two slashes' ),
560 # Following fail since hostname can be made of anything
561 array( false, 'http:///example.org', 'Must have exactly two slashes, not three' ),
562
563 # (\w+:{0,1}\w*@)? - optional user:pass
564 array( true, 'http://user@host', 'Username provided' ),
565 array( true, 'http://user:@host', 'Username provided, no password' ),
566 array( true, 'http://user:pass@host', 'Username and password provided' ),
567
568 # (\S+) - host part is made of anything not whitespaces
569 array( false, 'http://!"èèè¿¿¿~~\'', 'hostname is made of any non whitespace' ),
570 array( false, 'http://exam:ple.org/', 'hostname can not use colons!' ),
571
572 # (:[0-9]+)? - port number
573 array( true, 'http://example.org:80/' ),
574 array( true, 'https://example.org:80/' ),
575 array( true, 'http://example.org:443/' ),
576 array( true, 'https://example.org:443/' ),
577 array( true, 'ftp://example.org:1/', 'Minimum' ),
578 array( true, 'ftp://example.org:65535/', 'Maximum port number' ),
579
580 # Part after the hostname is / or / with something else
581 array( true, 'http://example/#' ),
582 array( true, 'http://example/!' ),
583 array( true, 'http://example/:' ),
584 array( true, 'http://example/.' ),
585 array( true, 'http://example/?' ),
586 array( true, 'http://example/+' ),
587 array( true, 'http://example/=' ),
588 array( true, 'http://example/&' ),
589 array( true, 'http://example/%' ),
590 array( true, 'http://example/@' ),
591 array( true, 'http://example/-' ),
592 array( true, 'http://example//' ),
593 array( true, 'http://example/&' ),
594
595 # Fragment
596 array( true, 'http://exam#ple.org', ), # This one is valid, really!
597 array( true, 'http://example.org:80#anchor' ),
598 array( true, 'http://example.org/?id#anchor' ),
599 array( true, 'http://example.org/?#anchor' ),
600
601 array( false, 'http://a ¿non !!sens after', 'Allow anything after URI' ),
602 );
603 }
604
605 }