Browse Source

Merge pull request #10328 from cakephp/3.next-cookies

New cookie implementation for the HTTP stack
Mark Story 9 years ago
parent
commit
030f3e0e1a

+ 533 - 0
src/Http/Cookie/Cookie.php

@@ -0,0 +1,533 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.5.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Http\Cookie;
+
+use Cake\Chronos\Chronos;
+use Cake\Utility\Hash;
+use DateTimeInterface;
+use InvalidArgumentException;
+use RuntimeException;
+
+/**
+ * Cookie object to build a cookie and turn it into a header value
+ *
+ * An HTTP cookie (also called web cookie, Internet cookie, browser cookie or
+ * simply cookie) is a small piece of data sent from a website and stored on
+ * the user's computer by the user's web browser while the user is browsing.
+ *
+ * Cookies were designed to be a reliable mechanism for websites to remember
+ * stateful information (such as items added in the shopping cart in an online
+ * store) or to record the user's browsing activity (including clicking
+ * particular buttons, logging in, or recording which pages were visited in
+ * the past). They can also be used to remember arbitrary pieces of information
+ * that the user previously entered into form fields such as names, addresses,
+ * passwords, and credit card numbers.
+ *
+ * @link https://tools.ietf.org/html/rfc6265
+ * @link https://en.wikipedia.org/wiki/HTTP_cookie
+ */
+class Cookie implements CookieInterface
+{
+
+    use CookieCryptTrait;
+
+    /**
+     * Cookie name
+     *
+     * @var string
+     */
+    protected $name = '';
+
+    /**
+     * Raw Cookie value.
+     *
+     * @var string|array
+     */
+    protected $value = '';
+
+    /**
+     * Whether or not a JSON value has been expanded into an array.
+     *
+     * @var bool
+     */
+    protected $isExpanded = false;
+
+    /**
+     * Expiration time
+     *
+     * @var int
+     */
+    protected $expiresAt = 0;
+
+    /**
+     * Path
+     *
+     * @var string
+     */
+    protected $path = '';
+
+    /**
+     * Domain
+     *
+     * @var string
+     */
+    protected $domain = '';
+
+    /**
+     * Secure
+     *
+     * @var bool
+     */
+    protected $secure = false;
+
+    /**
+     * HTTP only
+     *
+     * @var bool
+     */
+    protected $httpOnly = false;
+
+    /**
+     * Constructor
+     *
+     * The constructors args are similar to the native PHP `setcookie()` method.
+     * The only difference is the 3rd argument which excepts null or an object
+     * implementing \DateTimeInterface instead an integer.
+     *
+     * @link http://php.net/manual/en/function.setcookie.php
+     * @param string $name Cookie name
+     * @param string|array $value Value of the cookie
+     * @param \DateTimeInterface|null $expiresAt Expiration time and date
+     * @param string $path Path
+     * @param string $domain Domain
+     * @param bool $secure Is secure
+     * @param bool $httpOnly HTTP Only
+     */
+    public function __construct($name, $value = '', $expiresAt = null, $path = '', $domain = '', $secure = false, $httpOnly = false)
+    {
+        $this->validateName($name);
+        $this->setName($name);
+        $this->setValue($value);
+        $this->setDomain($domain);
+        $this->setHttpOnly($httpOnly);
+        $this->setPath($path);
+
+        if ($expiresAt !== null) {
+            $this->expiresAt($expiresAt);
+        }
+    }
+
+    /**
+     * Builds the expiration value part of the header string
+     *
+     * @return string
+     */
+    protected function _buildExpirationValue()
+    {
+        return sprintf(
+            'expires=%s',
+            gmdate('D, d-M-Y H:i:s T', $this->expiresAt)
+        );
+    }
+
+    /**
+     * Returns a header value as string
+     *
+     * @return string
+     */
+    public function toHeaderValue()
+    {
+        $headerValue[] = sprintf('%s=%s', $this->name, urlencode($this->value));
+
+        if ($this->expiresAt !== 0) {
+            $headerValue[] = $this->_buildExpirationValue();
+        }
+        if ($this->path !== '') {
+            $headerValue[] = sprintf('path=%s', $this->path);
+        }
+        if ($this->domain !== '') {
+            $headerValue[] = sprintf('domain=%s', $this->domain);
+        }
+        if ($this->secure) {
+            $headerValue[] = 'secure';
+        }
+        if ($this->httpOnly) {
+            $headerValue[] = 'httponly';
+        }
+
+        return implode('; ', $headerValue);
+    }
+
+    /**
+     * Sets the cookie name
+     *
+     * @param string $name Name of the cookie
+     * @return $this
+     */
+    public function setName($name)
+    {
+        $this->validateName($name);
+        $this->name = $name;
+
+        return $this;
+    }
+
+    /**
+     * Gets the cookie name
+     *
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Validates the cookie name
+     *
+     * @param string $name Name of the cookie
+     * @return void
+     * @throws \InvalidArgumentException
+     */
+    protected function validateName($name)
+    {
+        if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
+            throw new InvalidArgumentException(
+                sprintf('The cookie name `%s` contains invalid characters.', $name)
+            );
+        }
+
+        if (empty($name)) {
+            throw new InvalidArgumentException('The cookie name cannot be empty.');
+        }
+    }
+
+    /**
+     * Gets the cookie value
+     *
+     * @return string|array
+     */
+    public function getValue()
+    {
+        return $this->value;
+    }
+
+    /**
+     * Sets the raw cookie data
+     *
+     * @param string|array $value Value of the cookie to set
+     * @return $this
+     */
+    public function setValue($value)
+    {
+        if (is_array($value)) {
+            $this->isExpanded = true;
+        }
+
+        $this->value = $value;
+
+        return $this;
+    }
+
+    /**
+     * Sets the path
+     *
+     * @param string $path Sets the path
+     * @return $this
+     */
+    public function setPath($path)
+    {
+        if (!is_string($path)) {
+            throw new InvalidArgumentException(sprintf(
+                'The provided arg must be of type `string` but `%s` given',
+                gettype($path)
+            ));
+        }
+
+        $this->path = $path;
+
+        return $this;
+    }
+
+    /**
+     * Sets the domain
+     *
+     * @param string $domain Domain to set
+     * @return $this
+     */
+    public function setDomain($domain)
+    {
+        if (!is_string($domain)) {
+            throw new InvalidArgumentException(sprintf(
+                'The provided arg must be of type `string` but `%s` given',
+                gettype($domain)
+            ));
+        }
+
+        $this->domain = $domain;
+
+        return $this;
+    }
+
+    /**
+     * Check if the cookie is secure
+     *
+     * @return bool
+     */
+    public function isSecure()
+    {
+        return $this->secure;
+    }
+
+    /**
+     * Set HTTP Only
+     *
+     * @param bool $httpOnly HTTP Only
+     * @return $this
+     */
+    public function setHttpOnly($httpOnly)
+    {
+        if (!is_bool($httpOnly)) {
+            throw new InvalidArgumentException(sprintf(
+                'The provided arg must be of type `bool` but `%s` given',
+                gettype($httpOnly)
+            ));
+        }
+
+        $this->httpOnly = $httpOnly;
+
+        return $this;
+    }
+
+    /**
+     * Check if the cookie is HTTP only
+     *
+     * @return bool
+     */
+    public function isHttpOnly()
+    {
+        return $this->httpOnly;
+    }
+
+    /**
+     * Sets the expiration date
+     *
+     * @param \DateTimeInterface $dateTime Date time object
+     * @return $this
+     */
+    public function expiresAt(DateTimeInterface $dateTime)
+    {
+        $this->expiresAt = (int)$dateTime->format('U');
+
+        return $this;
+    }
+
+    /**
+     * Checks if a value exists in the cookie data
+     *
+     * @param string $path Path to check
+     * @return bool
+     */
+    public function check($path)
+    {
+        $this->_isExpanded();
+
+        return Hash::check($this->value, $path);
+    }
+
+    /**
+     * Writes data to the cookie
+     *
+     * @param string $path Path to write to
+     * @param mixed $value Value to write
+     * @return $this
+     */
+    public function write($path, $value)
+    {
+        $this->_isExpanded();
+
+        Hash::insert($this->value, $path, $value);
+
+        return $this;
+    }
+
+    /**
+     * Read data from the cookie
+     *
+     * @param string $path Path to read the data from
+     * @return mixed
+     */
+    public function read($path = null)
+    {
+        $this->_isExpanded();
+
+        if ($path === null) {
+            return $this->value;
+        }
+
+        return Hash::get($this->value, $path);
+    }
+
+    /**
+     * Throws a \RuntimeException if the cookie value was not expanded
+     *
+     * @throws \RuntimeException
+     * @return void
+     */
+    protected function _isExpanded()
+    {
+        if (!$this->isExpanded) {
+            throw new RuntimeException('The Cookie data has not been expanded');
+        }
+    }
+
+    /**
+     * Sets the cookies date to a far future so it will virtually never expire
+     *
+     * @return $this
+     */
+    public function willNeverExpire()
+    {
+        $this->expiresAt = Chronos::now()->setDate(2038, 1, 1)->format('U');
+
+        return $this;
+    }
+
+    /**
+     * Deletes the cookie from the browser
+     *
+     * This is done by setting the expiration time to "now"
+     *
+     * @return $this
+     */
+    public function willBeDeleted()
+    {
+        $this->expiresAt = Chronos::now()->format('U');
+
+        return $this;
+    }
+
+    /**
+     * Encrypts the cookie value
+     *
+     * @param string|null $key Encryption key
+     * @return $this
+     */
+    public function encrypt($key = null)
+    {
+        if ($key !== null) {
+            $this->setEncryptionKey($key);
+        }
+
+        $this->value = $this->_encrypt($this->value);
+
+        return $this;
+    }
+
+    /**
+     * Decrypts the cookie value
+     *
+     * @param string|null $key Encryption key
+     * @return $this
+     */
+    public function decrypt($key = null)
+    {
+        if ($key !== null) {
+            $this->setEncryptionKey($key);
+        }
+
+        $this->value = $this->_decrypt($this->value);
+
+        return $this;
+    }
+
+    /**
+     * Expands a serialized cookie value
+     *
+     * @return $this
+     */
+    public function expand()
+    {
+        if (!$this->isExpanded) {
+            $this->value = $this->_expand($this->value);
+            $this->isExpanded = true;
+        }
+
+        return $this;
+    }
+
+    /**
+     * Serializes the cookie value to a string
+     *
+     * @return $this
+     */
+    public function flatten()
+    {
+        if ($this->isExpanded) {
+            $this->value = $this->_flatten($this->value);
+            $this->isExpanded = false;
+        }
+
+        return $this;
+    }
+
+    /**
+     * Checks if the cookie value was expanded
+     *
+     * @return bool
+     */
+    public function isExpanded()
+    {
+        return $this->isExpanded;
+    }
+
+    /**
+     * Implode method to keep keys are multidimensional arrays
+     *
+     * @param array $array Map of key and values
+     * @return string A json encoded string.
+     */
+    protected function _flatten(array $array)
+    {
+        return json_encode($array);
+    }
+
+    /**
+     * Explode method to return array from string set in CookieComponent::_flatten()
+     * Maintains reading backwards compatibility with 1.x CookieComponent::_flatten().
+     *
+     * @param string $string A string containing JSON encoded data, or a bare string.
+     * @return string|array Map of key and values
+     */
+    protected function _expand($string)
+    {
+        $first = substr($string, 0, 1);
+        if ($first === '{' || $first === '[') {
+            $ret = json_decode($string, true);
+
+            return ($ret !== null) ? $ret : $string;
+        }
+
+        $array = [];
+        foreach (explode(',', $string) as $pair) {
+            $key = explode('|', $pair);
+            if (!isset($key[1])) {
+                return $key[0];
+            }
+            $array[$key[0]] = $key[1];
+        }
+
+        return $array;
+    }
+}

+ 80 - 0
src/Http/Cookie/CookieCollection.php

@@ -0,0 +1,80 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.5.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Http\Cookie;
+
+use ArrayIterator;
+use InvalidArgumentException;
+use IteratorAggregate;
+
+/**
+ * Cookie Collection
+ */
+class CookieCollection implements IteratorAggregate
+{
+
+    /**
+     * Cookie objects
+     *
+     * @var Cookie[]
+     */
+    protected $cookies = [];
+
+    /**
+     * Constructor
+     *
+     * @param array $cookies Array of cookie objects
+     */
+    public function __construct(array $cookies = [])
+    {
+        $this->checkCookies($cookies);
+        foreach ($cookies as $cookie) {
+            $name = $cookie->getName();
+            $key = mb_strtolower($name);
+            $this->cookies[$key] = $cookie;
+        }
+    }
+
+    /**
+     * Checks if only valid cookie objects are in the array
+     *
+     * @param array $cookies Array of cookie objects
+     * @return void
+     * @throws \InvalidArgumentException
+     */
+    protected function checkCookies(array $cookies)
+    {
+        foreach ($cookies as $index => $cookie) {
+            if (!$cookie instanceof CookieInterface) {
+                throw new InvalidArgumentException(
+                    sprintf(
+                        'Expected `%s[]` as $cookies but instead got `%s` at index %d',
+                        static::class,
+                        is_object($cookie) ? get_class($cookie) : gettype($cookie),
+                        $index
+                    )
+                );
+            }
+        }
+    }
+
+    /**
+     * Gets the iterator
+     *
+     * @return \ArrayIterator
+     */
+    public function getIterator()
+    {
+        return new ArrayIterator($this->cookies);
+    }
+}

+ 209 - 0
src/Http/Cookie/CookieCryptTrait.php

@@ -0,0 +1,209 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.5.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Http\Cookie;
+
+use Cake\Utility\Security;
+use RuntimeException;
+
+/**
+ * Cookie Crypt Trait.
+ *
+ * Provides the encrypt/decrypt logic.
+ */
+trait CookieCryptTrait
+{
+
+    /**
+     * Valid cipher names for encrypted cookies.
+     *
+     * @var array
+     */
+    protected $_validCiphers = [
+        'aes'
+    ];
+
+    /**
+     * Encryption cipher
+     *
+     * @param string|bool
+     */
+    protected $encryptionCipher = 'aes';
+
+    /**
+     * The key for encrypting and decrypting the cookie
+     *
+     * @var string
+     */
+    protected $encryptionKey = '';
+
+    /**
+     * Prefix of the encrypted string
+     *
+     * @var string
+     */
+    protected $encryptedStringPrefix = 'Q2FrZQ==.';
+
+    /**
+     * Sets the encryption cipher
+     *
+     * @param string $cipher Cipher
+     * @return $this
+     */
+    public function setEncryptionCipher($cipher)
+    {
+        $this->checkCipher($cipher);
+        $this->encryptionCipher = $cipher;
+
+        return $this;
+    }
+
+    /**
+     * Check if encryption is enabled
+     *
+     * @return bool
+     */
+    public function isEncryptionEnabled()
+    {
+        return is_string($this->encryptionCipher);
+    }
+
+    /**
+     * Disables the encryption
+     *
+     * @return $this
+     */
+    public function disableEncryption()
+    {
+        $this->encryptionCipher = false;
+
+        return $this;
+    }
+
+    /**
+     * Sets the encryption key
+     *
+     * @param string $key Encryption key
+     * @return $this
+     */
+    public function setEncryptionKey($key)
+    {
+        $this->encryptionKey = $key;
+
+        return $this;
+    }
+
+    /**
+     * Returns the encryption key to be used.
+     *
+     * @return string
+     */
+    public function getEncryptionKey()
+    {
+        if ($this->encryptionKey === null) {
+            return Security::salt();
+        }
+
+        return $this->encryptionKey;
+    }
+
+    /**
+     * Encrypts $value using public $type method in Security class
+     *
+     * @param string|array $value Value to encrypt
+     * @return string Encoded values
+     */
+    protected function _encrypt($value)
+    {
+        if (is_array($value)) {
+            $value = $this->_flatten($value);
+        }
+
+        $encrypt = $this->encryptionCipher;
+        if ($encrypt === false) {
+            throw new RuntimeException('Encryption is disabled, no cipher provided.');
+        }
+
+        $cipher = null;
+        $key = $this->getEncryptionKey();
+
+        if ($encrypt === 'aes') {
+            $cipher = Security::encrypt($value, $key);
+        }
+
+        return $this->encryptedStringPrefix . base64_encode($cipher);
+    }
+
+    /**
+     * Helper method for validating encryption cipher names.
+     *
+     * @param string $encrypt The cipher name.
+     * @return void
+     * @throws \RuntimeException When an invalid cipher is provided.
+     */
+    protected function checkCipher($encrypt)
+    {
+        if (!in_array($encrypt, $this->_validCiphers)) {
+            $msg = sprintf(
+                'Invalid encryption cipher. Must be one of %s.',
+                implode(', ', $this->_validCiphers)
+            );
+            throw new RuntimeException($msg);
+        }
+    }
+
+    /**
+     * Decrypts $value using public $type method in Security class
+     *
+     * @param string|array $values Values to decrypt
+     * @return string|array Decrypted values
+     */
+    protected function _decrypt($values)
+    {
+        if (is_string($values)) {
+            return $this->_decode($values);
+        }
+
+        $decrypted = [];
+        foreach ($values as $name => $value) {
+            $decrypted[$name] = $this->_decode($value);
+        }
+
+        return $decrypted;
+    }
+
+    /**
+     * Decodes and decrypts a single value.
+     *
+     * @param string $value The value to decode & decrypt.
+     * @return string|array Decoded values.
+     */
+    protected function _decode($value)
+    {
+        if (!$this->isEncryptionEnabled()) {
+            return $this->_expand($value);
+        }
+
+        $key = $this->getEncryptionKey();
+        $encrypt = $this->encryptionCipher;
+
+        $value = base64_decode(substr($value, strlen($this->encryptedStringPrefix)));
+
+        if ($encrypt === 'aes') {
+            $value = Security::decrypt($value, $key);
+        }
+
+        return $this->_expand($value);
+    }
+}

+ 57 - 0
src/Http/Cookie/CookieInterface.php

@@ -0,0 +1,57 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.5.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Http\Cookie;
+
+/**
+ * Cookie Interface
+ */
+interface CookieInterface
+{
+    /**
+     * Sets the cookie name
+     *
+     * @param string $name Name of the cookie
+     * @return $this
+     */
+    public function setName($name);
+
+    /**
+     * Gets the cookie name
+     *
+     * @return string
+     */
+    public function getName();
+
+    /**
+     * Gets the cookie value
+     *
+     * @return string|array
+     */
+    public function getValue();
+
+    /**
+     * Sets the raw cookie data
+     *
+     * @param string|array $value Value of the cookie to set
+     * @return $this
+     */
+    public function setValue($value);
+
+    /**
+     * Returns the cookie as header value
+     *
+     * @return string
+     */
+    public function toHeaderValue();
+}

+ 69 - 0
src/Http/Cookie/RequestCookies.php

@@ -0,0 +1,69 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.5.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Http\Cookie;
+
+use InvalidArgumentException;
+use Psr\Http\Message\ServerRequestInterface;
+
+class RequestCookies extends CookieCollection
+{
+
+    /**
+     * Create instance from a server request.
+     *
+     * @param \Psr\Http\Message\ServerRequestInterface $request Request object
+     * @param string $cookieClass Cookie class to use for the cookies
+     * @return \Cake\Http\Cookie\RequestCookies
+     */
+    public static function createFromRequest(ServerRequestInterface $request, $cookieClass = Cookie::class)
+    {
+        $cookies = [];
+        $cookieParams = $request->getCookieParams();
+
+        foreach ($cookieParams as $name => $value) {
+            $cookies[] = new $cookieClass($name, $value);
+        }
+
+        return new static($cookies);
+    }
+
+    /**
+     * Checks if the collection has a cookie with the given name
+     *
+     * @param string $name Name of the cookie
+     * @return bool
+     */
+    public function has($name)
+    {
+        $key = mb_strtolower($name);
+
+        return isset($this->cookies[$key]);
+    }
+
+    /**
+     * Get a cookie from the collection by name.
+     *
+     * @param string $name Name of the cookie to get
+     * @throws \InvalidArgumentException
+     * @return Cookie
+     */
+    public function get($name)
+    {
+        if (!$this->has($name)) {
+            throw new InvalidArgumentException(sprintf('Cookie `%s` does not exist', $name));
+        }
+
+        return $this->cookies[mb_strtolower($name)];
+    }
+}

+ 36 - 0
src/Http/Cookie/ResponseCookies.php

@@ -0,0 +1,36 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.5.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Http\Cookie;
+
+use Psr\Http\Message\ResponseInterface;
+
+class ResponseCookies extends CookieCollection
+{
+
+    /**
+     * Adds the cookies to the response
+     *
+     * @param \Psr\Http\Message\ResponseInterface $response Response object.
+     * @return \Psr\Http\Message\ResponseInterface
+     */
+    public function addToResponse(ResponseInterface $response)
+    {
+        $header = [];
+        foreach ($this->cookies as $setCookie) {
+            $header[] = $setCookie->toHeaderValue();
+        }
+
+        return $response->withAddedHeader('Set-Cookie', $header);
+    }
+}

+ 69 - 0
tests/TestCase/Http/Cookie/CookieCollectionTest.php

@@ -0,0 +1,69 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.5.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\TestCase\Http\Cookie;
+
+use Cake\Http\Cookie\Cookie;
+use Cake\Http\Cookie\CookieCollection;
+use Cake\TestSuite\TestCase;
+
+/**
+ * Cookie collection test.
+ */
+class CookieCollectionTest extends TestCase
+{
+
+    /**
+     * Test constructor
+     *
+     * @return void
+     */
+    public function testConstructorWithEmptyArray()
+    {
+        $collection = new CookieCollection([]);
+        $this->assertCount(0, $collection);
+    }
+
+    /**
+     * Test valid cookies
+     *
+     * @return void
+     */
+    public function testConstructorWithCookieArray()
+    {
+        $cookies = [
+            new Cookie('one', 'one'),
+            new Cookie('two', 'two')
+        ];
+
+        $collection = new CookieCollection($cookies);
+        $this->assertCount(2, $collection);
+    }
+
+    /**
+     * Test that the constructor takes only an array of objects implementing
+     * the CookieInterface
+     *
+     * @expectedException \InvalidArgumentException
+     * @expectedExceptionMessage Expected `Cake\Http\Cookie\CookieCollection[]` as $cookies but instead got `array` at index 1
+     * @return void
+     */
+    public function testConstructorWithInvalidCookieObjects()
+    {
+        $array = [
+            new Cookie('one', 'one'),
+            []
+        ];
+
+        new CookieCollection($array);
+    }
+}

+ 160 - 0
tests/TestCase/Http/Cookie/CookieTest.php

@@ -0,0 +1,160 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.5.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\TestCase\Http\Cookie;
+
+use Cake\Chronos\Chronos;
+use Cake\Http\Cookie\Cookie;
+use Cake\TestSuite\TestCase;
+
+/**
+ * HTTP cookies test.
+ */
+class CookieTest extends TestCase
+{
+
+    /**
+     * Encryption key used in the tests
+     *
+     * @var string
+     */
+    protected $encryptionKey = 'someverysecretkeythatisatleast32charslong';
+
+    /**
+     * Test invalid cookie name
+     *
+     * @expectedException \InvalidArgumentException
+     * @expectedExceptionMessage The cookie name `no, this wont, work` contains invalid characters.
+     */
+    public function testValidateNameInvalidChars()
+    {
+        new Cookie('no, this wont, work', '');
+    }
+
+    /**
+     * Test empty cookie name
+     *
+     * @expectedException \InvalidArgumentException
+     * @expectedExceptionMessage The cookie name cannot be empty.
+     * @return void
+     */
+    public function testValidateNameEmptyName()
+    {
+        new Cookie('', '');
+    }
+
+    /**
+     * Test decrypting the cookie
+     *
+     * @return void
+     */
+    public function testDecrypt()
+    {
+        $value = 'cakephp-rocks-and-is-awesome';
+        $cookie = new Cookie('cakephp', $value);
+        $cookie->encrypt($this->encryptionKey);
+        $this->assertTextStartsWith('Q2FrZQ==.', $cookie->getValue());
+        $cookie->decrypt($this->encryptionKey);
+        $this->assertSame($value, $cookie->getValue());
+    }
+
+    /**
+     * Testing encrypting the cookie
+     *
+     * @return void
+     */
+    public function testEncrypt()
+    {
+        $value = 'cakephp-rocks-and-is-awesome';
+
+        $cookie = new Cookie('cakephp', $value);
+        $cookie->encrypt($this->encryptionKey);
+
+        $this->assertNotEquals($value, $cookie->getValue());
+        $this->assertNotEmpty($cookie->getValue());
+    }
+
+    /**
+     * Tests the header value
+     *
+     * @return void
+     */
+    public function testToHeaderValue()
+    {
+        $cookie = new Cookie('cakephp', 'cakephp-rocks');
+        $result = $cookie->toHeaderValue();
+        $this->assertEquals('cakephp=cakephp-rocks', $result);
+
+        $date = Chronos::createFromFormat('m/d/Y h:m:s', '12/1/2027 12:00:00');
+
+        $cookie = new Cookie('cakephp', 'cakephp-rocks');
+        $cookie->setDomain('cakephp.org');
+        $cookie->expiresAt($date);
+        $result = $cookie->toHeaderValue();
+
+        $expected = 'cakephp=cakephp-rocks; expires=Tue, 01-Dec-2026 12:00:00 GMT; domain=cakephp.org';
+
+        $this->assertEquals($expected, $result);
+    }
+
+    /**
+     * Test getting the value from the cookie
+     *
+     * @return void
+     */
+    public function testGetValue()
+    {
+        $cookie = new Cookie('cakephp', 'cakephp-rocks');
+        $result = $cookie->getValue();
+        $this->assertEquals('cakephp-rocks', $result);
+
+        $cookie = new Cookie('cakephp', '');
+        $result = $cookie->getValue();
+        $this->assertEquals('', $result);
+    }
+
+    /**
+     * testInflateAndExpand
+     *
+     * @return void
+     */
+    public function testInflateAndExpand()
+    {
+        $data = [
+            'username' => 'florian',
+            'profile' => [
+                'profession' => 'developer'
+            ]
+        ];
+        $cookie = new Cookie('cakephp', $data);
+
+        $result = $cookie->getValue();
+        $this->assertEquals($data, $result);
+
+        $result = $cookie->read('foo');
+        $this->assertNull($result);
+
+        $result = $cookie->read();
+        $this->assertEquals($data, $result);
+
+        $result = $cookie->read('profile.profession');
+        $this->assertEquals('developer', $result);
+
+        $result = $cookie->flatten();
+        $this->assertInstanceOf(Cookie::class, $result);
+
+        $expected = '{"username":"florian","profile":{"profession":"developer"}}';
+        $result = $cookie->getValue();
+
+        $this->assertEquals($expected, $result);
+    }
+}

+ 62 - 0
tests/TestCase/Http/Cookie/RequestCookiesTest.php

@@ -0,0 +1,62 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.5.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\TestCase\Http\Cookie;
+
+use Cake\Http\Cookie\Cookie;
+use Cake\Http\Cookie\RequestCookies;
+use Cake\Http\ServerRequest;
+use Cake\TestSuite\TestCase;
+
+/**
+ * HTTP cookies test.
+ */
+class RequestCookiesTest extends TestCase
+{
+
+    /**
+     * Server Request
+     *
+     * @var \Cake\Http\ServerRequest
+     */
+    public $request;
+
+    /**
+     * @inheritDoc
+     */
+    public function setUp()
+    {
+        $this->request = new ServerRequest([
+            'cookies' => [
+                'remember_me' => 'test',
+                'something' => 'test2'
+            ]
+        ]);
+    }
+
+    /**
+     * Test testCreateFromRequest
+     *
+     * @return null
+     */
+    public function testCreateFromRequest()
+    {
+        $result = RequestCookies::createFromRequest($this->request);
+        $this->assertInstanceOf(RequestCookies::class, $result);
+        $this->assertInstanceOf(Cookie::class, $result->get('remember_me'));
+        $this->assertInstanceOf(Cookie::class, $result->get('something'));
+
+        $this->assertTrue($result->has('remember_me'));
+        $this->assertTrue($result->has('something'));
+        $this->assertFalse($result->has('does-not-exist'));
+    }
+}

+ 51 - 0
tests/TestCase/Http/Cookie/ResponseCookiesTest.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.5.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\TestCase\Http\Cookie;
+
+use Cake\Http\Client\Response;
+use Cake\Http\Cookie\Cookie;
+use Cake\Http\Cookie\ResponseCookies;
+use Cake\TestSuite\TestCase;
+
+/**
+ * Response Cookies Test
+ */
+class ResponseCookiesTest extends TestCase
+{
+
+    /**
+     * testAddToResponse
+     *
+     * @return void
+     */
+    public function testAddToResponse()
+    {
+        $cookies = [
+            new Cookie('one', 'one'),
+            new Cookie('two', 'two')
+        ];
+
+        $responseCookies = new ResponseCookies($cookies);
+
+        $response = new Response();
+        $response = $responseCookies->addToResponse($response);
+
+        $expected = [
+            'Set-Cookie' => [
+                'one=one',
+                'two=two'
+            ]
+        ];
+        $this->assertEquals($expected, $response->getHeaders());
+    }
+}