Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Implement cookie handling.
Client now manages cookies from responses and appends them to future
requests.
  • Loading branch information
markstory committed Jan 4, 2013
1 parent 521896f commit 29ef4f3
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 8 deletions.
88 changes: 80 additions & 8 deletions lib/Cake/Network/Http/Client.php
Expand Up @@ -39,6 +39,14 @@
* - delete()
* - patch()
*
* ### Cookie management
*
* Client will maintain cookies from the responses done with
* a client instance. These cookies will be automatically added
* to future requests to matching hosts. Cookies will respect the
* `Expires` and `Domain` attributes. You can get the list of
* currently stored cookies using the cookies() method.
*
* ### Sending request bodies
*
* By default any POST/PUT/PATCH/DELETE request with $data will
Expand Down Expand Up @@ -67,12 +75,9 @@
* ### Using proxies
*
* By using the `proxy` key you can set authentication credentials for
* a proxy if you need to use one.. The type sub option can be used to
* a proxy if you need to use one.. The type sub option can be used to
* specify which authentication strategy you want to use.
* CakePHP comes with a few built-in strategies:
*
* - Basic
* - Digest
* CakePHP comes with built-in support for basic authentication.
*
*/
class Client {
Expand All @@ -93,6 +98,16 @@ class Client {
'redirect' => false,
];

/**
* List of cookies from responses made with this client.
*
* Cookies are indexed by the cookie's domain or
* request host name.
*
* @var array
*/
protected $_cookies = [];

/**
* Adapter for sending requests. Defaults to
* Cake\Network\Http\Stream
Expand Down Expand Up @@ -153,6 +168,15 @@ public function config($config = null) {
return $this;
}

/**
* Get the cookies stored in the Client.
*
* @return array
*/
public function cookies() {
return $this->_cookies;
}

/**
* Do a GET request.
*
Expand Down Expand Up @@ -275,13 +299,60 @@ protected function _mergeOptions($options) {
* @return Cake\Network\Http\Response
*/
public function send(Request $request, $options = []) {
// TODO possibly implment support for
// holding onto cookies so subsequent requests
// can share cookies.
$responses = $this->_adapter->send($request, $options);
$host = parse_url($request->url(), PHP_URL_HOST);
foreach ($responses as $response) {
$this->_storeCookies($response, $host);
}
return array_pop($responses);
}

/**
* Store cookies in a response to be used in future requests.
*
* Non-expired cookies will be stored for use in future requests
* made with the same Client instance. Cookies are not saved
* between instances.
*
* @param Response $response The response to read cookies from
* @param string $host The request host, used for getting host names
* in case the cookies didn't set a domain.
* @return void
*/
protected function _storeCookies(Response $response, $host) {
$cookies = $response->cookies();
foreach ($cookies as $name => $cookie) {
$expires = isset($cookie['expires']) ? $cookie['expires'] : false;
$domain = isset($cookie['domain']) ? $cookie['domain'] : $host;
$domain = trim($domain, '.');
if ($expires) {
$expires = \DateTime::createFromFormat('D, j-M-Y H:i:s e', $expires);
}
if ($expires && $expires->getTimestamp() <= time()) {
continue;
}
if (empty($this->_cookies[$domain])) {
$this->_cookies[$domain] = [];
}
$this->_cookies[$domain][$name] = $cookie['value'];
}
}

/**
* Adds cookies stored in the client to the request.
*
* Uses the request's host to find matching cookies.
*
* @param Request $request
* @return void
*/
protected function _addCookies(Request $request) {
$host = parse_url($request->url(), PHP_URL_HOST);
if (isset($this->_cookies[$host])) {
$request->cookie($this->_cookies[$host]);
}
}

/**
* Generate a URL based on the scoped client options.
*
Expand Down Expand Up @@ -339,6 +410,7 @@ protected function _createRequest($method, $url, $data, $options) {
if (isset($options['headers'])) {
$request->header($options['headers']);
}
$this->_addCookies($request);
if (isset($options['cookies'])) {
$request->cookie($options['cookies']);
}
Expand Down
111 changes: 111 additions & 0 deletions lib/Cake/Test/TestCase/Network/Http/ClientTest.php
Expand Up @@ -404,4 +404,115 @@ public function testExceptionOnUnknownType() {
$http->post('/projects/add', 'it works', ['type' => 'invalid']);
}

/**
* Test that Client keeps cookies from responses and holds onto non expired cookies.
*
* @return void
*/
public function testCookiesExpiring() {
$mock = $this->getMock(
'Cake\Network\Http\Adapter\Stream',
['send']
);

$firstHeaders = [
'HTTP/1.0 200 Ok',
'Set-Cookie: first=1',
'Set-Cookie: expiring=now; Expires=Wed, 09-Jun-1999 10:18:14 GMT'
];
$firstResponse = new Response($firstHeaders, '');

$secondHeaders = [
'HTTP/1.0 200 Ok',
'Set-Cookie: second=2',
];
$secondResponse = new Response($secondHeaders, '');

$mock->expects($this->at(0))
->method('send')
->will($this->returnValue([$firstResponse]));

$mock->expects($this->at(1))
->method('send')
->with($this->attributeEqualTo(
'_cookies',
['first' => '1']
))
->will($this->returnValue([$secondResponse]));

$http = new Client([
'host' => 'cakephp.org',
'adapter' => $mock
]);

$http->get('/projects');
$http->get('/projects/two');

$result = $http->cookies();
$expected = [
'cakephp.org' => [
'first' => 1,
'second' => 2
]
];
$this->assertEquals($expected, $result);
}

/**
* Test cookies with domain set.
*
* @return void
*/
public function testCookiesWithDomain() {
$firstHeaders = [
'HTTP/1.0 200 Ok',
'Set-Cookie: first=1; Domain=.cakephp.org',
'Set-Cookie: second=2',
];
$firstResponse = new Response($firstHeaders, '');

$secondHeaders = [
'HTTP/1.0 200 Ok',
'Set-Cookie: third=3',
];
$secondResponse = new Response($secondHeaders, '');

$mock = $this->getMock(
'Cake\Network\Http\Adapter\Stream',
['send']
);

$mock->expects($this->at(0))
->method('send')
->will($this->returnValue([$firstResponse]));

$mock->expects($this->at(1))
->method('send')
->with($this->attributeEqualTo(
'_cookies',
['first' => '1']
))
->will($this->returnValue([$secondResponse]));

$http = new Client([
'host' => 'test.cakephp.org',
'adapter' => $mock
]);

$http->get('/projects');
$http->get('http://cakephp.org/versions');

$result = $http->cookies();
$expected = [
'cakephp.org' => [
'first' => '1',
'third' => '3',
],
'test.cakephp.org' => [
'second' => '2',
]
];
$this->assertEquals($expected, $result);
}

}

0 comments on commit 29ef4f3

Please sign in to comment.