| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 | <?phpnamespace GuzzleHttp\Cookie;use Psr\Http\Message\RequestInterface;use Psr\Http\Message\ResponseInterface;/** * Cookie jar that stores cookies as an array */class CookieJar implements CookieJarInterface{    /**     * @var SetCookie[] Loaded cookie data     */    private $cookies = [];    /**     * @var bool     */    private $strictMode;    /**     * @param bool  $strictMode  Set to true to throw exceptions when invalid     *                           cookies are added to the cookie jar.     * @param array $cookieArray Array of SetCookie objects or a hash of     *                           arrays that can be used with the SetCookie     *                           constructor     */    public function __construct(bool $strictMode = false, array $cookieArray = [])    {        $this->strictMode = $strictMode;        foreach ($cookieArray as $cookie) {            if (!($cookie instanceof SetCookie)) {                $cookie = new SetCookie($cookie);            }            $this->setCookie($cookie);        }    }    /**     * Create a new Cookie jar from an associative array and domain.     *     * @param array  $cookies Cookies to create the jar from     * @param string $domain  Domain to set the cookies to     */    public static function fromArray(array $cookies, string $domain): self    {        $cookieJar = new self();        foreach ($cookies as $name => $value) {            $cookieJar->setCookie(new SetCookie([                'Domain' => $domain,                'Name' => $name,                'Value' => $value,                'Discard' => true,            ]));        }        return $cookieJar;    }    /**     * Evaluate if this cookie should be persisted to storage     * that survives between requests.     *     * @param SetCookie $cookie              Being evaluated.     * @param bool      $allowSessionCookies If we should persist session cookies     */    public static function shouldPersist(SetCookie $cookie, bool $allowSessionCookies = false): bool    {        if ($cookie->getExpires() || $allowSessionCookies) {            if (!$cookie->getDiscard()) {                return true;            }        }        return false;    }    /**     * Finds and returns the cookie based on the name     *     * @param string $name cookie name to search for     *     * @return SetCookie|null cookie that was found or null if not found     */    public function getCookieByName(string $name): ?SetCookie    {        foreach ($this->cookies as $cookie) {            if ($cookie->getName() !== null && \strcasecmp($cookie->getName(), $name) === 0) {                return $cookie;            }        }        return null;    }    public function toArray(): array    {        return \array_map(static function (SetCookie $cookie): array {            return $cookie->toArray();        }, $this->getIterator()->getArrayCopy());    }    public function clear(string $domain = null, string $path = null, string $name = null): void    {        if (!$domain) {            $this->cookies = [];            return;        } elseif (!$path) {            $this->cookies = \array_filter(                $this->cookies,                static function (SetCookie $cookie) use ($domain): bool {                    return !$cookie->matchesDomain($domain);                }            );        } elseif (!$name) {            $this->cookies = \array_filter(                $this->cookies,                static function (SetCookie $cookie) use ($path, $domain): bool {                    return !($cookie->matchesPath($path)                        && $cookie->matchesDomain($domain));                }            );        } else {            $this->cookies = \array_filter(                $this->cookies,                static function (SetCookie $cookie) use ($path, $domain, $name) {                    return !($cookie->getName() == $name                        && $cookie->matchesPath($path)                        && $cookie->matchesDomain($domain));                }            );        }    }    public function clearSessionCookies(): void    {        $this->cookies = \array_filter(            $this->cookies,            static function (SetCookie $cookie): bool {                return !$cookie->getDiscard() && $cookie->getExpires();            }        );    }    public function setCookie(SetCookie $cookie): bool    {        // If the name string is empty (but not 0), ignore the set-cookie        // string entirely.        $name = $cookie->getName();        if (!$name && $name !== '0') {            return false;        }        // Only allow cookies with set and valid domain, name, value        $result = $cookie->validate();        if ($result !== true) {            if ($this->strictMode) {                throw new \RuntimeException('Invalid cookie: '.$result);            }            $this->removeCookieIfEmpty($cookie);            return false;        }        // Resolve conflicts with previously set cookies        foreach ($this->cookies as $i => $c) {            // Two cookies are identical, when their path, and domain are            // identical.            if ($c->getPath() != $cookie->getPath()                || $c->getDomain() != $cookie->getDomain()                || $c->getName() != $cookie->getName()            ) {                continue;            }            // The previously set cookie is a discard cookie and this one is            // not so allow the new cookie to be set            if (!$cookie->getDiscard() && $c->getDiscard()) {                unset($this->cookies[$i]);                continue;            }            // If the new cookie's expiration is further into the future, then            // replace the old cookie            if ($cookie->getExpires() > $c->getExpires()) {                unset($this->cookies[$i]);                continue;            }            // If the value has changed, we better change it            if ($cookie->getValue() !== $c->getValue()) {                unset($this->cookies[$i]);                continue;            }            // The cookie exists, so no need to continue            return false;        }        $this->cookies[] = $cookie;        return true;    }    public function count(): int    {        return \count($this->cookies);    }    /**     * @return \ArrayIterator<int, SetCookie>     */    public function getIterator(): \ArrayIterator    {        return new \ArrayIterator(\array_values($this->cookies));    }    public function extractCookies(RequestInterface $request, ResponseInterface $response): void    {        if ($cookieHeader = $response->getHeader('Set-Cookie')) {            foreach ($cookieHeader as $cookie) {                $sc = SetCookie::fromString($cookie);                if (!$sc->getDomain()) {                    $sc->setDomain($request->getUri()->getHost());                }                if (0 !== \strpos($sc->getPath(), '/')) {                    $sc->setPath($this->getCookiePathFromRequest($request));                }                if (!$sc->matchesDomain($request->getUri()->getHost())) {                    continue;                }                // Note: At this point `$sc->getDomain()` being a public suffix should                // be rejected, but we don't want to pull in the full PSL dependency.                $this->setCookie($sc);            }        }    }    /**     * Computes cookie path following RFC 6265 section 5.1.4     *     * @see https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4     */    private function getCookiePathFromRequest(RequestInterface $request): string    {        $uriPath = $request->getUri()->getPath();        if ('' === $uriPath) {            return '/';        }        if (0 !== \strpos($uriPath, '/')) {            return '/';        }        if ('/' === $uriPath) {            return '/';        }        $lastSlashPos = \strrpos($uriPath, '/');        if (0 === $lastSlashPos || false === $lastSlashPos) {            return '/';        }        return \substr($uriPath, 0, $lastSlashPos);    }    public function withCookieHeader(RequestInterface $request): RequestInterface    {        $values = [];        $uri = $request->getUri();        $scheme = $uri->getScheme();        $host = $uri->getHost();        $path = $uri->getPath() ?: '/';        foreach ($this->cookies as $cookie) {            if ($cookie->matchesPath($path)                && $cookie->matchesDomain($host)                && !$cookie->isExpired()                && (!$cookie->getSecure() || $scheme === 'https')            ) {                $values[] = $cookie->getName().'='                    .$cookie->getValue();            }        }        return $values            ? $request->withHeader('Cookie', \implode('; ', $values))            : $request;    }    /**     * If a cookie already exists and the server asks to set it again with a     * null value, the cookie must be deleted.     */    private function removeCookieIfEmpty(SetCookie $cookie): void    {        $cookieValue = $cookie->getValue();        if ($cookieValue === null || $cookieValue === '') {            $this->clear(                $cookie->getDomain(),                $cookie->getPath(),                $cookie->getName()            );        }    }}
 |