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