Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract cookie code into a separate class.
Having a separate class for cookie jar features makes it much easier to write tests and verify that cookies are being handled correctly.
- Loading branch information
Showing
2 changed files
with
310 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
<?php | ||
/** | ||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org) | ||
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org) | ||
* | ||
* Licensed under The MIT License | ||
* Redistributions of files must retain the above copyright notice. | ||
* | ||
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org) | ||
* @link http://cakephp.org CakePHP(tm) Project | ||
* @since CakePHP(tm) v 3.0.0 | ||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php) | ||
*/ | ||
namespace Cake\Network\Http; | ||
|
||
use Cake\Network\Http\Request; | ||
use Cake\Network\Http\Response; | ||
|
||
/** | ||
* Container class for cookies used in Http\Client. | ||
* | ||
* Provides cookie jar like features for storing cookies between | ||
* requests, as well as appending cookies to new requests. | ||
*/ | ||
class Cookies { | ||
|
||
/** | ||
* The cookies stored in this jar. | ||
* | ||
* @var array | ||
*/ | ||
protected $cookies = []; | ||
|
||
/** | ||
* Store the cookies from a response. | ||
* | ||
* Store the cookies that haven't expired. If a cookie has been expired | ||
* and is currently stored, it will be removed. | ||
* | ||
* @param Response $response The response to read cookies from | ||
* @param string $url The request URL used for default host/path values. | ||
* @return void | ||
*/ | ||
public function store(Response $response, $url) { | ||
$host = parse_url($url, PHP_URL_HOST); | ||
$path = parse_url($url, PHP_URL_PATH); | ||
$path = $path ?: '/'; | ||
|
||
$cookies = $response->cookies(); | ||
foreach ($cookies as $name => $cookie) { | ||
$expires = isset($cookie['expires']) ? $cookie['expires'] : false; | ||
if ($expires) { | ||
$expires = \DateTime::createFromFormat('D, j-M-Y H:i:s e', $expires); | ||
} | ||
if ($expires && $expires->getTimestamp() <= time()) { | ||
continue; | ||
} | ||
if (empty($cookie['domain'])) { | ||
$cookie['domain'] = $host; | ||
} | ||
if (empty($cookie['path'])) { | ||
$cookie['path'] = $path; | ||
} | ||
$this->_cookies[] = $cookie; | ||
} | ||
} | ||
|
||
/** | ||
* Get stored cookies for a url. | ||
* | ||
* Finds matching stored cookies and returns a simple array | ||
* of name => value | ||
* | ||
* @param string $url The url to find cookies for. | ||
* @return arraty | ||
*/ | ||
public function get($url) { | ||
$path = parse_url($url, PHP_URL_PATH) ?: '/'; | ||
$host = parse_url($url, PHP_URL_HOST); | ||
$scheme = parse_url($url, PHP_URL_SCHEME); | ||
|
||
$out = []; | ||
foreach ($this->_cookies as $cookie) { | ||
if ($scheme === 'http' && !empty($cookie['secure'])) { | ||
continue; | ||
} | ||
if (strpos($path, $cookie['path']) !== 0) { | ||
continue; | ||
} | ||
$leadingDot = $cookie['domain'][0] === '.'; | ||
if (!$leadingDot && $host !== $cookie['domain']) { | ||
continue; | ||
} | ||
if ($leadingDot) { | ||
$pattern = '/' . preg_quote(substr($cookie['domain'], 1), '/') . '$/'; | ||
if (!preg_match($pattern, $host)) { | ||
continue; | ||
} | ||
} | ||
$out[$cookie['name']] = $cookie['value']; | ||
} | ||
return $out; | ||
} | ||
|
||
/** | ||
* Get all the stored cookies. | ||
* | ||
* @return array | ||
*/ | ||
public function getAll() { | ||
return $this->_cookies; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
<?php | ||
/** | ||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org) | ||
* Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org) | ||
* | ||
* Licensed under The MIT License | ||
* Redistributions of files must retain the above copyright notice. | ||
* | ||
* @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org) | ||
* @link http://cakephp.org CakePHP(tm) Project | ||
* @since CakePHP(tm) v 3.0.0 | ||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php) | ||
*/ | ||
namespace Cake\Test\TestCase\Network\Http; | ||
|
||
use Cake\Network\Http\Response; | ||
use Cake\Network\Http\Cookies; | ||
use Cake\TestSuite\TestCase; | ||
|
||
/** | ||
* HTTP cookies test. | ||
*/ | ||
class CookiesTest extends TestCase { | ||
|
||
/** | ||
* setup | ||
* | ||
* @return void | ||
*/ | ||
public function setUp() { | ||
parent::setUp(); | ||
$this->cookies = new Cookies(); | ||
} | ||
|
||
/** | ||
* test store | ||
* | ||
* @return void | ||
*/ | ||
public function testStore() { | ||
$headers = [ | ||
'HTTP/1.0 200 Ok', | ||
'Set-Cookie: first=1', | ||
'Set-Cookie: second=2; Path=/; Domain=.foo.example.com', | ||
'Set-Cookie: expiring=now; Expires=Wed, 09-Jun-1999 10:18:14 GMT', | ||
]; | ||
$response = new Response($headers, ''); | ||
$result = $this->cookies->store($response, 'http://example.com/some/path'); | ||
$this->assertNull($result); | ||
|
||
$result = $this->cookies->getAll(); | ||
$this->assertCount(2, $result); | ||
$expected = [ | ||
[ | ||
'name' => 'first', | ||
'value' => '1', | ||
'path' => '/some/path', | ||
'domain' => 'example.com' | ||
], | ||
[ | ||
'name' => 'second', | ||
'value' => '2', | ||
'path' => '/', | ||
'domain' => '.foo.example.com' | ||
], | ||
]; | ||
$this->assertEquals($expected, $result); | ||
} | ||
|
||
/** | ||
* test store secure. | ||
* | ||
* @return void | ||
*/ | ||
public function testStoreSecure() { | ||
$headers = [ | ||
'HTTP/1.0 200 Ok', | ||
'Set-Cookie: first=1', | ||
'Set-Cookie: second=2; Secure; HttpOnly', | ||
]; | ||
$response = new Response($headers, ''); | ||
$result = $this->cookies->store($response, 'http://example.com/some/path'); | ||
$this->assertNull($result); | ||
|
||
$result = $this->cookies->getAll(); | ||
$this->assertCount(2, $result); | ||
$expected = [ | ||
[ | ||
'name' => 'first', | ||
'value' => '1', | ||
'path' => '/some/path', | ||
'domain' => 'example.com' | ||
], | ||
[ | ||
'name' => 'second', | ||
'value' => '2', | ||
'path' => '/some/path', | ||
'domain' => 'example.com', | ||
'secure' => true, | ||
'httponly' => true, | ||
], | ||
]; | ||
$this->assertEquals($expected, $result); | ||
} | ||
|
||
/** | ||
* test getting cookies with secure flags | ||
* | ||
* @return void | ||
*/ | ||
public function testGetMatchingSecure() { | ||
$headers = [ | ||
'HTTP/1.0 200 Ok', | ||
'Set-Cookie: first=1', | ||
'Set-Cookie: second=2; Secure; HttpOnly', | ||
]; | ||
$response = new Response($headers, ''); | ||
$this->cookies->store($response, 'https://example.com/'); | ||
|
||
$result = $this->cookies->get('https://example.com/test'); | ||
$expected = ['first' => '1', 'second' => '2']; | ||
$this->assertEquals($expected, $result); | ||
|
||
$result = $this->cookies->get('http://example.com/test'); | ||
$expected = ['first' => '1']; | ||
$this->assertEquals($expected, $result); | ||
} | ||
|
||
/** | ||
* test getting cookies with secure flags | ||
* | ||
* @return void | ||
*/ | ||
public function testGetMatchingPath() { | ||
$headers = [ | ||
'HTTP/1.0 200 Ok', | ||
'Set-Cookie: first=1; Path=/foo', | ||
'Set-Cookie: second=2; Path=/', | ||
]; | ||
$response = new Response($headers, ''); | ||
$this->cookies->store($response, 'http://example.com/foo'); | ||
|
||
$result = $this->cookies->get('http://example.com/foo'); | ||
$expected = ['first' => '1', 'second' => 2]; | ||
$this->assertEquals($expected, $result); | ||
|
||
$result = $this->cookies->get('http://example.com/'); | ||
$expected = ['second' => 2]; | ||
$this->assertEquals($expected, $result); | ||
|
||
$result = $this->cookies->get('http://example.com/test'); | ||
$expected = ['second' => 2]; | ||
$this->assertEquals($expected, $result); | ||
} | ||
|
||
/** | ||
* Test getting cookies matching on paths exactly | ||
*/ | ||
public function testGetMatchingDomainExact() { | ||
$headers = [ | ||
'HTTP/1.0 200 Ok', | ||
'Set-Cookie: first=1; Domain=.example.com', | ||
'Set-Cookie: second=2;', | ||
]; | ||
$response = new Response($headers, ''); | ||
$this->cookies->store($response, 'http://foo.example.com/'); | ||
|
||
$result = $this->cookies->get('http://example.com'); | ||
$expected = ['first' => 1]; | ||
$this->assertEquals($expected, $result); | ||
|
||
$result = $this->cookies->get('http://foo.example.com'); | ||
$expected = ['first' => 1, 'second' => '2']; | ||
$this->assertEquals($expected, $result); | ||
|
||
$result = $this->cookies->get('http://bar.foo.example.com'); | ||
$expected = ['first' => 1]; | ||
$this->assertEquals($expected, $result); | ||
|
||
$result = $this->cookies->get('http://api.example.com'); | ||
$expected = ['first' => 1]; | ||
$this->assertEquals($expected, $result); | ||
|
||
$result = $this->cookies->get('http://google.com'); | ||
$expected = []; | ||
$this->assertEquals($expected, $result); | ||
} | ||
|
||
/** | ||
* Test getting cookies matching on paths | ||
*/ | ||
public function testGetMatchingDomain() { | ||
} | ||
|
||
|
||
} |