diff --git a/src/Header/SetCookie.php b/src/Header/SetCookie.php index 093be4ed06..7a36cf3aed 100644 --- a/src/Header/SetCookie.php +++ b/src/Header/SetCookie.php @@ -23,56 +23,56 @@ class SetCookie implements MultipleHeaderInterface /** * Cookie name * - * @var string + * @var string|null */ protected $name = null; /** * Cookie value * - * @var string + * @var string|null */ protected $value = null; /** * Version * - * @var int + * @var int|null */ protected $version = null; /** * Max Age * - * @var int + * @var int|null */ protected $maxAge = null; /** * Cookie expiry date * - * @var int + * @var int|null */ protected $expires = null; /** * Cookie domain * - * @var string + * @var string|null */ protected $domain = null; /** * Cookie path * - * @var string + * @var string|null */ protected $path = null; /** * Whether the cookie is secure or not * - * @var bool + * @var bool|null */ protected $secure = null; @@ -161,56 +161,30 @@ public static function fromString($headerLine, $bypassHeaderFieldName = false) * * @todo Add validation of each one of the parameters (legal domain, etc.) * - * @param string $name - * @param string $value - * @param int $expires - * @param string $path - * @param string $domain - * @param bool $secure - * @param bool $httponly - * @param string $maxAge - * @param int $version - * @return SetCookie + * @param string $name + * @param string $value + * @param int|string $expires + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $httponly + * @param string $maxAge + * @param int $version + * @return SetCookie */ public function __construct($name = null, $value = null, $expires = null, $path = null, $domain = null, $secure = false, $httponly = false, $maxAge = null, $version = null) { $this->type = 'Cookie'; - if ($name) { - $this->setName($name); - } - - if ($value) { - $this->setValue($value); // in parent - } - - if ($version!==null) { - $this->setVersion($version); - } - - if ($maxAge!==null) { - $this->setMaxAge($maxAge); - } - - if ($domain) { - $this->setDomain($domain); - } - - if ($expires) { - $this->setExpires($expires); - } - - if ($path) { - $this->setPath($path); - } - - if ($secure) { - $this->setSecure($secure); - } - - if ($httponly) { - $this->setHttpOnly($httponly); - } + $this->setName($name) + ->setValue($value) + ->setVersion($version) + ->setMaxAge($maxAge) + ->setDomain($domain) + ->setExpires($expires) + ->setPath($path) + ->setSecure($secure) + ->setHttpOnly($httponly); } /** @@ -282,7 +256,7 @@ public function getFieldValue() */ public function setName($name) { - if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { + if ($name !== null && preg_match("/[=,; \t\r\n\013\014]/", $name)) { throw new Exception\InvalidArgumentException("Cookie name cannot contain these characters: =,; \\t\\r\\n\\013\\014 ({$name})"); } @@ -300,10 +274,12 @@ public function getName() /** * @param string $value + * @return SetCookie */ public function setValue($value) { $this->value = $value; + return $this; } /** @@ -319,13 +295,15 @@ public function getValue() * * @param int $version * @throws Exception\InvalidArgumentException + * @return SetCookie */ public function setVersion($version) { - if (!is_int($version)) { + if ($version !== null && !is_int($version)) { throw new Exception\InvalidArgumentException('Invalid Version number specified'); } $this->version = $version; + return $this; } /** @@ -343,13 +321,15 @@ public function getVersion() * * @param int $maxAge * @throws Exception\InvalidArgumentException + * @return SetCookie */ public function setMaxAge($maxAge) { - if (!is_int($maxAge) || ($maxAge<0)) { + if ($maxAge !== null && (!is_int($maxAge) || ($maxAge < 0))) { throw new Exception\InvalidArgumentException('Invalid Max-Age number specified'); } $this->maxAge = $maxAge; + return $this; } /** @@ -363,30 +343,36 @@ public function getMaxAge() } /** - * @param int $expires + * @param int|string $expires * @throws Exception\InvalidArgumentException * @return SetCookie */ public function setExpires($expires) { - if (!empty($expires)) { - if (is_string($expires)) { - $expires = strtotime($expires); - } elseif (!is_int($expires)) { - throw new Exception\InvalidArgumentException('Invalid expires time specified'); - } - $this->expires = (int) $expires; + if ($expires === null) { + $this->expires = null; + return $this; + } + + if (is_string($expires)) { + $expires = strtotime($expires); } + + if (!is_int($expires) || $expires < 0) { + throw new Exception\InvalidArgumentException('Invalid expires time specified'); + } + + $this->expires = $expires; return $this; } /** * @param bool $inSeconds - * @return int + * @return int|string */ public function getExpires($inSeconds = false) { - if ($this->expires == null) { + if ($this->expires === null) { return; } if ($inSeconds) { @@ -397,10 +383,12 @@ public function getExpires($inSeconds = false) /** * @param string $domain + * @return SetCookie */ public function setDomain($domain) { $this->domain = $domain; + return $this; } /** @@ -413,10 +401,12 @@ public function getDomain() /** * @param string $path + * @return SetCookie */ public function setPath($path) { $this->path = $path; + return $this; } /** @@ -429,10 +419,12 @@ public function getPath() /** * @param bool $secure + * @return SetCookie */ public function setSecure($secure) { $this->secure = $secure; + return $this; } /** @@ -445,10 +437,12 @@ public function isSecure() /** * @param bool $httponly + * @return SetCookie */ public function setHttponly($httponly) { $this->httponly = $httponly; + return $this; } /** diff --git a/test/Header/SetCookieTest.php b/test/Header/SetCookieTest.php index f062859525..2c070e9a4b 100644 --- a/test/Header/SetCookieTest.php +++ b/test/Header/SetCookieTest.php @@ -153,6 +153,94 @@ public function testSetCookieCanAppendOtherHeadersInWhenCreatingString() $this->assertEquals($target, $headerLine); } + public function testSetCookieAttributesAreUnsettable() + { + $setCookieHeader = new SetCookie(); + $setCookieHeader->setName('myname'); + $setCookieHeader->setValue('myvalue'); + $setCookieHeader->setExpires('Wed, 13-Jan-2021 22:23:01 GMT'); + $setCookieHeader->setDomain('docs.foo.com'); + $setCookieHeader->setPath('/accounts'); + $setCookieHeader->setSecure(true); + $setCookieHeader->setHttponly(true); + + $target = 'myname=myvalue; Expires=Wed, 13-Jan-2021 22:23:01 GMT;' + . ' Domain=docs.foo.com; Path=/accounts;' + . ' Secure; HttpOnly'; + $this->assertSame($target, $setCookieHeader->getFieldValue()); // attributes set + + $setCookieHeader->setExpires(NULL); + $setCookieHeader->setDomain(NULL); + $setCookieHeader->setPath(NULL); + $setCookieHeader->setSecure(NULL); + $setCookieHeader->setHttponly(NULL); + $this->assertSame('myname=myvalue', $setCookieHeader->getFieldValue()); // attributes unset + + $setCookieHeader->setValue(NULL); + $this->assertSame('myname=', $setCookieHeader->getFieldValue()); + $this->assertNull($setCookieHeader->getValue()); + $this->assertNull($setCookieHeader->getExpires()); + $this->assertNull($setCookieHeader->getDomain()); + $this->assertNull($setCookieHeader->getPath()); + $this->assertNull($setCookieHeader->isSecure()); + $this->assertNull($setCookieHeader->isHttponly()); + } + + public function testSetCookieFieldValueIsEmptyStringWhenNameIsUnset() + { + $setCookieHeader = new SetCookie(); + $this->assertSame('', $setCookieHeader->getFieldValue()); // empty + + $setCookieHeader->setName('myname'); + $setCookieHeader->setValue('myvalue'); + $setCookieHeader->setExpires('Wed, 13-Jan-2021 22:23:01 GMT'); + $setCookieHeader->setDomain('docs.foo.com'); + $setCookieHeader->setPath('/accounts'); + $setCookieHeader->setSecure(true); + $setCookieHeader->setHttponly(true); + + $target = 'myname=myvalue; Expires=Wed, 13-Jan-2021 22:23:01 GMT;' + . ' Domain=docs.foo.com; Path=/accounts;' + . ' Secure; HttpOnly'; + $this->assertSame($target, $setCookieHeader->getFieldValue()); // not empty + + $setCookieHeader->setName(null); + $this->assertSame('', $setCookieHeader->getFieldValue()); // empty again + $this->assertNull($setCookieHeader->getName()); + } + + public function testSetCookieSetExpiresWithZeroTimeStamp() + { + $setCookieHeader = new SetCookie('myname', 'myvalue', 0); + $this->assertSame('Thu, 01-Jan-1970 00:00:00 GMT', $setCookieHeader->getExpires()); + + $setCookieHeader = new SetCookie('myname', 'myvalue', 1); + $this->assertSame('Thu, 01-Jan-1970 00:00:01 GMT', $setCookieHeader->getExpires()); + + $setCookieHeader->setExpires(0); + $this->assertSame('Thu, 01-Jan-1970 00:00:00 GMT', $setCookieHeader->getExpires()); + + $target = 'myname=myvalue; Expires=Thu, 01-Jan-1970 00:00:00 GMT'; + $this->assertSame($target, $setCookieHeader->getFieldValue()); + } + + public function testSetCookieSetExpiresWithUnixEpochString() + { + $setCookieHeader = new SetCookie('myname', 'myvalue', 'Thu, 01-Jan-1970 00:00:00 GMT'); + $this->assertSame('Thu, 01-Jan-1970 00:00:00 GMT', $setCookieHeader->getExpires()); + $this->assertSame(0, $setCookieHeader->getExpires(true)); + + $setCookieHeader = new SetCookie('myname', 'myvalue', 1); + $this->assertSame('Thu, 01-Jan-1970 00:00:01 GMT', $setCookieHeader->getExpires()); + + $setCookieHeader->setExpires('Thu, 01-Jan-1970 00:00:00 GMT'); + $this->assertSame('Thu, 01-Jan-1970 00:00:00 GMT', $setCookieHeader->getExpires()); + $this->assertSame(0, $setCookieHeader->getExpires(true)); + + $target = 'myname=myvalue; Expires=Thu, 01-Jan-1970 00:00:00 GMT'; + $this->assertSame($target, $setCookieHeader->getFieldValue()); + } + public function testIsValidForRequestSubdomainMatch() { $setCookieHeader = new SetCookie(