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