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