Security update to Drupal 8.4.6
[yaffs-website] / vendor / guzzlehttp / guzzle / src / Cookie / CookieJar.php
1 <?php
2 namespace GuzzleHttp\Cookie;
3
4 use Psr\Http\Message\RequestInterface;
5 use Psr\Http\Message\ResponseInterface;
6
7 /**
8  * Cookie jar that stores cookies as an array
9  */
10 class 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 null name
98         if ($name === null) {
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
108     public function toArray()
109     {
110         return array_map(function (SetCookie $cookie) {
111             return $cookie->toArray();
112         }, $this->getIterator()->getArrayCopy());
113     }
114
115     public function clear($domain = null, $path = null, $name = null)
116     {
117         if (!$domain) {
118             $this->cookies = [];
119             return;
120         } elseif (!$path) {
121             $this->cookies = array_filter(
122                 $this->cookies,
123                 function (SetCookie $cookie) use ($path, $domain) {
124                     return !$cookie->matchesDomain($domain);
125                 }
126             );
127         } elseif (!$name) {
128             $this->cookies = array_filter(
129                 $this->cookies,
130                 function (SetCookie $cookie) use ($path, $domain) {
131                     return !($cookie->matchesPath($path) &&
132                         $cookie->matchesDomain($domain));
133                 }
134             );
135         } else {
136             $this->cookies = array_filter(
137                 $this->cookies,
138                 function (SetCookie $cookie) use ($path, $domain, $name) {
139                     return !($cookie->getName() == $name &&
140                         $cookie->matchesPath($path) &&
141                         $cookie->matchesDomain($domain));
142                 }
143             );
144         }
145     }
146
147     public function clearSessionCookies()
148     {
149         $this->cookies = array_filter(
150             $this->cookies,
151             function (SetCookie $cookie) {
152                 return !$cookie->getDiscard() && $cookie->getExpires();
153             }
154         );
155     }
156
157     public function setCookie(SetCookie $cookie)
158     {
159         // If the name string is empty (but not 0), ignore the set-cookie
160         // string entirely.
161         $name = $cookie->getName();
162         if (!$name && $name !== '0') {
163             return false;
164         }
165
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);
171             } else {
172                 $this->removeCookieIfEmpty($cookie);
173                 return false;
174             }
175         }
176
177         // Resolve conflicts with previously set cookies
178         foreach ($this->cookies as $i => $c) {
179
180             // Two cookies are identical, when their path, and domain are
181             // identical.
182             if ($c->getPath() != $cookie->getPath() ||
183                 $c->getDomain() != $cookie->getDomain() ||
184                 $c->getName() != $cookie->getName()
185             ) {
186                 continue;
187             }
188
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]);
193                 continue;
194             }
195
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]);
200                 continue;
201             }
202
203             // If the value has changed, we better change it
204             if ($cookie->getValue() !== $c->getValue()) {
205                 unset($this->cookies[$i]);
206                 continue;
207             }
208
209             // The cookie exists, so no need to continue
210             return false;
211         }
212
213         $this->cookies[] = $cookie;
214
215         return true;
216     }
217
218     public function count()
219     {
220         return count($this->cookies);
221     }
222
223     public function getIterator()
224     {
225         return new \ArrayIterator(array_values($this->cookies));
226     }
227
228     public function extractCookies(
229         RequestInterface $request,
230         ResponseInterface $response
231     ) {
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());
237                 }
238                 if (0 !== strpos($sc->getPath(), '/')) {
239                     $sc->setPath($this->getCookiePathFromRequest($request));
240                 }
241                 $this->setCookie($sc);
242             }
243         }
244     }
245
246     /**
247      * Computes cookie path following RFC 6265 section 5.1.4
248      *
249      * @link https://tools.ietf.org/html/rfc6265#section-5.1.4
250      *
251      * @param RequestInterface $request
252      * @return string
253      */
254     private function getCookiePathFromRequest(RequestInterface $request)
255     {
256         $uriPath = $request->getUri()->getPath();
257         if (''  === $uriPath) {
258             return '/';
259         }
260         if (0 !== strpos($uriPath, '/')) {
261             return '/';
262         }
263         if ('/' === $uriPath) {
264             return '/';
265         }
266         if (0 === $lastSlashPos = strrpos($uriPath, '/')) {
267             return '/';
268         }
269
270         return substr($uriPath, 0, $lastSlashPos);
271     }
272
273     public function withCookieHeader(RequestInterface $request)
274     {
275         $values = [];
276         $uri = $request->getUri();
277         $scheme = $uri->getScheme();
278         $host = $uri->getHost();
279         $path = $uri->getPath() ?: '/';
280
281         foreach ($this->cookies as $cookie) {
282             if ($cookie->matchesPath($path) &&
283                 $cookie->matchesDomain($host) &&
284                 !$cookie->isExpired() &&
285                 (!$cookie->getSecure() || $scheme === 'https')
286             ) {
287                 $values[] = $cookie->getName() . '='
288                     . $cookie->getValue();
289             }
290         }
291
292         return $values
293             ? $request->withHeader('Cookie', implode('; ', $values))
294             : $request;
295     }
296
297     /**
298      * If a cookie already exists and the server asks to set it again with a
299      * null value, the cookie must be deleted.
300      *
301      * @param SetCookie $cookie
302      */
303     private function removeCookieIfEmpty(SetCookie $cookie)
304     {
305         $cookieValue = $cookie->getValue();
306         if ($cookieValue === null || $cookieValue === '') {
307             $this->clear(
308                 $cookie->getDomain(),
309                 $cookie->getPath(),
310                 $cookie->getName()
311             );
312         }
313     }
314 }