2 namespace GuzzleHttp\Cookie;
4 use Psr\Http\Message\RequestInterface;
5 use Psr\Http\Message\ResponseInterface;
8 * Cookie jar that stores cookies as an array
10 class CookieJar implements CookieJarInterface
12 /** @var SetCookie[] Loaded cookie data */
13 private $cookies = [];
19 * @param bool $strictMode Set to true to throw exceptions when invalid
20 * cookies are added to the cookie jar.
21 * @param array $cookieArray Array of SetCookie objects or a hash of
22 * arrays that can be used with the SetCookie
25 public function __construct($strictMode = false, $cookieArray = [])
27 $this->strictMode = $strictMode;
29 foreach ($cookieArray as $cookie) {
30 if (!($cookie instanceof SetCookie)) {
31 $cookie = new SetCookie($cookie);
33 $this->setCookie($cookie);
38 * Create a new Cookie jar from an associative array and domain.
40 * @param array $cookies Cookies to create the jar from
41 * @param string $domain Domain to set the cookies to
45 public static function fromArray(array $cookies, $domain)
47 $cookieJar = new self();
48 foreach ($cookies as $name => $value) {
49 $cookieJar->setCookie(new SetCookie([
63 public static function getCookieValue($value)
69 * Evaluate if this cookie should be persisted to storage
70 * that survives between requests.
72 * @param SetCookie $cookie Being evaluated.
73 * @param bool $allowSessionCookies If we should persist session cookies
76 public static function shouldPersist(
78 $allowSessionCookies = false
80 if ($cookie->getExpires() || $allowSessionCookies) {
81 if (!$cookie->getDiscard()) {
90 * Finds and returns the cookie based on the name
92 * @param string $name cookie name to search for
93 * @return SetCookie|null cookie that was found or null if not found
95 public function getCookieByName($name)
97 // don't allow a null name
101 foreach($this->cookies as $cookie) {
102 if($cookie->getName() !== null && strcasecmp($cookie->getName(), $name) === 0) {
108 public function toArray()
110 return array_map(function (SetCookie $cookie) {
111 return $cookie->toArray();
112 }, $this->getIterator()->getArrayCopy());
115 public function clear($domain = null, $path = null, $name = null)
121 $this->cookies = array_filter(
123 function (SetCookie $cookie) use ($path, $domain) {
124 return !$cookie->matchesDomain($domain);
128 $this->cookies = array_filter(
130 function (SetCookie $cookie) use ($path, $domain) {
131 return !($cookie->matchesPath($path) &&
132 $cookie->matchesDomain($domain));
136 $this->cookies = array_filter(
138 function (SetCookie $cookie) use ($path, $domain, $name) {
139 return !($cookie->getName() == $name &&
140 $cookie->matchesPath($path) &&
141 $cookie->matchesDomain($domain));
147 public function clearSessionCookies()
149 $this->cookies = array_filter(
151 function (SetCookie $cookie) {
152 return !$cookie->getDiscard() && $cookie->getExpires();
157 public function setCookie(SetCookie $cookie)
159 // If the name string is empty (but not 0), ignore the set-cookie
161 $name = $cookie->getName();
162 if (!$name && $name !== '0') {
166 // Only allow cookies with set and valid domain, name, value
167 $result = $cookie->validate();
168 if ($result !== true) {
169 if ($this->strictMode) {
170 throw new \RuntimeException('Invalid cookie: ' . $result);
172 $this->removeCookieIfEmpty($cookie);
177 // Resolve conflicts with previously set cookies
178 foreach ($this->cookies as $i => $c) {
180 // Two cookies are identical, when their path, and domain are
182 if ($c->getPath() != $cookie->getPath() ||
183 $c->getDomain() != $cookie->getDomain() ||
184 $c->getName() != $cookie->getName()
189 // The previously set cookie is a discard cookie and this one is
190 // not so allow the new cookie to be set
191 if (!$cookie->getDiscard() && $c->getDiscard()) {
192 unset($this->cookies[$i]);
196 // If the new cookie's expiration is further into the future, then
197 // replace the old cookie
198 if ($cookie->getExpires() > $c->getExpires()) {
199 unset($this->cookies[$i]);
203 // If the value has changed, we better change it
204 if ($cookie->getValue() !== $c->getValue()) {
205 unset($this->cookies[$i]);
209 // The cookie exists, so no need to continue
213 $this->cookies[] = $cookie;
218 public function count()
220 return count($this->cookies);
223 public function getIterator()
225 return new \ArrayIterator(array_values($this->cookies));
228 public function extractCookies(
229 RequestInterface $request,
230 ResponseInterface $response
232 if ($cookieHeader = $response->getHeader('Set-Cookie')) {
233 foreach ($cookieHeader as $cookie) {
234 $sc = SetCookie::fromString($cookie);
235 if (!$sc->getDomain()) {
236 $sc->setDomain($request->getUri()->getHost());
238 if (0 !== strpos($sc->getPath(), '/')) {
239 $sc->setPath($this->getCookiePathFromRequest($request));
241 $this->setCookie($sc);
247 * Computes cookie path following RFC 6265 section 5.1.4
249 * @link https://tools.ietf.org/html/rfc6265#section-5.1.4
251 * @param RequestInterface $request
254 private function getCookiePathFromRequest(RequestInterface $request)
256 $uriPath = $request->getUri()->getPath();
257 if ('' === $uriPath) {
260 if (0 !== strpos($uriPath, '/')) {
263 if ('/' === $uriPath) {
266 if (0 === $lastSlashPos = strrpos($uriPath, '/')) {
270 return substr($uriPath, 0, $lastSlashPos);
273 public function withCookieHeader(RequestInterface $request)
276 $uri = $request->getUri();
277 $scheme = $uri->getScheme();
278 $host = $uri->getHost();
279 $path = $uri->getPath() ?: '/';
281 foreach ($this->cookies as $cookie) {
282 if ($cookie->matchesPath($path) &&
283 $cookie->matchesDomain($host) &&
284 !$cookie->isExpired() &&
285 (!$cookie->getSecure() || $scheme === 'https')
287 $values[] = $cookie->getName() . '='
288 . $cookie->getValue();
293 ? $request->withHeader('Cookie', implode('; ', $values))
298 * If a cookie already exists and the server asks to set it again with a
299 * null value, the cookie must be deleted.
301 * @param SetCookie $cookie
303 private function removeCookieIfEmpty(SetCookie $cookie)
305 $cookieValue = $cookie->getValue();
306 if ($cookieValue === null || $cookieValue === '') {
308 $cookie->getDomain(),