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