123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527 |
- <?php
- namespace Http\Message;
- /**
- * Cookie Value Object.
- *
- * @author Márk Sági-Kazár <mark.sagikazar@gmail.com>
- *
- * @see http://tools.ietf.org/search/rfc6265
- */
- final class Cookie
- {
- /**
- * @var string
- */
- private $name;
- /**
- * @var string|null
- */
- private $value;
- /**
- * @var int|null
- */
- private $maxAge;
- /**
- * @var string|null
- */
- private $domain;
- /**
- * @var string
- */
- private $path;
- /**
- * @var bool
- */
- private $secure;
- /**
- * @var bool
- */
- private $httpOnly;
- /**
- * Expires attribute is HTTP 1.0 only and should be avoided.
- *
- * @var \DateTime|null
- */
- private $expires;
- /**
- * @param string $name
- * @param string|null $value
- * @param int|null $maxAge
- * @param string|null $domain
- * @param string|null $path
- * @param bool $secure
- * @param bool $httpOnly
- * @param \DateTime|null $expires Expires attribute is HTTP 1.0 only and should be avoided.
- *
- * @throws \InvalidArgumentException If name, value or max age is not valid.
- */
- public function __construct(
- $name,
- $value = null,
- $maxAge = null,
- $domain = null,
- $path = null,
- $secure = false,
- $httpOnly = false,
- \DateTime $expires = null
- ) {
- $this->validateName($name);
- $this->validateValue($value);
- $this->validateMaxAge($maxAge);
- $this->name = $name;
- $this->value = $value;
- $this->maxAge = $maxAge;
- $this->expires = $expires;
- $this->domain = $this->normalizeDomain($domain);
- $this->path = $this->normalizePath($path);
- $this->secure = (bool) $secure;
- $this->httpOnly = (bool) $httpOnly;
- }
- /**
- * Creates a new cookie without any attribute validation.
- *
- * @param string $name
- * @param string|null $value
- * @param int $maxAge
- * @param string|null $domain
- * @param string|null $path
- * @param bool $secure
- * @param bool $httpOnly
- * @param \DateTime|null $expires Expires attribute is HTTP 1.0 only and should be avoided.
- */
- public static function createWithoutValidation(
- $name,
- $value = null,
- $maxAge = null,
- $domain = null,
- $path = null,
- $secure = false,
- $httpOnly = false,
- \DateTime $expires = null
- ) {
- $cookie = new self('name', null, null, $domain, $path, $secure, $httpOnly, $expires);
- $cookie->name = $name;
- $cookie->value = $value;
- $cookie->maxAge = $maxAge;
- return $cookie;
- }
- /**
- * Returns the name.
- *
- * @return string
- */
- public function getName()
- {
- return $this->name;
- }
- /**
- * Returns the value.
- *
- * @return string|null
- */
- public function getValue()
- {
- return $this->value;
- }
- /**
- * Checks if there is a value.
- *
- * @return bool
- */
- public function hasValue()
- {
- return isset($this->value);
- }
- /**
- * Sets the value.
- *
- * @param string|null $value
- *
- * @return Cookie
- */
- public function withValue($value)
- {
- $this->validateValue($value);
- $new = clone $this;
- $new->value = $value;
- return $new;
- }
- /**
- * Returns the max age.
- *
- * @return int|null
- */
- public function getMaxAge()
- {
- return $this->maxAge;
- }
- /**
- * Checks if there is a max age.
- *
- * @return bool
- */
- public function hasMaxAge()
- {
- return isset($this->maxAge);
- }
- /**
- * Sets the max age.
- *
- * @param int|null $maxAge
- *
- * @return Cookie
- */
- public function withMaxAge($maxAge)
- {
- $this->validateMaxAge($maxAge);
- $new = clone $this;
- $new->maxAge = $maxAge;
- return $new;
- }
- /**
- * Returns the expiration time.
- *
- * @return \DateTime|null
- */
- public function getExpires()
- {
- return $this->expires;
- }
- /**
- * Checks if there is an expiration time.
- *
- * @return bool
- */
- public function hasExpires()
- {
- return isset($this->expires);
- }
- /**
- * Sets the expires.
- *
- * @param \DateTime|null $expires
- *
- * @return Cookie
- */
- public function withExpires(\DateTime $expires = null)
- {
- $new = clone $this;
- $new->expires = $expires;
- return $new;
- }
- /**
- * Checks if the cookie is expired.
- *
- * @return bool
- */
- public function isExpired()
- {
- return isset($this->expires) and $this->expires < new \DateTime();
- }
- /**
- * Returns the domain.
- *
- * @return string|null
- */
- public function getDomain()
- {
- return $this->domain;
- }
- /**
- * Checks if there is a domain.
- *
- * @return bool
- */
- public function hasDomain()
- {
- return isset($this->domain);
- }
- /**
- * Sets the domain.
- *
- * @param string|null $domain
- *
- * @return Cookie
- */
- public function withDomain($domain)
- {
- $new = clone $this;
- $new->domain = $this->normalizeDomain($domain);
- return $new;
- }
- /**
- * Checks whether this cookie is meant for this domain.
- *
- * @see http://tools.ietf.org/html/rfc6265#section-5.1.3
- *
- * @param string $domain
- *
- * @return bool
- */
- public function matchDomain($domain)
- {
- // Domain is not set or exact match
- if (!$this->hasDomain() || 0 === strcasecmp($domain, $this->domain)) {
- return true;
- }
- // Domain is not an IP address
- if (filter_var($domain, FILTER_VALIDATE_IP)) {
- return false;
- }
- return (bool) preg_match(sprintf('/\b%s$/i', preg_quote($this->domain)), $domain);
- }
- /**
- * Returns the path.
- *
- * @return string
- */
- public function getPath()
- {
- return $this->path;
- }
- /**
- * Sets the path.
- *
- * @param string|null $path
- *
- * @return Cookie
- */
- public function withPath($path)
- {
- $new = clone $this;
- $new->path = $this->normalizePath($path);
- return $new;
- }
- /**
- * Checks whether this cookie is meant for this path.
- *
- * @see http://tools.ietf.org/html/rfc6265#section-5.1.4
- *
- * @param string $path
- *
- * @return bool
- */
- public function matchPath($path)
- {
- return $this->path === $path || (0 === strpos($path, rtrim($this->path, '/').'/'));
- }
- /**
- * Checks whether this cookie may only be sent over HTTPS.
- *
- * @return bool
- */
- public function isSecure()
- {
- return $this->secure;
- }
- /**
- * Sets whether this cookie should only be sent over HTTPS.
- *
- * @param bool $secure
- *
- * @return Cookie
- */
- public function withSecure($secure)
- {
- $new = clone $this;
- $new->secure = (bool) $secure;
- return $new;
- }
- /**
- * Check whether this cookie may not be accessed through Javascript.
- *
- * @return bool
- */
- public function isHttpOnly()
- {
- return $this->httpOnly;
- }
- /**
- * Sets whether this cookie may not be accessed through Javascript.
- *
- * @param bool $httpOnly
- *
- * @return Cookie
- */
- public function withHttpOnly($httpOnly)
- {
- $new = clone $this;
- $new->httpOnly = (bool) $httpOnly;
- return $new;
- }
- /**
- * Checks if this cookie represents the same cookie as $cookie.
- *
- * This does not compare the values, only name, domain and path.
- *
- * @param Cookie $cookie
- *
- * @return bool
- */
- public function match(self $cookie)
- {
- return $this->name === $cookie->name && $this->domain === $cookie->domain and $this->path === $cookie->path;
- }
- /**
- * Validates cookie attributes.
- *
- * @return bool
- */
- public function isValid()
- {
- try {
- $this->validateName($this->name);
- $this->validateValue($this->value);
- $this->validateMaxAge($this->maxAge);
- } catch (\InvalidArgumentException $e) {
- return false;
- }
- return true;
- }
- /**
- * Validates the name attribute.
- *
- * @see http://tools.ietf.org/search/rfc2616#section-2.2
- *
- * @param string $name
- *
- * @throws \InvalidArgumentException If the name is empty or contains invalid characters.
- */
- private function validateName($name)
- {
- if (strlen($name) < 1) {
- throw new \InvalidArgumentException('The name cannot be empty');
- }
- // Name attribute is a token as per spec in RFC 2616
- if (preg_match('/[\x00-\x20\x22\x28-\x29\x2C\x2F\x3A-\x40\x5B-\x5D\x7B\x7D\x7F]/', $name)) {
- throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
- }
- }
- /**
- * Validates a value.
- *
- * @see http://tools.ietf.org/html/rfc6265#section-4.1.1
- *
- * @param string|null $value
- *
- * @throws \InvalidArgumentException If the value contains invalid characters.
- */
- private function validateValue($value)
- {
- if (isset($value)) {
- if (preg_match('/[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/', $value)) {
- throw new \InvalidArgumentException(sprintf('The cookie value "%s" contains invalid characters.', $value));
- }
- }
- }
- /**
- * Validates a Max-Age attribute.
- *
- * @param int|null $maxAge
- *
- * @throws \InvalidArgumentException If the Max-Age is not an empty or integer value.
- */
- private function validateMaxAge($maxAge)
- {
- if (isset($maxAge)) {
- if (!is_int($maxAge)) {
- throw new \InvalidArgumentException('Max-Age must be integer');
- }
- }
- }
- /**
- * Remove the leading '.' and lowercase the domain as per spec in RFC 6265.
- *
- * @see http://tools.ietf.org/html/rfc6265#section-4.1.2.3
- * @see http://tools.ietf.org/html/rfc6265#section-5.1.3
- * @see http://tools.ietf.org/html/rfc6265#section-5.2.3
- *
- * @param string|null $domain
- *
- * @return string
- */
- private function normalizeDomain($domain)
- {
- if (isset($domain)) {
- $domain = ltrim(strtolower($domain), '.');
- }
- return $domain;
- }
- /**
- * Processes path as per spec in RFC 6265.
- *
- * @see http://tools.ietf.org/html/rfc6265#section-5.1.4
- * @see http://tools.ietf.org/html/rfc6265#section-5.2.4
- *
- * @param string|null $path
- *
- * @return string
- */
- private function normalizePath($path)
- {
- $path = rtrim($path, '/');
- if (empty($path) or '/' !== substr($path, 0, 1)) {
- $path = '/';
- }
- return $path;
- }
- }
|