vendor/symfony/http-foundation/Cookie.php line 19

  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\HttpFoundation;
  11. /**
  12.  * Represents a cookie.
  13.  *
  14.  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
  15.  */
  16. class Cookie
  17. {
  18.     public const SAMESITE_NONE 'none';
  19.     public const SAMESITE_LAX 'lax';
  20.     public const SAMESITE_STRICT 'strict';
  21.     protected $name;
  22.     protected $value;
  23.     protected $domain;
  24.     protected $expire;
  25.     protected $path;
  26.     protected $secure;
  27.     protected $httpOnly;
  28.     private bool $raw;
  29.     private ?string $sameSite null;
  30.     private bool $secureDefault false;
  31.     private const RESERVED_CHARS_LIST "=,; \t\r\n\v\f";
  32.     private const RESERVED_CHARS_FROM = ['='','';'' '"\t""\r""\n""\v""\f"];
  33.     private const RESERVED_CHARS_TO = ['%3D''%2C''%3B''%20''%09''%0D''%0A''%0B''%0C'];
  34.     /**
  35.      * Creates cookie from raw header string.
  36.      */
  37.     public static function fromString(string $cookiebool $decode false): static
  38.     {
  39.         $data = [
  40.             'expires' => 0,
  41.             'path' => '/',
  42.             'domain' => null,
  43.             'secure' => false,
  44.             'httponly' => false,
  45.             'raw' => !$decode,
  46.             'samesite' => null,
  47.         ];
  48.         $parts HeaderUtils::split($cookie';=');
  49.         $part array_shift($parts);
  50.         $name $decode urldecode($part[0]) : $part[0];
  51.         $value = isset($part[1]) ? ($decode urldecode($part[1]) : $part[1]) : null;
  52.         $data HeaderUtils::combine($parts) + $data;
  53.         $data['expires'] = self::expiresTimestamp($data['expires']);
  54.         if (isset($data['max-age']) && ($data['max-age'] > || $data['expires'] > time())) {
  55.             $data['expires'] = time() + (int) $data['max-age'];
  56.         }
  57.         return new static($name$value$data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']);
  58.     }
  59.     /**
  60.      * @see self::__construct
  61.      *
  62.      * @param self::SAMESITE_*|''|null $sameSite
  63.      */
  64.     public static function create(string $namestring $value nullint|string|\DateTimeInterface $expire 0, ?string $path '/'string $domain nullbool $secure nullbool $httpOnly truebool $raw false, ?string $sameSite self::SAMESITE_LAX): self
  65.     {
  66.         return new self($name$value$expire$path$domain$secure$httpOnly$raw$sameSite);
  67.     }
  68.     /**
  69.      * @param string                        $name     The name of the cookie
  70.      * @param string|null                   $value    The value of the cookie
  71.      * @param int|string|\DateTimeInterface $expire   The time the cookie expires
  72.      * @param string|null                   $path     The path on the server in which the cookie will be available on
  73.      * @param string|null                   $domain   The domain that the cookie is available to
  74.      * @param bool|null                     $secure   Whether the client should send back the cookie only over HTTPS or null to auto-enable this when the request is already using HTTPS
  75.      * @param bool                          $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
  76.      * @param bool                          $raw      Whether the cookie value should be sent with no url encoding
  77.      * @param self::SAMESITE_*|''|null      $sameSite Whether the cookie will be available for cross-site requests
  78.      *
  79.      * @throws \InvalidArgumentException
  80.      */
  81.     public function __construct(string $namestring $value nullint|string|\DateTimeInterface $expire 0, ?string $path '/'string $domain nullbool $secure nullbool $httpOnly truebool $raw false, ?string $sameSite self::SAMESITE_LAX)
  82.     {
  83.         // from PHP source code
  84.         if ($raw && false !== strpbrk($nameself::RESERVED_CHARS_LIST)) {
  85.             throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.'$name));
  86.         }
  87.         if (empty($name)) {
  88.             throw new \InvalidArgumentException('The cookie name cannot be empty.');
  89.         }
  90.         $this->name $name;
  91.         $this->value $value;
  92.         $this->domain $domain;
  93.         $this->expire self::expiresTimestamp($expire);
  94.         $this->path = empty($path) ? '/' $path;
  95.         $this->secure $secure;
  96.         $this->httpOnly $httpOnly;
  97.         $this->raw $raw;
  98.         $this->sameSite $this->withSameSite($sameSite)->sameSite;
  99.     }
  100.     /**
  101.      * Creates a cookie copy with a new value.
  102.      */
  103.     public function withValue(?string $value): static
  104.     {
  105.         $cookie = clone $this;
  106.         $cookie->value $value;
  107.         return $cookie;
  108.     }
  109.     /**
  110.      * Creates a cookie copy with a new domain that the cookie is available to.
  111.      */
  112.     public function withDomain(?string $domain): static
  113.     {
  114.         $cookie = clone $this;
  115.         $cookie->domain $domain;
  116.         return $cookie;
  117.     }
  118.     /**
  119.      * Creates a cookie copy with a new time the cookie expires.
  120.      */
  121.     public function withExpires(int|string|\DateTimeInterface $expire 0): static
  122.     {
  123.         $cookie = clone $this;
  124.         $cookie->expire self::expiresTimestamp($expire);
  125.         return $cookie;
  126.     }
  127.     /**
  128.      * Converts expires formats to a unix timestamp.
  129.      */
  130.     private static function expiresTimestamp(int|string|\DateTimeInterface $expire 0): int
  131.     {
  132.         // convert expiration time to a Unix timestamp
  133.         if ($expire instanceof \DateTimeInterface) {
  134.             $expire $expire->format('U');
  135.         } elseif (!is_numeric($expire)) {
  136.             $expire strtotime($expire);
  137.             if (false === $expire) {
  138.                 throw new \InvalidArgumentException('The cookie expiration time is not valid.');
  139.             }
  140.         }
  141.         return $expire ? (int) $expire 0;
  142.     }
  143.     /**
  144.      * Creates a cookie copy with a new path on the server in which the cookie will be available on.
  145.      */
  146.     public function withPath(string $path): static
  147.     {
  148.         $cookie = clone $this;
  149.         $cookie->path '' === $path '/' $path;
  150.         return $cookie;
  151.     }
  152.     /**
  153.      * Creates a cookie copy that only be transmitted over a secure HTTPS connection from the client.
  154.      */
  155.     public function withSecure(bool $secure true): static
  156.     {
  157.         $cookie = clone $this;
  158.         $cookie->secure $secure;
  159.         return $cookie;
  160.     }
  161.     /**
  162.      * Creates a cookie copy that be accessible only through the HTTP protocol.
  163.      */
  164.     public function withHttpOnly(bool $httpOnly true): static
  165.     {
  166.         $cookie = clone $this;
  167.         $cookie->httpOnly $httpOnly;
  168.         return $cookie;
  169.     }
  170.     /**
  171.      * Creates a cookie copy that uses no url encoding.
  172.      */
  173.     public function withRaw(bool $raw true): static
  174.     {
  175.         if ($raw && false !== strpbrk($this->nameself::RESERVED_CHARS_LIST)) {
  176.             throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.'$this->name));
  177.         }
  178.         $cookie = clone $this;
  179.         $cookie->raw $raw;
  180.         return $cookie;
  181.     }
  182.     /**
  183.      * Creates a cookie copy with SameSite attribute.
  184.      *
  185.      * @param self::SAMESITE_*|''|null $sameSite
  186.      */
  187.     public function withSameSite(?string $sameSite): static
  188.     {
  189.         if ('' === $sameSite) {
  190.             $sameSite null;
  191.         } elseif (null !== $sameSite) {
  192.             $sameSite strtolower($sameSite);
  193.         }
  194.         if (!\in_array($sameSite, [self::SAMESITE_LAXself::SAMESITE_STRICTself::SAMESITE_NONEnull], true)) {
  195.             throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.');
  196.         }
  197.         $cookie = clone $this;
  198.         $cookie->sameSite $sameSite;
  199.         return $cookie;
  200.     }
  201.     /**
  202.      * Returns the cookie as a string.
  203.      */
  204.     public function __toString(): string
  205.     {
  206.         if ($this->isRaw()) {
  207.             $str $this->getName();
  208.         } else {
  209.             $str str_replace(self::RESERVED_CHARS_FROMself::RESERVED_CHARS_TO$this->getName());
  210.         }
  211.         $str .= '=';
  212.         if ('' === (string) $this->getValue()) {
  213.             $str .= 'deleted; expires='.gmdate('D, d M Y H:i:s T'time() - 31536001).'; Max-Age=0';
  214.         } else {
  215.             $str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue());
  216.             if (!== $this->getExpiresTime()) {
  217.                 $str .= '; expires='.gmdate('D, d M Y H:i:s T'$this->getExpiresTime()).'; Max-Age='.$this->getMaxAge();
  218.             }
  219.         }
  220.         if ($this->getPath()) {
  221.             $str .= '; path='.$this->getPath();
  222.         }
  223.         if ($this->getDomain()) {
  224.             $str .= '; domain='.$this->getDomain();
  225.         }
  226.         if (true === $this->isSecure()) {
  227.             $str .= '; secure';
  228.         }
  229.         if (true === $this->isHttpOnly()) {
  230.             $str .= '; httponly';
  231.         }
  232.         if (null !== $this->getSameSite()) {
  233.             $str .= '; samesite='.$this->getSameSite();
  234.         }
  235.         return $str;
  236.     }
  237.     /**
  238.      * Gets the name of the cookie.
  239.      */
  240.     public function getName(): string
  241.     {
  242.         return $this->name;
  243.     }
  244.     /**
  245.      * Gets the value of the cookie.
  246.      */
  247.     public function getValue(): ?string
  248.     {
  249.         return $this->value;
  250.     }
  251.     /**
  252.      * Gets the domain that the cookie is available to.
  253.      */
  254.     public function getDomain(): ?string
  255.     {
  256.         return $this->domain;
  257.     }
  258.     /**
  259.      * Gets the time the cookie expires.
  260.      */
  261.     public function getExpiresTime(): int
  262.     {
  263.         return $this->expire;
  264.     }
  265.     /**
  266.      * Gets the max-age attribute.
  267.      */
  268.     public function getMaxAge(): int
  269.     {
  270.         $maxAge $this->expire time();
  271.         return >= $maxAge $maxAge;
  272.     }
  273.     /**
  274.      * Gets the path on the server in which the cookie will be available on.
  275.      */
  276.     public function getPath(): string
  277.     {
  278.         return $this->path;
  279.     }
  280.     /**
  281.      * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client.
  282.      */
  283.     public function isSecure(): bool
  284.     {
  285.         return $this->secure ?? $this->secureDefault;
  286.     }
  287.     /**
  288.      * Checks whether the cookie will be made accessible only through the HTTP protocol.
  289.      */
  290.     public function isHttpOnly(): bool
  291.     {
  292.         return $this->httpOnly;
  293.     }
  294.     /**
  295.      * Whether this cookie is about to be cleared.
  296.      */
  297.     public function isCleared(): bool
  298.     {
  299.         return !== $this->expire && $this->expire time();
  300.     }
  301.     /**
  302.      * Checks if the cookie value should be sent with no url encoding.
  303.      */
  304.     public function isRaw(): bool
  305.     {
  306.         return $this->raw;
  307.     }
  308.     /**
  309.      * @return self::SAMESITE_*|null
  310.      */
  311.     public function getSameSite(): ?string
  312.     {
  313.         return $this->sameSite;
  314.     }
  315.     /**
  316.      * @param bool $default The default value of the "secure" flag when it is set to null
  317.      */
  318.     public function setSecureDefault(bool $default): void
  319.     {
  320.         $this->secureDefault $default;
  321.     }
  322. }