merged branch francisbesset/httpfoundation_responseheaderbag (PR #1640)

Commits
-------

64e9263 Updated UPDATE.md
7cf891a Renamed variable returned and used self in place of static for constants
f91f4dd Added the possibility to set cookies with the same name for different domains and paths for Symfony\Component\HttpFoundation\ResponseHeaderBag
f08eeb4 Moved managing cookies of HeaderBag in ResponseHeaderBag

Discussion
----------

[HttpFoundation] Cookies management in ResponseHeaderBag

Fixed cookies management in `Symfony\Component\HttpFoundation\HeaderBag` and `Symfony\Component\HttpFoundation\ResponseHeaderBag`
This commit is contained in:
Fabien Potencier 2011-07-13 16:37:40 +02:00
commit 4004b4411e
7 changed files with 144 additions and 74 deletions

View File

@ -13,6 +13,19 @@ RC4 to RC5
`Symfony\Component\Security\Core\User\EntityUserProvider` to
`Symfony\Bridge\Doctrine\Security\User\EntityUserProvider`.
* Moved cookies management of HeaderBag in ResponseHeaderBag of the HttpFoundation.
To access a cookie in Request instance, please use Request::$cookies public attribute.
* It is not possible to know if a cookie is present or get a cookie in ResponseHeaderBag
because getCookie() and hasCookie() methods were removed.
* The method `ResponseHeaderBag::getCookie()` accepts an argument for the return format,
the possible values are `ResponseHeaderBag::COOKIES_FLAT` (default value) or `ResponseHeaderBag::COOKIES_ARRAY`
* ResponseHeaderBag::COOKIES_FLAT return a simple array
* array(0 => `a cookie instance`, 1 => `another cookie instance`)
* ResponseHeaderBag::COOKIES_ARRAY return a multidimensional array
* array(`the domain` => array(`the path` => array(`the cookie name` => `a cookie instance`)))
* Removed the guesser for the Choice constraint as the constraint only knows
about the valid keys, and not their values.

View File

@ -19,7 +19,6 @@ namespace Symfony\Component\HttpFoundation;
class HeaderBag
{
protected $headers;
protected $cookies;
protected $cacheControl;
/**
@ -30,7 +29,6 @@ class HeaderBag
public function __construct(array $headers = array())
{
$this->cacheControl = array();
$this->cookies = array();
$this->headers = array();
foreach ($headers as $key => $values) {
$this->set($key, $values);
@ -200,67 +198,6 @@ class HeaderBag
}
}
/**
* Sets a cookie.
*
* @param Cookie $cookie
* @return void
*/
public function setCookie(Cookie $cookie)
{
$this->cookies[$cookie->getName()] = $cookie;
}
/**
* Removes a cookie from the array, but does not unset it in the browser
*
* @param string $name
* @return void
*/
public function removeCookie($name)
{
unset($this->cookies[$name]);
}
/**
* Whether the array contains any cookie with this name
*
* @param string $name
* @return Boolean
*/
public function hasCookie($name)
{
return isset($this->cookies[$name]);
}
/**
* Returns a cookie
*
* @param string $name
*
* @throws \InvalidArgumentException When the cookie does not exist
*
* @return Cookie
*/
public function getCookie($name)
{
if (!$this->hasCookie($name)) {
throw new \InvalidArgumentException(sprintf('There is no cookie with name "%s".', $name));
}
return $this->cookies[$name];
}
/**
* Returns an array with all cookies
*
* @return array
*/
public function getCookies()
{
return $this->cookies;
}
/**
* Returns the HTTP header value converted to a date.
*

View File

@ -18,7 +18,11 @@ namespace Symfony\Component\HttpFoundation;
*/
class ResponseHeaderBag extends HeaderBag
{
const COOKIES_FLAT = 'flat';
const COOKIES_ARRAY = 'array';
protected $computedCacheControl = array();
protected $cookies = array();
/**
* Constructor.
@ -40,7 +44,7 @@ class ResponseHeaderBag extends HeaderBag
public function __toString()
{
$cookies = '';
foreach ($this->cookies as $cookie) {
foreach ($this->getCookies() as $cookie) {
$cookies .= 'Set-Cookie: '.$cookie."\r\n";
}
@ -102,6 +106,69 @@ class ResponseHeaderBag extends HeaderBag
return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null;
}
/**
* Sets a cookie.
*
* @param Cookie $cookie
* @return void
*/
public function setCookie(Cookie $cookie)
{
$this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
}
/**
* Removes a cookie from the array, but does not unset it in the browser
*
* @param string $name
* @param string $path
* @param string $domain
* @return void
*/
public function removeCookie($name, $path = null, $domain = null)
{
unset($this->cookies[$domain][$path][$name]);
if (empty($this->cookies[$domain][$path])) {
unset($this->cookies[$domain][$path]);
if (empty($this->cookies[$domain])) {
unset($this->cookies[$domain]);
}
}
}
/**
* Returns an array with all cookies
*
* @param string $format
*
* @throws \InvalidArgumentException When the $format is invalid
*
* @return array
*/
public function getCookies($format = self::COOKIES_FLAT)
{
if (!in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) {
throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY))));
}
if (self::COOKIES_ARRAY === $format) {
return $this->cookies;
}
$flattenedCookies = array();
foreach ($this->cookies as $path) {
foreach ($path as $cookies) {
foreach ($cookies as $cookie) {
$flattenedCookies[] = $cookie;
}
}
}
return $flattenedCookies;
}
/**
* Clears a cookie in the browser
*

View File

@ -75,4 +75,47 @@ class ResponseHeaderBagTest extends \PHPUnit_Framework_TestCase
$this->assertContains("Set-Cookie: foo=deleted; expires=".gmdate("D, d-M-Y H:i:s T", time() - 31536001)."; httponly", explode("\r\n", $bag->__toString()));
}
public function testCookiesWithSameNames()
{
$bag = new ResponseHeaderBag();
$bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar'));
$bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'foo.bar'));
$bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'bar.foo'));
$bag->setCookie(new Cookie('foo', 'bar'));
$this->assertEquals(4, count($bag->getCookies()));
$headers = explode("\r\n", $bag->__toString());
$this->assertContains("Set-Cookie: foo=bar; path=/path/foo; domain=foo.bar; httponly", $headers);
$this->assertContains("Set-Cookie: foo=bar; path=/path/foo; domain=foo.bar; httponly", $headers);
$this->assertContains("Set-Cookie: foo=bar; path=/path/bar; domain=bar.foo; httponly", $headers);
$this->assertContains("Set-Cookie: foo=bar; path=/; httponly", $headers);
$cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
$this->assertTrue(isset($cookies['foo.bar']['/path/foo']['foo']));
$this->assertTrue(isset($cookies['foo.bar']['/path/bar']['foo']));
$this->assertTrue(isset($cookies['bar.foo']['/path/bar']['foo']));
$this->assertTrue(isset($cookies['']['/']['foo']));
}
public function testRemoveCookie()
{
$bag = new ResponseHeaderBag();
$bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar'));
$bag->setCookie(new Cookie('bar', 'foo', 0, '/path/bar', 'foo.bar'));
$cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
$this->assertTrue(isset($cookies['foo.bar']['/path/foo']));
$bag->removeCookie('foo', '/path/foo', 'foo.bar');
$cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
$this->assertFalse(isset($cookies['foo.bar']['/path/foo']));
$bag->removeCookie('bar', '/path/bar', 'foo.bar');
$cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
$this->assertFalse(isset($cookies['foo.bar']));
}
}

View File

@ -12,6 +12,7 @@
namespace Symfony\Tests\Component\Security\Http\Logout;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Logout\CookieClearingLogoutHandler;
@ -25,20 +26,21 @@ class CookieClearingLogoutHandlerTest extends \PHPUnit_Framework_TestCase
$handler = new CookieClearingLogoutHandler(array('foo' => array('path' => '/foo', 'domain' => 'foo.foo'), 'foo2' => array('path' => null, 'domain' => null)));
$this->assertFalse($response->headers->hasCookie('foo'));
$cookies = $response->headers->getCookies();
$this->assertEquals(0, count($cookies));
$handler->logout($request, $response, $token);
$cookies = $response->headers->getCookies();
$cookies = $response->headers->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
$this->assertEquals(2, count($cookies));
$cookie = $cookies['foo'];
$cookie = $cookies['foo.foo']['/foo']['foo'];
$this->assertEquals('foo', $cookie->getName());
$this->assertEquals('/foo', $cookie->getPath());
$this->assertEquals('foo.foo', $cookie->getDomain());
$this->assertTrue($cookie->isCleared());
$cookie = $cookies['foo2'];
$cookie = $cookies['']['']['foo2'];
$this->assertStringStartsWith('foo2', $cookie->getName());
$this->assertNull($cookie->getPath());
$this->assertNull($cookie->getDomain());

View File

@ -19,6 +19,7 @@ use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Security\Http\RememberMe\PersistentTokenBasedRememberMeServices;
use Symfony\Component\Security\Core\Exception\TokenNotFoundException;
use Symfony\Component\Security\Core\Exception\CookieTheftException;
@ -281,11 +282,13 @@ class PersistentTokenBasedRememberMeServicesTest extends \PHPUnit_Framework_Test
;
$service->setTokenProvider($tokenProvider);
$this->assertFalse($response->headers->hasCookie('foo'));
$cookies = $response->headers->getCookies();
$this->assertEquals(0, count($cookies));
$service->loginSuccess($request, $response, $token);
$cookie = $response->headers->getCookie('foo');
$cookies = $response->headers->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
$cookie = $cookies['myfoodomain.foo']['/foo/path']['foo'];
$this->assertFalse($cookie->isCleared());
$this->assertTrue($cookie->isSecure());
$this->assertTrue($cookie->isHttpOnly());

View File

@ -19,6 +19,7 @@ use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Security\Http\RememberMe\TokenBasedRememberMeServices;
use Symfony\Component\Security\Core\Exception\TokenNotFoundException;
use Symfony\Component\Security\Core\Exception\CookieTheftException;
@ -184,11 +185,13 @@ class TokenBasedRememberMeServicesTest extends \PHPUnit_Framework_TestCase
->will($this->returnValue('foo'))
;
$this->assertFalse($response->headers->hasCookie('foo'));
$cookies = $response->headers->getCookies();
$this->assertEquals(0, count($cookies));
$service->loginSuccess($request, $response, $token);
$this->assertFalse($response->headers->hasCookie('foo'));
$cookies = $response->headers->getCookies();
$this->assertEquals(0, count($cookies));
}
public function testLoginSuccess()
@ -215,11 +218,13 @@ class TokenBasedRememberMeServicesTest extends \PHPUnit_Framework_TestCase
->will($this->returnValue($user))
;
$this->assertFalse($response->headers->hasCookie('foo'));
$cookies = $response->headers->getCookies();
$this->assertEquals(0, count($cookies));
$service->loginSuccess($request, $response, $token);
$cookie = $response->headers->getCookie('foo');
$cookies = $response->headers->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
$cookie = $cookies['myfoodomain.foo']['/foo/path']['foo'];
$this->assertFalse($cookie->isCleared());
$this->assertTrue($cookie->isSecure());
$this->assertTrue($cookie->isHttpOnly());