From b0d8b378504e24b7c9c8a439441589347f88e0e9 Mon Sep 17 00:00:00 2001 From: mark_story Date: Wed, 26 Dec 2012 21:54:04 -0500 Subject: [PATCH] Add support for cookie parsing. Also add support for headers with multiple values, like Set-Cookie. --- lib/Cake/Network/Http/Response.php | 90 +++++++++++++++++-- .../TestCase/Network/Http/ResponseTest.php | 40 ++++++++- 2 files changed, 121 insertions(+), 9 deletions(-) diff --git a/lib/Cake/Network/Http/Response.php b/lib/Cake/Network/Http/Response.php index 8fc42525f33..a95d5278ee3 100644 --- a/lib/Cake/Network/Http/Response.php +++ b/lib/Cake/Network/Http/Response.php @@ -44,6 +44,13 @@ class Response { */ protected $_headers; +/** + * The array of cookies in the response. + * + * @var array + */ + protected $_cookies; + /** * The response body * @@ -77,13 +84,57 @@ protected function _parseHeaders($headers) { $this->_code = $matches[1]; continue; } - if (is_int($key)) { - list($name, $value) = explode(':', $value, 2); - $name = $this->_normalizeHeader($name); - $this->_headers[trim($name)] = trim($value); + list($name, $value) = explode(':', $value, 2); + $value = trim($value); + $name = $this->_normalizeHeader($name); + if ($name === 'Set-Cookie') { + $this->_parseCookie($value); + } + if (isset($this->_headers[$name])) { + $this->_headers[$name] = (array)$this->_headers[$name]; + $this->_headers[$name][] = $value; + } else { + $this->_headers[$name] = $value; + } + } + } + +/** + * Parse a cookie header into data. + * + * @param string $value The cookie value to parse. + * @return void + */ + protected function _parseCookie($value) { + $nestedSemi = '";"'; + if (strpos($value, $nestedSemi) !== false) { + $value = str_replace($nestedSemi, "{__cookie_replace__}", $value); + $parts = explode(';', $value); + $parts = str_replace("{__cookie_replace__}", $nestedSemi, $parts); + } else { + $parts = preg_split('/\;[ \t]*/', $value); + } + + $name = false; + $cookie = []; + foreach ($parts as $i => $part) { + if (strpos($part, '=') !== false) { + list($key, $value) = explode('=', $part, 2); + } else { + $key = $part; + $value = true; + } + if ($i === 0) { + $name = $key; + $cookie['value'] = $value; continue; } + $key = strtolower($key); + if (!isset($cookie[$key])) { + $cookie[$key] = $value; + } } + $this->_cookies[$name] = $cookie; } /** @@ -93,7 +144,7 @@ protected function _parseHeaders($headers) { * @return string Normalized header name. */ protected function _normalizeHeader($name) { - $parts = explode('-', $name); + $parts = explode('-', trim($name)); $parts = array_map('strtolower', $parts); $parts = array_map('ucfirst', $parts); return implode('-', $parts); @@ -143,9 +194,12 @@ public function encoding() { /** * Read single/multiple header value(s) out. * - * @param string $name The name of the header you want. Leave + * @param string $name The name of the header you want. Leave * null to get all headers. - * @return null|string + * @return mixed Null when the header doesn't exist. An array + * will be returned when getting all headers or when getting + * a header that had multiple values set. Otherwise a string + * will be returned. */ public function header($name = null) { if ($name === null) { @@ -158,6 +212,28 @@ public function header($name = null) { return $this->_headers[$name]; } +/** + * Read single/multiple cookie values out. + * + * @param string $name The name of the cookie you want. Leave + * null to get all cookies. + * @param boolean $all Get all parts of the cookie. When false only + * the value will be returned. + * @return mixed + */ + public function cookie($name = null, $all = false) { + if ($name === null) { + return $this->_cookies; + } + if (!isset($this->_cookies[$name])) { + return null; + } + if ($all) { + return $this->_cookies[$name]; + } + return $this->_cookies[$name]['value']; + } + /** * Get the response body. * diff --git a/lib/Cake/Test/TestCase/Network/Http/ResponseTest.php b/lib/Cake/Test/TestCase/Network/Http/ResponseTest.php index c115064b06f..8787da08501 100644 --- a/lib/Cake/Test/TestCase/Network/Http/ResponseTest.php +++ b/lib/Cake/Test/TestCase/Network/Http/ResponseTest.php @@ -128,12 +128,48 @@ public function testIsRedirect() { $this->assertFalse($response->isRedirect()); } +/** + * Test parsing / getting cookies. + * + * @return void + */ public function testCookie() { - $this->markTestIncomplete(); + $headers = [ + 'HTTP/1.0 200 Ok', + 'Set-Cookie: test=value', + 'Set-Cookie: session=123abc', + 'Set-Cookie: expiring=soon; Expires=Wed, 09-Jun-2021 10:18:14 GMT; Path=/; HttpOnly; Secure', + ]; + $response = new Response($headers, ''); + $this->assertEquals('value', $response->cookie('test')); + $this->assertEquals('123abc', $response->cookie('session')); + $this->assertEquals('soon', $response->cookie('expiring')); + + $result = $response->cookie('expiring', true); + $this->assertTrue($result['httponly']); + $this->assertTrue($result['secure']); + $this->assertEquals( + 'Wed, 09-Jun-2021 10:18:14 GMT', + $result['expires'] + ); + $this->assertEquals('/', $result['path']); + + $result = $response->header('set-cookie'); + $this->assertCount(3, $result, 'Should be an array.'); } +/** + * Test statusCode() + * + * @return void + */ public function testStatusCode() { - $this->markTestIncomplete(); + $headers = [ + 'HTTP/1.0 404 Not Found', + 'Content-Type: text/html' + ]; + $response = new Response($headers, ''); + $this->assertEquals(404, $response->statusCode()); } public function testEncoding() {