Skip to content

Commit

Permalink
Extract cookie code into a separate class.
Browse files Browse the repository at this point in the history
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
markstory committed Jan 14, 2013
1 parent 30c4c54 commit a0eebe9
Show file tree
Hide file tree
Showing 2 changed files with 310 additions and 0 deletions.
114 changes: 114 additions & 0 deletions lib/Cake/Network/Http/Cookies.php
@@ -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;
}

}
196 changes: 196 additions & 0 deletions lib/Cake/Test/TestCase/Network/Http/CookiesTest.php
@@ -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() {
}


}

0 comments on commit a0eebe9

Please sign in to comment.