1 <?php 2 3 namespace GuzzleHttp\Cookie; 4 5 use Psr\Http\Message\RequestInterface; 6 use Psr\Http\Message\ResponseInterface; 7 8 /** 9 * Cookie jar that stores cookies as an array 10 */ 11 class CookieJar implements CookieJarInterface 12 { 13 /** 14 * @var SetCookie[] Loaded cookie data 15 */ 16 private $cookies = []; 17 18 /** 19 * @var bool 20 */ 21 private $strictMode; 22 23 /** 24 * @param bool $strictMode Set to true to throw exceptions when invalid 25 * cookies are added to the cookie jar. 26 * @param array $cookieArray Array of SetCookie objects or a hash of 27 * arrays that can be used with the SetCookie 28 * constructor 29 */ 30 public function __construct(bool $strictMode = false, array $cookieArray = []) 31 { 32 $this->strictMode = $strictMode; 33 34 foreach ($cookieArray as $cookie) { 35 if (!($cookie instanceof SetCookie)) { 36 $cookie = new SetCookie($cookie); 37 } 38 $this->setCookie($cookie); 39 } 40 } 41 42 /** 43 * Create a new Cookie jar from an associative array and domain. 44 * 45 * @param array $cookies Cookies to create the jar from 46 * @param string $domain Domain to set the cookies to 47 */ 48 public static function fromArray(array $cookies, string $domain): self 49 { 50 $cookieJar = new self(); 51 foreach ($cookies as $name => $value) { 52 $cookieJar->setCookie(new SetCookie([ 53 'Domain' => $domain, 54 'Name' => $name, 55 'Value' => $value, 56 'Discard' => true 57 ])); 58 } 59 60 return $cookieJar; 61 } 62 63 /** 64 * Evaluate if this cookie should be persisted to storage 65 * that survives between requests. 66 * 67 * @param SetCookie $cookie Being evaluated. 68 * @param bool $allowSessionCookies If we should persist session cookies 69 */ 70 public static function shouldPersist(SetCookie $cookie, bool $allowSessionCookies = false): bool 71 { 72 if ($cookie->getExpires() || $allowSessionCookies) { 73 if (!$cookie->getDiscard()) { 74 return true; 75 } 76 } 77 78 return false; 79 } 80 81 /** 82 * Finds and returns the cookie based on the name 83 * 84 * @param string $name cookie name to search for 85 * 86 * @return SetCookie|null cookie that was found or null if not found 87 */ 88 public function getCookieByName(string $name): ?SetCookie 89 { 90 foreach ($this->cookies as $cookie) { 91 if ($cookie->getName() !== null && \strcasecmp($cookie->getName(), $name) === 0) { 92 return $cookie; 93 } 94 } 95 96 return null; 97 } 98 99 /** 100 * @inheritDoc 101 */ 102 public function toArray(): array 103 { 104 return \array_map(static function (SetCookie $cookie): array { 105 return $cookie->toArray(); 106 }, $this->getIterator()->getArrayCopy()); 107 } 108 109 /** 110 * @inheritDoc 111 */ 112 public function clear(?string $domain = null, ?string $path = null, ?string $name = null): void 113 { 114 if (!$domain) { 115 $this->cookies = []; 116 return; 117 } elseif (!$path) { 118 $this->cookies = \array_filter( 119 $this->cookies, 120 static function (SetCookie $cookie) use ($domain): bool { 121 return !$cookie->matchesDomain($domain); 122 } 123 ); 124 } elseif (!$name) { 125 $this->cookies = \array_filter( 126 $this->cookies, 127 static function (SetCookie $cookie) use ($path, $domain): bool { 128 return !($cookie->matchesPath($path) && 129 $cookie->matchesDomain($domain)); 130 } 131 ); 132 } else { 133 $this->cookies = \array_filter( 134 $this->cookies, 135 static function (SetCookie $cookie) use ($path, $domain, $name) { 136 return !($cookie->getName() == $name && 137 $cookie->matchesPath($path) && 138 $cookie->matchesDomain($domain)); 139 } 140 ); 141 } 142 } 143 144 /** 145 * @inheritDoc 146 */ 147 public function clearSessionCookies(): void 148 { 149 $this->cookies = \array_filter( 150 $this->cookies, 151 static function (SetCookie $cookie): bool { 152 return !$cookie->getDiscard() && $cookie->getExpires(); 153 } 154 ); 155 } 156 157 /** 158 * @inheritDoc 159 */ 160 public function setCookie(SetCookie $cookie): bool 161 { 162 // If the name string is empty (but not 0), ignore the set-cookie 163 // string entirely. 164 $name = $cookie->getName(); 165 if (!$name && $name !== '0') { 166 return false; 167 } 168 169 // Only allow cookies with set and valid domain, name, value 170 $result = $cookie->validate(); 171 if ($result !== true) { 172 if ($this->strictMode) { 173 throw new \RuntimeException('Invalid cookie: ' . $result); 174 } 175 $this->removeCookieIfEmpty($cookie); 176 return false; 177 } 178 179 // Resolve conflicts with previously set cookies 180 foreach ($this->cookies as $i => $c) { 181 // Two cookies are identical, when their path, and domain are 182 // identical. 183 if ($c->getPath() != $cookie->getPath() || 184 $c->getDomain() != $cookie->getDomain() || 185 $c->getName() != $cookie->getName() 186 ) { 187 continue; 188 } 189 190 // The previously set cookie is a discard cookie and this one is 191 // not so allow the new cookie to be set 192 if (!$cookie->getDiscard() && $c->getDiscard()) { 193 unset($this->cookies[$i]); 194 continue; 195 } 196 197 // If the new cookie's expiration is further into the future, then 198 // replace the old cookie 199 if ($cookie->getExpires() > $c->getExpires()) { 200 unset($this->cookies[$i]); 201 continue; 202 } 203 204 // If the value has changed, we better change it 205 if ($cookie->getValue() !== $c->getValue()) { 206 unset($this->cookies[$i]); 207 continue; 208 } 209 210 // The cookie exists, so no need to continue 211 return false; 212 } 213 214 $this->cookies[] = $cookie; 215 216 return true; 217 } 218 219 public function count(): int 220 { 221 return \count($this->cookies); 222 } 223 224 /** 225 * @return \ArrayIterator<int, SetCookie> 226 */ 227 public function getIterator(): \ArrayIterator 228 { 229 return new \ArrayIterator(\array_values($this->cookies)); 230 } 231 232 public function extractCookies(RequestInterface $request, ResponseInterface $response): void 233 { 234 if ($cookieHeader = $response->getHeader('Set-Cookie')) { 235 foreach ($cookieHeader as $cookie) { 236 $sc = SetCookie::fromString($cookie); 237 if (!$sc->getDomain()) { 238 $sc->setDomain($request->getUri()->getHost()); 239 } 240 if (0 !== \strpos($sc->getPath(), '/')) { 241 $sc->setPath($this->getCookiePathFromRequest($request)); 242 } 243 if (!$sc->matchesDomain($request->getUri()->getHost())) { 244 continue; 245 } 246 // Note: At this point `$sc->getDomain()` being a public suffix should 247 // be rejected, but we don't want to pull in the full PSL dependency. 248 $this->setCookie($sc); 249 } 250 } 251 } 252 253 /** 254 * Computes cookie path following RFC 6265 section 5.1.4 255 * 256 * @link https://tools.ietf.org/html/rfc6265#section-5.1.4 257 */ 258 private function getCookiePathFromRequest(RequestInterface $request): string 259 { 260 $uriPath = $request->getUri()->getPath(); 261 if ('' === $uriPath) { 262 return '/'; 263 } 264 if (0 !== \strpos($uriPath, '/')) { 265 return '/'; 266 } 267 if ('/' === $uriPath) { 268 return '/'; 269 } 270 $lastSlashPos = \strrpos($uriPath, '/'); 271 if (0 === $lastSlashPos || false === $lastSlashPos) { 272 return '/'; 273 } 274 275 return \substr($uriPath, 0, $lastSlashPos); 276 } 277 278 public function withCookieHeader(RequestInterface $request): RequestInterface 279 { 280 $values = []; 281 $uri = $request->getUri(); 282 $scheme = $uri->getScheme(); 283 $host = $uri->getHost(); 284 $path = $uri->getPath() ?: '/'; 285 286 foreach ($this->cookies as $cookie) { 287 if ($cookie->matchesPath($path) && 288 $cookie->matchesDomain($host) && 289 !$cookie->isExpired() && 290 (!$cookie->getSecure() || $scheme === 'https') 291 ) { 292 $values[] = $cookie->getName() . '=' 293 . $cookie->getValue(); 294 } 295 } 296 297 return $values 298 ? $request->withHeader('Cookie', \implode('; ', $values)) 299 : $request; 300 } 301 302 /** 303 * If a cookie already exists and the server asks to set it again with a 304 * null value, the cookie must be deleted. 305 */ 306 private function removeCookieIfEmpty(SetCookie $cookie): void 307 { 308 $cookieValue = $cookie->getValue(); 309 if ($cookieValue === null || $cookieValue === '') { 310 $this->clear( 311 $cookie->getDomain(), 312 $cookie->getPath(), 313 $cookie->getName() 314 ); 315 } 316 } 317 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body