From 048139074df674d0ac82da12357d3934af3abc2e Mon Sep 17 00:00:00 2001 From: Lukas Reschke Date: Thu, 27 Nov 2014 14:19:00 +0100 Subject: [PATCH] Add functions to modify cookies to response class Currently there is no AppFramework way to modify cookies, which makes it unusable for quite some use-cases or results in untestable code. This PR adds some basic functionalities to add and invalidate cookies. Usage: ```php $response = new TemplateResponse(...); $response->addCookie('foo', 'bar'); $response->invalidateCookie('foo'); $response->addCookie('bar', 'foo', new \DateTime('2015-01-01 00:00')); ``` Existing cookies can be accessed with the AppFramework using `$this->request->getCookie($name)`. --- lib/private/appframework/app.php | 10 ++- lib/private/appframework/http/dispatcher.php | 8 +- lib/public/appframework/http/response.php | 73 ++++++++++++++-- tests/lib/appframework/AppTest.php | 2 +- .../lib/appframework/http/DispatcherTest.php | 16 ++-- tests/lib/appframework/http/ResponseTest.php | 86 +++++++++++++++++++ 6 files changed, 177 insertions(+), 18 deletions(-) diff --git a/lib/private/appframework/app.php b/lib/private/appframework/app.php index baf52d02054..074b6cc3fd2 100644 --- a/lib/private/appframework/app.php +++ b/lib/private/appframework/app.php @@ -53,7 +53,7 @@ class App { // initialize the dispatcher and run all the middleware before the controller $dispatcher = $container['Dispatcher']; - list($httpHeaders, $responseHeaders, $output) = + list($httpHeaders, $responseHeaders, $responseCookies, $output) = $dispatcher->dispatch($controller, $methodName); if(!is_null($httpHeaders)) { @@ -64,6 +64,14 @@ class App { header($name . ': ' . $value); } + foreach($responseCookies as $name => $value) { + $expireDate = null; + if($value['expireDate'] instanceof \DateTime) { + $expireDate = $value['expireDate']->getTimestamp(); + } + setcookie($name, $value['value'], $expireDate, \OC::$WEBROOT, null, \OC::$server->getConfig()->getSystemValue('forcessl', false), true); + } + if(!is_null($output)) { header('Content-Length: ' . strlen($output)); print($output); diff --git a/lib/private/appframework/http/dispatcher.php b/lib/private/appframework/http/dispatcher.php index 29a661d5743..24540ef3c94 100644 --- a/lib/private/appframework/http/dispatcher.php +++ b/lib/private/appframework/http/dispatcher.php @@ -48,7 +48,7 @@ class Dispatcher { * @param Http $protocol the http protocol with contains all status headers * @param MiddlewareDispatcher $middlewareDispatcher the dispatcher which * runs the middleware - * @param ControllerMethodReflector the reflector that is used to inject + * @param ControllerMethodReflector $reflector the reflector that is used to inject * the arguments for the controller * @param IRequest $request the incoming request */ @@ -71,6 +71,7 @@ class Dispatcher { * @return array $array[0] contains a string with the http main header, * $array[1] contains headers in the form: $key => value, $array[2] contains * the response output + * @throws \Exception */ public function dispatch(Controller $controller, $methodName) { $out = array(null, array(), null); @@ -102,13 +103,14 @@ class Dispatcher { // get the output which should be printed and run the after output // middleware to modify the response $output = $response->render(); - $out[2] = $this->middlewareDispatcher->beforeOutput( + $out[3] = $this->middlewareDispatcher->beforeOutput( $controller, $methodName, $output); // depending on the cache object the headers need to be changed $out[0] = $this->protocol->getStatusHeader($response->getStatus(), $response->getLastModified(), $response->getETag()); - $out[1] = $response->getHeaders(); + $out[1] = array_merge($response->getHeaders()); + $out[2] = $response->getCookies(); return $out; } diff --git a/lib/public/appframework/http/response.php b/lib/public/appframework/http/response.php index 354911fee21..67e72cff6d9 100644 --- a/lib/public/appframework/http/response.php +++ b/lib/public/appframework/http/response.php @@ -45,9 +45,16 @@ class Response { ); + /** + * Cookies that will be need to be constructed as header + * @var array + */ + private $cookies = array(); + + /** * HTTP status code - defaults to STATUS OK - * @var string + * @var int */ private $status = Http::STATUS_OK; @@ -70,6 +77,7 @@ class Response { * Caches the response * @param int $cacheSeconds the amount of seconds that should be cached * if 0 then caching will be disabled + * @return $this */ public function cacheFor($cacheSeconds) { @@ -83,13 +91,68 @@ class Response { return $this; } + /** + * Adds a new cookie to the response + * @param string $name The name of the cookie + * @param string $value The value of the cookie + * @param \DateTime|null $expireDate Date on that the cookie should expire, if set + * to null cookie will be considered as session + * cookie. + * @return $this + */ + public function addCookie($name, $value, \DateTime $expireDate = null) { + $this->cookies[$name] = array('value' => $value, 'expireDate' => $expireDate); + return $this; + } + + + /** + * Set the specified cookies + * @param array $cookies array('foo' => array('value' => 'bar', 'expire' => null)) + * @return $this + */ + public function setCookies(array $cookies) { + $this->cookies = $cookies; + return $this; + } + + + /** + * Invalidates the specified cookie + * @param string $name + * @return $this + */ + public function invalidateCookie($name) { + $this->addCookie($name, 'expired', new \DateTime('1971-01-01 00:00')); + return $this; + } + + /** + * Invalidates the specified cookies + * @param array $cookieNames array('foo', 'bar') + * @return $this + */ + public function invalidateCookies(array $cookieNames) { + foreach($cookieNames as $cookieName) { + $this->invalidateCookie($cookieName); + } + return $this; + } + + /** + * Returns the cookies + * @return array + */ + public function getCookies() { + return $this->cookies; + } /** * Adds a new header to the response that will be called before the render * function * @param string $name The name of the HTTP header * @param string $value The value, null will delete it - * @return Response Reference to this object + * @return $this */ public function addHeader($name, $value) { $name = trim($name); // always remove leading and trailing whitespace @@ -108,10 +171,10 @@ class Response { /** * Set the headers - * @param array key value header pairs - * @return Response Reference to this object + * @param array $headers value header pairs + * @return $this */ - public function setHeaders($headers) { + public function setHeaders(array $headers) { $this->headers = $headers; return $this; diff --git a/tests/lib/appframework/AppTest.php b/tests/lib/appframework/AppTest.php index bd565e9765e..86128db118f 100644 --- a/tests/lib/appframework/AppTest.php +++ b/tests/lib/appframework/AppTest.php @@ -63,7 +63,7 @@ class AppTest extends \Test\TestCase { public function testControllerNameAndMethodAreBeingPassed(){ - $return = array(null, array(), null); + $return = array(null, array(), array(), null); $this->dispatcher->expects($this->once()) ->method('dispatch') ->with($this->equalTo($this->controller), diff --git a/tests/lib/appframework/http/DispatcherTest.php b/tests/lib/appframework/http/DispatcherTest.php index f92e7161e6b..45ebd6fce96 100644 --- a/tests/lib/appframework/http/DispatcherTest.php +++ b/tests/lib/appframework/http/DispatcherTest.php @@ -227,7 +227,7 @@ class DispatcherTest extends \Test\TestCase { $this->assertEquals($httpHeaders, $response[0]); $this->assertEquals($responseHeaders, $response[1]); - $this->assertEquals($out, $response[2]); + $this->assertEquals($out, $response[3]); } @@ -246,7 +246,7 @@ class DispatcherTest extends \Test\TestCase { $this->assertEquals($httpHeaders, $response[0]); $this->assertEquals($responseHeaders, $response[1]); - $this->assertEquals($out, $response[2]); + $this->assertEquals($out, $response[3]); } @@ -301,7 +301,7 @@ class DispatcherTest extends \Test\TestCase { $this->dispatcherPassthrough(); $response = $this->dispatcher->dispatch($controller, 'exec'); - $this->assertEquals('[3,true,4,1]', $response[2]); + $this->assertEquals('[3,true,4,1]', $response[3]); } @@ -324,7 +324,7 @@ class DispatcherTest extends \Test\TestCase { $this->dispatcherPassthrough(); $response = $this->dispatcher->dispatch($controller, 'exec'); - $this->assertEquals('[3,true,4,7]', $response[2]); + $this->assertEquals('[3,true,4,7]', $response[3]); } @@ -350,7 +350,7 @@ class DispatcherTest extends \Test\TestCase { $this->dispatcherPassthrough(); $response = $this->dispatcher->dispatch($controller, 'exec'); - $this->assertEquals('{"text":[3,false,4,1]}', $response[2]); + $this->assertEquals('{"text":[3,false,4,1]}', $response[3]); } @@ -375,7 +375,7 @@ class DispatcherTest extends \Test\TestCase { $this->dispatcherPassthrough(); $response = $this->dispatcher->dispatch($controller, 'execDataResponse'); - $this->assertEquals('{"text":[3,false,4,1]}', $response[2]); + $this->assertEquals('{"text":[3,false,4,1]}', $response[3]); } @@ -401,7 +401,7 @@ class DispatcherTest extends \Test\TestCase { $this->dispatcherPassthrough(); $response = $this->dispatcher->dispatch($controller, 'exec'); - $this->assertEquals('{"text":[3,false,4,1]}', $response[2]); + $this->assertEquals('{"text":[3,false,4,1]}', $response[3]); } @@ -429,7 +429,7 @@ class DispatcherTest extends \Test\TestCase { $this->dispatcherPassthrough(); $response = $this->dispatcher->dispatch($controller, 'exec'); - $this->assertEquals('{"text":[3,true,4,1]}', $response[2]); + $this->assertEquals('{"text":[3,true,4,1]}', $response[3]); } diff --git a/tests/lib/appframework/http/ResponseTest.php b/tests/lib/appframework/http/ResponseTest.php index 04e19fdaf71..b4352348bae 100644 --- a/tests/lib/appframework/http/ResponseTest.php +++ b/tests/lib/appframework/http/ResponseTest.php @@ -76,6 +76,92 @@ class ResponseTest extends \Test\TestCase { } + public function testAddCookie() { + $this->childResponse->addCookie('foo', 'bar'); + $this->childResponse->addCookie('bar', 'foo', new \DateTime('1970-01-01')); + + $expectedResponse = array( + 'foo' => array( + 'value' => 'bar', + 'expireDate' => null, + ), + 'bar' => array( + 'value' => 'foo', + 'expireDate' => new \DateTime('1970-01-01') + ) + ); + $this->assertEquals($expectedResponse, $this->childResponse->getCookies()); + } + + + function testSetCookies() { + $expected = array( + 'foo' => array( + 'value' => 'bar', + 'expireDate' => null, + ), + 'bar' => array( + 'value' => 'foo', + 'expireDate' => new \DateTime('1970-01-01') + ) + ); + + $this->childResponse->setCookies($expected); + $cookies = $this->childResponse->getCookies(); + + $this->assertEquals($expected, $cookies); + } + + + function testInvalidateCookie() { + $this->childResponse->addCookie('foo', 'bar'); + $this->childResponse->invalidateCookie('foo'); + $expected = array( + 'foo' => array( + 'value' => 'expired', + 'expireDate' => new \DateTime('1971-01-01') + ) + ); + + $cookies = $this->childResponse->getCookies(); + + $this->assertEquals($expected, $cookies); + } + + + function testInvalidateCookies() { + $this->childResponse->addCookie('foo', 'bar'); + $this->childResponse->addCookie('bar', 'foo'); + $expected = array( + 'foo' => array( + 'value' => 'bar', + 'expireDate' => null + ), + 'bar' => array( + 'value' => 'foo', + 'expireDate' => null + ) + ); + $cookies = $this->childResponse->getCookies(); + $this->assertEquals($expected, $cookies); + + $this->childResponse->invalidateCookies(array('foo', 'bar')); + $expected = array( + 'foo' => array( + 'value' => 'expired', + 'expireDate' => new \DateTime('1971-01-01') + ), + 'bar' => array( + 'value' => 'expired', + 'expireDate' => new \DateTime('1971-01-01') + ) + ); + + $cookies = $this->childResponse->getCookies(); + $this->assertEquals($expected, $cookies); + } + + public function testRenderReturnNullByDefault(){ $this->assertEquals(null, $this->childResponse->render()); } -- 2.39.5