namespace Ice;
/**
* Cookie helper.
*
* @package Ice/Cookies
* @category Helper
* @author Ice Team
* @copyright (c) 2014-2025 Ice Team
* @license http://iceframework.org/license
*/
class Cookies
{
protected di;
protected salt { get, set };
protected expiration = 0 { get, set };
protected path = "/" { get, set };
protected domain = null { get, set };
protected secure = false { get, set };
protected httpOnly = false { get, set };
protected encrypt = true { get, set };
public function __construct(string salt = null)
{
let this->di = Di::$fetch();
let this->salt = salt;
//let this->data = &_COOKIE;
}
/**
* Does cookie contain a key
*
* @param string key The cookie key
* @return boolean
*/
public function has(string key) -> boolean
{
return isset _COOKIE[key];
}
/**
* Gets the value of a signed cookie.
* Cookies without signatures will not be returned. If the cookie signature is present, but invalid, the cookie
* will be deleted.
*
* @param string key Cookie name
* @param mixed defaultValue Default value to return
*/
public function get(string key, var defaultValue = null)
{
var cookie, tmp, hash, value;
if !fetch cookie, _COOKIE[key] {
return defaultValue;
}
// Find the position of the split between salt and contents
if strpos(cookie, "~") !== false {
// Separate the salt and the value
//list (hash, value) = explode("~", cookie, 2);
let tmp = explode("~", cookie, 2),
hash = tmp[0],
value = tmp[1];
if this->salt(key, value) == hash {
// Cookie signature is valid
if this->encrypt {
let value = this->di->get("crypt")->decrypt(value);
}
return value;
}
// The cookie signature is invalid, delete it
this->remove(key);
}
return defaultValue;
}
/**
* Sets a signed cookie.
* Note that all cookie values must be strings and no automatic serialization will be performed!
*
* @param string key Name of cookie
* @param string value Value of cookie
* @param integer lifetime Expired time in seconds
* @return boolean
*/
public function set(string key, string value, int lifetime = 0)
{
if !lifetime {
// Use the default expiration
let lifetime = (int) this->expiration;
}
if this->encrypt {
if !empty value {
let value = this->di->get("crypt")->encrypt(value);
}
}
// Add the salt to the cookie value
let value = this->salt(key, value) . "~" . value;
return this->setcookie(key, value, lifetime, this->path, (string) this->domain, this->secure, this->httpOnly);
}
/**
* Deletes a cookie by making the value NULL and expiring it.
*
* @param string key cookie name
* @return boolean
*/
public function remove(string key) -> boolean
{
// Remove the cookie
unset _COOKIE[key];
// Nullify the cookie and make it expire
return this->setcookie(key, "", -86400, this->path, (string) this->domain, this->secure, this->httpOnly);
}
/**
* Generates a salt string for a cookie based on the name and value.
*
* @param string name Name of cookie
* @param string value Value of cookie
* @throws Exception if salt is not configured
* @return string
*/
public function salt(name, value) -> string
{
var userAgent;
// Require a valid salt
if !this->salt {
throw new Exception("A valid cookie salt is required.");
}
// Determine the user agent
let userAgent = this->di->get("request")->getUserAgent();
return sha1(userAgent . name . value . this->salt);
}
/**
* Proxy for the native setcookie function - to allow mocking in unit tests so that they do not fail when headers
* have been sent.
*
* @param string name
* @param string value
* @param integer expire
* @param string path
* @param string domain
* @param boolean secure
* @param boolean httpOnly
* @return bool
* @see setcookie
*/
protected function setcookie(string name, string value, int expire, string path, string domain, boolean secure, boolean httpOnly)
{
return setcookie(name, value, expire, path, domain, secure, httpOnly);
}
}