Index: trunk/phase3/maintenance/tests/HttpTest.php |
— | — | @@ -346,15 +346,56 @@ |
347 | 347 | function testIsValidUrl() { |
348 | 348 | } |
349 | 349 | |
350 | | - function testSetCookie() { |
| 350 | + function testValidateCookieDomain() { |
| 351 | + $this->assertFalse( Cookie::validateCookieDomain( "co.uk" ) ); |
| 352 | + $this->assertFalse( Cookie::validateCookieDomain( ".co.uk" ) ); |
| 353 | + $this->assertFalse( Cookie::validateCookieDomain( "gov.uk" ) ); |
| 354 | + $this->assertFalse( Cookie::validateCookieDomain( ".gov.uk" ) ); |
| 355 | + $this->assertTrue( Cookie::validateCookieDomain( "supermarket.uk" ) ); |
| 356 | + $this->assertFalse( Cookie::validateCookieDomain( "uk" ) ); |
| 357 | + $this->assertFalse( Cookie::validateCookieDomain( ".uk" ) ); |
| 358 | + $this->assertFalse( Cookie::validateCookieDomain( "127.0.0." ) ); |
| 359 | + $this->assertFalse( Cookie::validateCookieDomain( "127." ) ); |
| 360 | + $this->assertFalse( Cookie::validateCookieDomain( "127.0.0.1." ) ); |
| 361 | + $this->assertTrue( Cookie::validateCookieDomain( "127.0.0.1" ) ); |
| 362 | + $this->assertFalse( Cookie::validateCookieDomain( "333.0.0.1" ) ); |
| 363 | + $this->assertTrue( Cookie::validateCookieDomain( "example.com" ) ); |
| 364 | + $this->assertFalse( Cookie::validateCookieDomain( "example.com." ) ); |
| 365 | + $this->assertTrue( Cookie::validateCookieDomain( ".example.com" ) ); |
| 366 | + |
| 367 | + $this->assertTrue( Cookie::validateCookieDomain( ".example.com", "www.example.com" ) ); |
| 368 | + $this->assertFalse( Cookie::validateCookieDomain( "example.com", "www.example.com" ) ); |
| 369 | + $this->assertTrue( Cookie::validateCookieDomain( "127.0.0.1", "127.0.0.1" ) ); |
| 370 | + $this->assertFalse( Cookie::validateCookieDomain( "127.0.0.1", "localhost" ) ); |
| 371 | + |
| 372 | + |
| 373 | + } |
| 374 | + |
| 375 | + function testSetCooke() { |
351 | 376 | $c = new MockCookie( "name", "value", |
352 | 377 | array( |
| 378 | + "domain" => "ac.th", |
| 379 | + "path" => "/path/", |
| 380 | + ) ); |
| 381 | + $this->assertFalse($c->canServeDomain("ac.th")); |
| 382 | + |
| 383 | + $c = new MockCookie( "name", "value", |
| 384 | + array( |
| 385 | + "domain" => "example.com", |
| 386 | + "path" => "/path/", |
| 387 | + ) ); |
| 388 | + |
| 389 | + $this->assertTrue($c->canServeDomain("example.com")); |
| 390 | + $this->assertFalse($c->canServeDomain("www.example.com")); |
| 391 | + |
| 392 | + $c = new MockCookie( "name", "value", |
| 393 | + array( |
353 | 394 | "domain" => ".example.com", |
354 | 395 | "path" => "/path/", |
355 | 396 | ) ); |
356 | 397 | |
357 | | - $this->assertTrue($c->canServeDomain("example.com")); |
358 | 398 | $this->assertFalse($c->canServeDomain("www.example.net")); |
| 399 | + $this->assertFalse($c->canServeDomain("example.com")); |
359 | 400 | $this->assertTrue($c->canServeDomain("www.example.com")); |
360 | 401 | |
361 | 402 | $this->assertFalse($c->canServePath("/")); |
— | — | @@ -369,6 +410,15 @@ |
370 | 411 | $this->assertEquals("name=value", $c->serializeToHttpRequest("/path/", "www.example.com")); |
371 | 412 | |
372 | 413 | $c = new MockCookie( "name", "value", |
| 414 | + array( |
| 415 | + "domain" => "www.example.com", |
| 416 | + "path" => "/path/", |
| 417 | + ) ); |
| 418 | + $this->assertFalse($c->canServeDomain("example.com")); |
| 419 | + $this->assertFalse($c->canServeDomain("www.example.net")); |
| 420 | + $this->assertTrue($c->canServeDomain("www.example.com")); |
| 421 | + |
| 422 | + $c = new MockCookie( "name", "value", |
373 | 423 | array( |
374 | 424 | "domain" => ".example.com", |
375 | 425 | "path" => "/path/", |
Index: trunk/phase3/includes/HttpFunctions.php |
— | — | @@ -453,23 +453,69 @@ |
454 | 454 | $this->path = "/"; |
455 | 455 | } |
456 | 456 | if( isset( $attr['domain'] ) ) { |
457 | | - $this->domain = self::parseCookieDomain( $attr['domain'] ); |
| 457 | + if( self::validateCookieDomain( $attr['domain'] ) ) { |
| 458 | + $this->domain = $attr['domain']; |
| 459 | + } |
458 | 460 | } else { |
459 | 461 | throw new MWException("You must specify a domain."); |
460 | 462 | } |
461 | 463 | } |
462 | 464 | |
463 | | - public static function parseCookieDomain( $domain ) { |
464 | | - /* If domain is given, it has to contain at least two dots */ |
465 | | - if ( strrpos( $domain, '.' ) === false |
466 | | - || strrpos( $domain, '.' ) === strpos( $domain, '.' ) ) { |
467 | | - return; |
| 465 | + /** |
| 466 | + * Return the true if the cookie is valid is valid. Otherwise, |
| 467 | + * false. The uses a method similar to IE cookie security |
| 468 | + * described here: |
| 469 | + * http://kuza55.blogspot.com/2008/02/understanding-cookie-security.html |
| 470 | + * A better method might be to use a blacklist like |
| 471 | + * http://publicsuffix.org/ |
| 472 | + * |
| 473 | + * @param $domain string the domain to validate |
| 474 | + * @param $originDomain string (optional) the domain the cookie originates from |
| 475 | + * @return bool |
| 476 | + */ |
| 477 | + public static function validateCookieDomain( $domain, $originDomain = null) { |
| 478 | + // Don't allow a trailing dot |
| 479 | + if( substr( $domain, -1 ) == "." ) return false; |
| 480 | + |
| 481 | + $dc = explode(".", $domain); |
| 482 | + |
| 483 | + // Don't allow cookies for "localhost", "ls" or other dot-less hosts |
| 484 | + if( count($dc) < 2 ) return false; |
| 485 | + |
| 486 | + // Only allow full, valid IP addresses |
| 487 | + if( preg_match( '/^[0-9.]+$/', $domain ) ) { |
| 488 | + if( count( $dc ) != 4 ) return false; |
| 489 | + |
| 490 | + if( ip2long( $domain ) === false ) return false; |
| 491 | + |
| 492 | + if( $originDomain == null || $originDomain == $domain ) return true; |
| 493 | + |
468 | 494 | } |
469 | | - if ( substr( $domain, 0, 1 ) === '.' ) { |
470 | | - $domain = substr( $domain, 1 ); |
| 495 | + |
| 496 | + // Don't allow cookies for "co.uk" or "gov.uk", etc, but allow "supermarket.uk" |
| 497 | + if( strrpos( $domain, "." ) - strlen( $domain ) == -3 ) { |
| 498 | + if( (count($dc) == 2 && strlen( $dc[0] ) <= 2 ) |
| 499 | + || (count($dc) == 3 && strlen( $dc[0] ) == "" && strlen( $dc[1] ) <= 2 ) ) { |
| 500 | + return false; |
| 501 | + } |
| 502 | + if( (count($dc) == 2 || (count($dc) == 3 && $dc[0] == "") ) |
| 503 | + && preg_match( '/(com|net|org|gov|edu)\...$/', $domain) ) { |
| 504 | + return false; |
| 505 | + } |
471 | 506 | } |
472 | 507 | |
473 | | - return $domain; |
| 508 | + if( $originDomain != null ) { |
| 509 | + if( substr( $domain, 0, 1 ) != "." && $domain != $originDomain ) { |
| 510 | + return false; |
| 511 | + } |
| 512 | + if( substr( $domain, 0, 1 ) == "." |
| 513 | + && substr_compare( $originDomain, $domain, -strlen( $domain ), |
| 514 | + strlen( $domain ), TRUE ) != 0 ) { |
| 515 | + return false; |
| 516 | + } |
| 517 | + } |
| 518 | + |
| 519 | + return true; |
474 | 520 | } |
475 | 521 | |
476 | 522 | /** |
— | — | @@ -491,8 +537,10 @@ |
492 | 538 | } |
493 | 539 | |
494 | 540 | protected function canServeDomain( $domain ) { |
495 | | - if( $this->domain && substr_compare( $domain, $this->domain, -strlen( $this->domain ), |
496 | | - strlen( $this->domain ), TRUE ) == 0 ) { |
| 541 | + if( $domain == $this->domain |
| 542 | + || ( substr( $this->domain, 0, 1) == "." |
| 543 | + && substr_compare( $domain, $this->domain, -strlen( $this->domain ), |
| 544 | + strlen( $this->domain ), TRUE ) == 0 ) ) { |
497 | 545 | return true; |
498 | 546 | } |
499 | 547 | return false; |
— | — | @@ -572,13 +620,10 @@ |
573 | 621 | |
574 | 622 | if( !isset( $attr['domain'] ) ) { |
575 | 623 | $attr['domain'] = $domain; |
576 | | - } else { |
577 | | - if ( strlen( $attr['domain'] ) < strlen( $domain ) |
578 | | - && substr_compare( $domain, $attr['domain'], -strlen( $attr['domain'] ), |
579 | | - strlen( $attr['domain'] ), TRUE ) != 0 ) { |
580 | | - return; /* silently reject a bad cookie */ |
581 | | - } |
| 624 | + } elseif ( !Cookie::validateCookieDomain( $attr['domain'], $domain ) ) { |
| 625 | + return null; |
582 | 626 | } |
| 627 | + |
583 | 628 | $this->setCookie( $name, $value, $attr ); |
584 | 629 | } |
585 | 630 | } |