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