Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\HttpFoundation; Chris@0: Chris@0: /** Chris@0: * Represents a cookie. Chris@0: * Chris@0: * @author Johannes M. Schmitt Chris@0: */ Chris@0: class Cookie Chris@0: { Chris@0: protected $name; Chris@0: protected $value; Chris@0: protected $domain; Chris@0: protected $expire; Chris@0: protected $path; Chris@0: protected $secure; Chris@0: protected $httpOnly; Chris@0: private $raw; Chris@0: private $sameSite; Chris@0: Chris@0: const SAMESITE_LAX = 'lax'; Chris@0: const SAMESITE_STRICT = 'strict'; Chris@0: Chris@0: /** Chris@14: * Creates cookie from raw header string. Chris@0: * Chris@14: * @param string $cookie Chris@14: * @param bool $decode Chris@14: * Chris@14: * @return static Chris@14: */ Chris@14: public static function fromString($cookie, $decode = false) Chris@14: { Chris@17: $data = [ Chris@14: 'expires' => 0, Chris@14: 'path' => '/', Chris@14: 'domain' => null, Chris@14: 'secure' => false, Chris@14: 'httponly' => false, Chris@14: 'raw' => !$decode, Chris@14: 'samesite' => null, Chris@17: ]; Chris@14: foreach (explode(';', $cookie) as $part) { Chris@14: if (false === strpos($part, '=')) { Chris@14: $key = trim($part); Chris@14: $value = true; Chris@14: } else { Chris@14: list($key, $value) = explode('=', trim($part), 2); Chris@14: $key = trim($key); Chris@14: $value = trim($value); Chris@14: } Chris@14: if (!isset($data['name'])) { Chris@14: $data['name'] = $decode ? urldecode($key) : $key; Chris@14: $data['value'] = true === $value ? null : ($decode ? urldecode($value) : $value); Chris@14: continue; Chris@14: } Chris@14: switch ($key = strtolower($key)) { Chris@14: case 'name': Chris@14: case 'value': Chris@14: break; Chris@14: case 'max-age': Chris@14: $data['expires'] = time() + (int) $value; Chris@14: break; Chris@14: default: Chris@14: $data[$key] = $value; Chris@14: break; Chris@14: } Chris@14: } Chris@14: Chris@14: return new static($data['name'], $data['value'], $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']); Chris@14: } Chris@14: Chris@14: /** Chris@0: * @param string $name The name of the cookie Chris@14: * @param string|null $value The value of the cookie Chris@0: * @param int|string|\DateTimeInterface $expire The time the cookie expires Chris@0: * @param string $path The path on the server in which the cookie will be available on Chris@14: * @param string|null $domain The domain that the cookie is available to Chris@0: * @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client Chris@0: * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol Chris@0: * @param bool $raw Whether the cookie value should be sent with no url encoding Chris@0: * @param string|null $sameSite Whether the cookie will be available for cross-site requests Chris@0: * Chris@0: * @throws \InvalidArgumentException Chris@0: */ Chris@0: public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null) Chris@0: { Chris@0: // from PHP source code Chris@0: if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { Chris@0: throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); Chris@0: } Chris@0: Chris@0: if (empty($name)) { Chris@0: throw new \InvalidArgumentException('The cookie name cannot be empty.'); Chris@0: } Chris@0: Chris@0: // convert expiration time to a Unix timestamp Chris@0: if ($expire instanceof \DateTimeInterface) { Chris@0: $expire = $expire->format('U'); Chris@0: } elseif (!is_numeric($expire)) { Chris@0: $expire = strtotime($expire); Chris@0: Chris@0: if (false === $expire) { Chris@0: throw new \InvalidArgumentException('The cookie expiration time is not valid.'); Chris@0: } Chris@0: } Chris@0: Chris@0: $this->name = $name; Chris@0: $this->value = $value; Chris@0: $this->domain = $domain; Chris@0: $this->expire = 0 < $expire ? (int) $expire : 0; Chris@0: $this->path = empty($path) ? '/' : $path; Chris@0: $this->secure = (bool) $secure; Chris@0: $this->httpOnly = (bool) $httpOnly; Chris@0: $this->raw = (bool) $raw; Chris@0: Chris@12: if (null !== $sameSite) { Chris@12: $sameSite = strtolower($sameSite); Chris@12: } Chris@12: Chris@17: if (!\in_array($sameSite, [self::SAMESITE_LAX, self::SAMESITE_STRICT, null], true)) { Chris@0: throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.'); Chris@0: } Chris@0: Chris@0: $this->sameSite = $sameSite; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the cookie as a string. Chris@0: * Chris@0: * @return string The cookie Chris@0: */ Chris@0: public function __toString() Chris@0: { Chris@0: $str = ($this->isRaw() ? $this->getName() : urlencode($this->getName())).'='; Chris@0: Chris@0: if ('' === (string) $this->getValue()) { Chris@16: $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0'; Chris@0: } else { Chris@12: $str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue()); Chris@0: Chris@0: if (0 !== $this->getExpiresTime()) { Chris@16: $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge(); Chris@0: } Chris@0: } Chris@0: Chris@0: if ($this->getPath()) { Chris@0: $str .= '; path='.$this->getPath(); Chris@0: } Chris@0: Chris@0: if ($this->getDomain()) { Chris@0: $str .= '; domain='.$this->getDomain(); Chris@0: } Chris@0: Chris@0: if (true === $this->isSecure()) { Chris@0: $str .= '; secure'; Chris@0: } Chris@0: Chris@0: if (true === $this->isHttpOnly()) { Chris@0: $str .= '; httponly'; Chris@0: } Chris@0: Chris@0: if (null !== $this->getSameSite()) { Chris@0: $str .= '; samesite='.$this->getSameSite(); Chris@0: } Chris@0: Chris@0: return $str; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the name of the cookie. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function getName() Chris@0: { Chris@0: return $this->name; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the value of the cookie. Chris@0: * Chris@0: * @return string|null Chris@0: */ Chris@0: public function getValue() Chris@0: { Chris@0: return $this->value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the domain that the cookie is available to. Chris@0: * Chris@0: * @return string|null Chris@0: */ Chris@0: public function getDomain() Chris@0: { Chris@0: return $this->domain; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the time the cookie expires. Chris@0: * Chris@0: * @return int Chris@0: */ Chris@0: public function getExpiresTime() Chris@0: { Chris@0: return $this->expire; Chris@0: } Chris@0: Chris@0: /** Chris@14: * Gets the max-age attribute. Chris@14: * Chris@14: * @return int Chris@14: */ Chris@14: public function getMaxAge() Chris@14: { Chris@16: $maxAge = $this->expire - time(); Chris@16: Chris@16: return 0 >= $maxAge ? 0 : $maxAge; Chris@14: } Chris@14: Chris@14: /** Chris@0: * Gets the path on the server in which the cookie will be available on. Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: public function getPath() Chris@0: { Chris@0: return $this->path; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function isSecure() Chris@0: { Chris@0: return $this->secure; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks whether the cookie will be made accessible only through the HTTP protocol. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function isHttpOnly() Chris@0: { Chris@0: return $this->httpOnly; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Whether this cookie is about to be cleared. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function isCleared() Chris@0: { Chris@17: return 0 !== $this->expire && $this->expire < time(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Checks if the cookie value should be sent with no url encoding. Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function isRaw() Chris@0: { Chris@0: return $this->raw; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the SameSite attribute. Chris@0: * Chris@0: * @return string|null Chris@0: */ Chris@0: public function getSameSite() Chris@0: { Chris@0: return $this->sameSite; Chris@0: } Chris@0: }