diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php index e0d528f7f5bb..568f5a28e040 100644 --- a/system/HTTP/Response.php +++ b/system/HTTP/Response.php @@ -870,7 +870,7 @@ public function hasCookie(string $name, $value = null, string $prefix = '') foreach ($this->cookies as $cookie) { - if ($cookie['name'] !== $prefix . $name) + if ($cookie['name'] !== $name) { continue; } @@ -894,8 +894,14 @@ public function hasCookie(string $name, $value = null, string $prefix = '') * * @return mixed */ - public function getCookie(string $name, string $prefix = '') + public function getCookie(string $name = null, string $prefix = '') { + // if no name given, return them all + if (empty($name)) + { + return $this->cookies; + } + if ($prefix === '' && $this->cookiePrefix !== '') { $prefix = $this->cookiePrefix; @@ -910,6 +916,7 @@ public function getCookie(string $name, string $prefix = '') return $cookie; } } + return null; } /** @@ -920,8 +927,13 @@ public function getCookie(string $name, string $prefix = '') * @param string $path * @param string $prefix */ - public function deleteCookie($name, string $domain = '', string $path = '/', string $prefix = '') + public function deleteCookie($name = '', string $domain = '', string $path = '/', string $prefix = '') { + if (empty($name)) + { + return; + } + if ($prefix === '' && $this->cookiePrefix !== '') { $prefix = $this->cookiePrefix; @@ -933,6 +945,14 @@ public function deleteCookie($name, string $domain = '', string $path = '/', str { if ($cookie['name'] === $name) { + if (! empty($domain) && $cookie['domain'] !== $domain) + { + continue; + } + if (! empty($path) && $cookie['path'] !== $path) + { + continue; + } $cookie['value'] = ''; $cookie['expires'] = ''; diff --git a/tests/system/HTTP/ResponseCookieTest.php b/tests/system/HTTP/ResponseCookieTest.php new file mode 100644 index 000000000000..f835cdab5748 --- /dev/null +++ b/tests/system/HTTP/ResponseCookieTest.php @@ -0,0 +1,240 @@ +server = $_SERVER; + } + + public function tearDown() + { + $_SERVER = $this->server; + } + + public function testCookiePrefixed() + { + $config = new App(); + $config->cookiePrefix = 'mine'; + $response = new Response($config); + $response->setCookie('foo', 'bar'); + + $this->assertTrue(is_array($response->getCookie('foo'))); + $this->assertTrue($response->hasCookie('foo')); + $this->assertTrue($response->hasCookie('foo', 'bar')); + $this->assertTrue($response->hasCookie('foo', 'bar', 'mine')); + $this->assertTrue($response->hasCookie('foo', null, 'mine')); + $this->assertFalse($response->hasCookie('foo', null, 'yours')); + } + + public function testCookiesAll() + { + $config = new App(); + $response = new Response($config); + $response->setCookie('foo', 'bar'); + $response->setCookie('bee', 'bop'); + + $allCookies = $response->getCookie(); + $this->assertEquals(2, count($allCookies)); + $this->assertTrue($response->hasCookie('foo')); + $this->assertTrue($response->hasCookie('bee')); + } + + public function testCookieGet() + { + $config = new App(); + $response = new Response($config); + $response->setCookie('foo', 'bar'); + $response->setCookie('bee', 'bop'); + + $allCookies = $response->getCookie(); + $this->assertEquals(2, count($allCookies)); + $this->assertEquals(null, $response->getCookie('bogus')); + } + + public function testCookieDomain() + { + $config = new App(); + $response = new Response($config); + + $response->setCookie('foo', 'bar'); + $cookie = $response->getCookie('foo'); + $this->assertEquals('', $cookie['domain']); + + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'domain' => 'somewhere.com']); + $cookie = $response->getCookie('bee'); + $this->assertEquals('somewhere.com', $cookie['domain']); + + $config->cookieDomain = 'mine.com'; + $response = new Response($config); + $response->setCookie('alu', 'la'); + $cookie = $response->getCookie('alu'); + $this->assertEquals('mine.com', $cookie['domain']); + } + + public function testCookiePath() + { + $config = new App(); + $response = new Response($config); + + $response->setCookie('foo', 'bar'); + $cookie = $response->getCookie('foo'); + $this->assertEquals('/', $cookie['path']); + + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'path' => '/tmp/here']); + $cookie = $response->getCookie('bee'); + $this->assertEquals('/tmp/here', $cookie['path']); + + $config->cookiePath = '/tmp/there'; + $response = new Response($config); + $response->setCookie('alu', 'la'); + $cookie = $response->getCookie('alu'); + $this->assertEquals('/tmp/there', $cookie['path']); + } + + public function testCookieSecure() + { + $config = new App(); + $response = new Response($config); + + $response->setCookie('foo', 'bar'); + $cookie = $response->getCookie('foo'); + $this->assertEquals(false, $cookie['secure']); + + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'secure' => true]); + $cookie = $response->getCookie('bee'); + $this->assertEquals(true, $cookie['secure']); + + $config->cookieSecure = true; + $response = new Response($config); + $response->setCookie('alu', 'la'); + $cookie = $response->getCookie('alu'); + $this->assertEquals(true, $cookie['secure']); + } + + public function testCookieHTTPOnly() + { + $config = new App(); + $response = new Response($config); + + $response->setCookie('foo', 'bar'); + $cookie = $response->getCookie('foo'); + $this->assertEquals(false, $cookie['httponly']); + + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'httponly' => true]); + $cookie = $response->getCookie('bee'); + $this->assertEquals(true, $cookie['httponly']); + + $config->cookieHTTPOnly = true; + $response = new Response($config); + $response->setCookie('alu', 'la'); + $cookie = $response->getCookie('alu'); + $this->assertEquals(true, $cookie['httponly']); + } + + public function testCookieExpiry() + { + $config = new App(); + $response = new Response($config); + + $response->setCookie('foo', 'bar'); + $cookie = $response->getCookie('foo'); + $this->assertTrue($cookie['expires'] < time()); + + $response = new Response($config); + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => 1000]); + $cookie = $response->getCookie('bee'); + $this->assertFalse($cookie['expires'] < time()); + + $response = new Response($config); + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => 'oops']); + $cookie = $response->getCookie('bee'); + $this->assertTrue($cookie['expires'] < time()); + + $response = new Response($config); + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => -1000]); + $cookie = $response->getCookie('bee'); + $this->assertEquals(0, $cookie['expires']); + } + + public function testCookieDelete() + { + $config = new App(); + $response = new Response($config); + + // foo is already expired, bee will stick around + $response->setCookie('foo', 'bar'); + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => 1000]); + $cookie = $response->getCookie('bee'); + $this->assertFalse($cookie['expires'] <= time()); + + // delete cookie manually + $response = new Response($config); + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => '']); + $cookie = $response->getCookie('bee'); + $this->assertTrue($cookie['expires'] <= time(), $cookie['expires'] . ' should be less than ' . time()); + + // delete with no effect + $response = new Response($config); + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => 1000]); + $response->deleteCookie(); + $cookie = $response->getCookie('bee'); + $this->assertFalse($cookie['expires'] < time()); + + // delete cookie for real + $response = new Response($config); + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => 1000]); + $response->deleteCookie('bee'); + $cookie = $response->getCookie('bee'); + $this->assertTrue($cookie['expires'] <= time(), $cookie['expires'] . ' should be less than ' . time()); + + // delete cookie for real, with prefix + $config->cookiePrefix = 'mine'; + $response = new Response($config); + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => 1000]); + $response->deleteCookie('bee'); + $cookie = $response->getCookie('bee'); + $this->assertTrue($cookie['expires'] <= time(), $cookie['expires'] . ' should be less than ' . time()); + + // delete cookie with wrong prefix? + $config->cookiePrefix = 'mine'; + $response = new Response($config); + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => 1000]); + $response->deleteCookie('bee', '', '', 'wrong'); + $cookie = $response->getCookie('bee'); + $this->assertFalse($cookie['expires'] <= time(), $cookie['expires'] . ' should be less than ' . time()); + $response->deleteCookie('bee', '', '', 'mine'); + $cookie = $response->getCookie('bee'); + $this->assertTrue($cookie['expires'] <= time(), $cookie['expires'] . ' should be less than ' . time()); + + // delete cookie with wrong domain? + $config->cookieDomain = '.mine.com'; + $response = new Response($config); + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => 1000]); + $response->deleteCookie('bee', 'wrong', '', ''); + $cookie = $response->getCookie('bee'); + $this->assertFalse($cookie['expires'] <= time(), $cookie['expires'] . ' should be less than ' . time()); + $response->deleteCookie('bee', '.mine.com', '', ''); + $cookie = $response->getCookie('bee'); + $this->assertTrue($cookie['expires'] <= time(), $cookie['expires'] . ' should be less than ' . time()); + + // delete cookie with wrong path? + $config->cookiePath = '/whoknowswhere'; + $response = new Response($config); + $response->setCookie(['name' => 'bee', 'value' => 'bop', 'expire' => 1000]); + $response->deleteCookie('bee', '', '/wrong', ''); + $cookie = $response->getCookie('bee'); + $this->assertFalse($cookie['expires'] <= time(), $cookie['expires'] . ' should be less than ' . time()); + $response->deleteCookie('bee', '', '/whoknowswhere', ''); + $cookie = $response->getCookie('bee'); + $this->assertTrue($cookie['expires'] <= time(), $cookie['expires'] . ' should be less than ' . time()); + } + +} diff --git a/tests/system/HTTP/ResponseTest.php b/tests/system/HTTP/ResponseTest.php index c865068998ef..84f5e501529f 100644 --- a/tests/system/HTTP/ResponseTest.php +++ b/tests/system/HTTP/ResponseTest.php @@ -294,7 +294,8 @@ public function testSetCookieSuccessOnPrefix() $response = new Response(new App()); $response->setCookie('foo', 'bar', '', '', '', 'ack'); - $this->assertFalse($response->hasCookie('foo', null, 'ack')); + $this->assertTrue($response->hasCookie('foo', null, 'ack')); + $this->assertFalse($response->hasCookie('foo', null, 'nak')); } //-------------------------------------------------------------------- diff --git a/user_guide_src/source/outgoing/response.rst b/user_guide_src/source/outgoing/response.rst index 8d81d24256d0..015edadea3fd 100644 --- a/user_guide_src/source/outgoing/response.rst +++ b/user_guide_src/source/outgoing/response.rst @@ -379,7 +379,7 @@ The methods provided by the parent class that are available are: **Array Method** - Using this method, an associative array is passed to the first + Using this method, an associative array is passed as the first parameter:: $cookie = [ @@ -389,7 +389,8 @@ The methods provided by the parent class that are available are: 'domain' => '.some-domain.com', 'path' => '/', 'prefix' => 'myprefix_', - 'secure' => TRUE + 'secure' => TRUE, + 'httponly' => FALSE ]; $response->setCookie($cookie); @@ -421,4 +422,70 @@ The methods provided by the parent class that are available are: If you prefer, you can set the cookie by passing data using individual parameters:: - $response->setCookie($name, $value, $expire, $domain, $path, $prefix, $secure); + $response->setCookie($name, $value, $expire, $domain, $path, $prefix, $secure, $httponly); + + .. php:method:: deleteCookie($name = ''[, $domain = ''[, $path = '/'[, $prefix = '']]]) + + :param mixed $name: Cookie name or an array of parameters + :param string $domain: Cookie domain + :param string $path: Cookie path + :param string $prefix: Cookie name prefix + :rtype: void + + Delete an existing cookie by setting its expiry to blank. + + **Notes** + + Only the name is required. + + The prefix is only needed if you need to avoid name collisions with + other identically named cookies for your server. + + Provide a prefix if cookies should only be deleted for that subset. + Provide a domain name if cookies should only be deleted for that domain. + Provide a path name if cookies should only be deleted for that path. + + If any of the optional parameters are empty, then the same-named + cookie will be deleted across all that apply. + + Example:: + + $response->deleteCookie($name); + + .. php:method:: hasCookie($name = ''[, $value = null[, $prefix = '']]) + + :param mixed $name: Cookie name or an array of parameters + :param string $value: cookie value + :param string $prefix: Cookie name prefix + :rtype: boolean + + Checks to see if the Response has a specified cookie or not. + + **Notes** + + Only the name is required. If a prefix is specified, it will be + pre-pended to the cookie name. + + If no value is given, the method just checks for the existence + of the named cookie. If a value is given, then the method checks + that the cookie exists, and that it has the prescribed value. + + Example:: + + if ($response->hasCookie($name)) ... + + .. php:method:: getCookie($name = ''[, $prefix = '']) + + :param mixed $name: Cookie name + :param string $prefix: Cookie name prefix + :rtype: boolean + + Returns the named cookie, if found, or null. + + If no name is given, returns the array of cookies. + + Each cookie is returned as an associative array. + + Example:: + + $cookie = $response->getCookie($name);