source: vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php

Last change on this file was e3d4e0a, checked in by Vlado 222039 <vlado.popovski@…>, 7 days ago

Upload project files

  • Property mode set to 100644
File size: 13.9 KB
Line 
1<?php
2
3namespace GuzzleHttp\Cookie;
4
5/**
6 * Set-Cookie object
7 */
8class SetCookie
9{
10 /**
11 * @var array
12 */
13 private static $defaults = [
14 'Name' => null,
15 'Value' => null,
16 'Domain' => null,
17 'Path' => '/',
18 'Max-Age' => null,
19 'Expires' => null,
20 'Secure' => false,
21 'Discard' => false,
22 'HttpOnly' => false,
23 ];
24
25 /**
26 * @var array Cookie data
27 */
28 private $data;
29
30 /**
31 * Create a new SetCookie object from a string.
32 *
33 * @param string $cookie Set-Cookie header string
34 */
35 public static function fromString(string $cookie): self
36 {
37 // Create the default return array
38 $data = self::$defaults;
39 // Explode the cookie string using a series of semicolons
40 $pieces = \array_filter(\array_map('trim', \explode(';', $cookie)));
41 // The name of the cookie (first kvp) must exist and include an equal sign.
42 if (!isset($pieces[0]) || \strpos($pieces[0], '=') === false) {
43 return new self($data);
44 }
45
46 // Add the cookie pieces into the parsed data array
47 foreach ($pieces as $part) {
48 $cookieParts = \explode('=', $part, 2);
49 $key = \trim($cookieParts[0]);
50 $value = isset($cookieParts[1])
51 ? \trim($cookieParts[1], " \n\r\t\0\x0B")
52 : true;
53
54 // Only check for non-cookies when cookies have been found
55 if (!isset($data['Name'])) {
56 $data['Name'] = $key;
57 $data['Value'] = $value;
58 } else {
59 foreach (\array_keys(self::$defaults) as $search) {
60 if (!\strcasecmp($search, $key)) {
61 if ($search === 'Max-Age') {
62 if (is_numeric($value)) {
63 $data[$search] = (int) $value;
64 }
65 } else {
66 $data[$search] = $value;
67 }
68 continue 2;
69 }
70 }
71 $data[$key] = $value;
72 }
73 }
74
75 return new self($data);
76 }
77
78 /**
79 * @param array $data Array of cookie data provided by a Cookie parser
80 */
81 public function __construct(array $data = [])
82 {
83 $this->data = self::$defaults;
84
85 if (isset($data['Name'])) {
86 $this->setName($data['Name']);
87 }
88
89 if (isset($data['Value'])) {
90 $this->setValue($data['Value']);
91 }
92
93 if (isset($data['Domain'])) {
94 $this->setDomain($data['Domain']);
95 }
96
97 if (isset($data['Path'])) {
98 $this->setPath($data['Path']);
99 }
100
101 if (isset($data['Max-Age'])) {
102 $this->setMaxAge($data['Max-Age']);
103 }
104
105 if (isset($data['Expires'])) {
106 $this->setExpires($data['Expires']);
107 }
108
109 if (isset($data['Secure'])) {
110 $this->setSecure($data['Secure']);
111 }
112
113 if (isset($data['Discard'])) {
114 $this->setDiscard($data['Discard']);
115 }
116
117 if (isset($data['HttpOnly'])) {
118 $this->setHttpOnly($data['HttpOnly']);
119 }
120
121 // Set the remaining values that don't have extra validation logic
122 foreach (array_diff(array_keys($data), array_keys(self::$defaults)) as $key) {
123 $this->data[$key] = $data[$key];
124 }
125
126 // Extract the Expires value and turn it into a UNIX timestamp if needed
127 if (!$this->getExpires() && $this->getMaxAge()) {
128 // Calculate the Expires date
129 $this->setExpires(\time() + $this->getMaxAge());
130 } elseif (null !== ($expires = $this->getExpires()) && !\is_numeric($expires)) {
131 $this->setExpires($expires);
132 }
133 }
134
135 public function __toString()
136 {
137 $str = $this->data['Name'].'='.($this->data['Value'] ?? '').'; ';
138 foreach ($this->data as $k => $v) {
139 if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
140 if ($k === 'Expires') {
141 $str .= 'Expires='.\gmdate('D, d M Y H:i:s \G\M\T', $v).'; ';
142 } else {
143 $str .= ($v === true ? $k : "{$k}={$v}").'; ';
144 }
145 }
146 }
147
148 return \rtrim($str, '; ');
149 }
150
151 public function toArray(): array
152 {
153 return $this->data;
154 }
155
156 /**
157 * Get the cookie name.
158 *
159 * @return string
160 */
161 public function getName()
162 {
163 return $this->data['Name'];
164 }
165
166 /**
167 * Set the cookie name.
168 *
169 * @param string $name Cookie name
170 */
171 public function setName($name): void
172 {
173 if (!is_string($name)) {
174 trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
175 }
176
177 $this->data['Name'] = (string) $name;
178 }
179
180 /**
181 * Get the cookie value.
182 *
183 * @return string|null
184 */
185 public function getValue()
186 {
187 return $this->data['Value'];
188 }
189
190 /**
191 * Set the cookie value.
192 *
193 * @param string $value Cookie value
194 */
195 public function setValue($value): void
196 {
197 if (!is_string($value)) {
198 trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
199 }
200
201 $this->data['Value'] = (string) $value;
202 }
203
204 /**
205 * Get the domain.
206 *
207 * @return string|null
208 */
209 public function getDomain()
210 {
211 return $this->data['Domain'];
212 }
213
214 /**
215 * Set the domain of the cookie.
216 *
217 * @param string|null $domain
218 */
219 public function setDomain($domain): void
220 {
221 if (!is_string($domain) && null !== $domain) {
222 trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
223 }
224
225 $this->data['Domain'] = null === $domain ? null : (string) $domain;
226 }
227
228 /**
229 * Get the path.
230 *
231 * @return string
232 */
233 public function getPath()
234 {
235 return $this->data['Path'];
236 }
237
238 /**
239 * Set the path of the cookie.
240 *
241 * @param string $path Path of the cookie
242 */
243 public function setPath($path): void
244 {
245 if (!is_string($path)) {
246 trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a string to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
247 }
248
249 $this->data['Path'] = (string) $path;
250 }
251
252 /**
253 * Maximum lifetime of the cookie in seconds.
254 *
255 * @return int|null
256 */
257 public function getMaxAge()
258 {
259 return null === $this->data['Max-Age'] ? null : (int) $this->data['Max-Age'];
260 }
261
262 /**
263 * Set the max-age of the cookie.
264 *
265 * @param int|null $maxAge Max age of the cookie in seconds
266 */
267 public function setMaxAge($maxAge): void
268 {
269 if (!is_int($maxAge) && null !== $maxAge) {
270 trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
271 }
272
273 $this->data['Max-Age'] = $maxAge === null ? null : (int) $maxAge;
274 }
275
276 /**
277 * The UNIX timestamp when the cookie Expires.
278 *
279 * @return string|int|null
280 */
281 public function getExpires()
282 {
283 return $this->data['Expires'];
284 }
285
286 /**
287 * Set the unix timestamp for which the cookie will expire.
288 *
289 * @param int|string|null $timestamp Unix timestamp or any English textual datetime description.
290 */
291 public function setExpires($timestamp): void
292 {
293 if (!is_int($timestamp) && !is_string($timestamp) && null !== $timestamp) {
294 trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int, string or null to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
295 }
296
297 $this->data['Expires'] = null === $timestamp ? null : (\is_numeric($timestamp) ? (int) $timestamp : \strtotime((string) $timestamp));
298 }
299
300 /**
301 * Get whether or not this is a secure cookie.
302 *
303 * @return bool
304 */
305 public function getSecure()
306 {
307 return $this->data['Secure'];
308 }
309
310 /**
311 * Set whether or not the cookie is secure.
312 *
313 * @param bool $secure Set to true or false if secure
314 */
315 public function setSecure($secure): void
316 {
317 if (!is_bool($secure)) {
318 trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
319 }
320
321 $this->data['Secure'] = (bool) $secure;
322 }
323
324 /**
325 * Get whether or not this is a session cookie.
326 *
327 * @return bool|null
328 */
329 public function getDiscard()
330 {
331 return $this->data['Discard'];
332 }
333
334 /**
335 * Set whether or not this is a session cookie.
336 *
337 * @param bool $discard Set to true or false if this is a session cookie
338 */
339 public function setDiscard($discard): void
340 {
341 if (!is_bool($discard)) {
342 trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
343 }
344
345 $this->data['Discard'] = (bool) $discard;
346 }
347
348 /**
349 * Get whether or not this is an HTTP only cookie.
350 *
351 * @return bool
352 */
353 public function getHttpOnly()
354 {
355 return $this->data['HttpOnly'];
356 }
357
358 /**
359 * Set whether or not this is an HTTP only cookie.
360 *
361 * @param bool $httpOnly Set to true or false if this is HTTP only
362 */
363 public function setHttpOnly($httpOnly): void
364 {
365 if (!is_bool($httpOnly)) {
366 trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing a bool to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
367 }
368
369 $this->data['HttpOnly'] = (bool) $httpOnly;
370 }
371
372 /**
373 * Check if the cookie matches a path value.
374 *
375 * A request-path path-matches a given cookie-path if at least one of
376 * the following conditions holds:
377 *
378 * - The cookie-path and the request-path are identical.
379 * - The cookie-path is a prefix of the request-path, and the last
380 * character of the cookie-path is %x2F ("/").
381 * - The cookie-path is a prefix of the request-path, and the first
382 * character of the request-path that is not included in the cookie-
383 * path is a %x2F ("/") character.
384 *
385 * @param string $requestPath Path to check against
386 */
387 public function matchesPath(string $requestPath): bool
388 {
389 $cookiePath = $this->getPath();
390
391 // Match on exact matches or when path is the default empty "/"
392 if ($cookiePath === '/' || $cookiePath == $requestPath) {
393 return true;
394 }
395
396 // Ensure that the cookie-path is a prefix of the request path.
397 if (0 !== \strpos($requestPath, $cookiePath)) {
398 return false;
399 }
400
401 // Match if the last character of the cookie-path is "/"
402 if (\substr($cookiePath, -1, 1) === '/') {
403 return true;
404 }
405
406 // Match if the first character not included in cookie path is "/"
407 return \substr($requestPath, \strlen($cookiePath), 1) === '/';
408 }
409
410 /**
411 * Check if the cookie matches a domain value.
412 *
413 * @param string $domain Domain to check against
414 */
415 public function matchesDomain(string $domain): bool
416 {
417 $cookieDomain = $this->getDomain();
418 if (null === $cookieDomain) {
419 return true;
420 }
421
422 // Remove the leading '.' as per spec in RFC 6265.
423 // https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3
424 $cookieDomain = \ltrim(\strtolower($cookieDomain), '.');
425
426 $domain = \strtolower($domain);
427
428 // Domain not set or exact match.
429 if ('' === $cookieDomain || $domain === $cookieDomain) {
430 return true;
431 }
432
433 // Matching the subdomain according to RFC 6265.
434 // https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3
435 if (\filter_var($domain, \FILTER_VALIDATE_IP)) {
436 return false;
437 }
438
439 return (bool) \preg_match('/\.'.\preg_quote($cookieDomain, '/').'$/', $domain);
440 }
441
442 /**
443 * Check if the cookie is expired.
444 */
445 public function isExpired(): bool
446 {
447 return $this->getExpires() !== null && \time() > $this->getExpires();
448 }
449
450 /**
451 * Check if the cookie is valid according to RFC 6265.
452 *
453 * @return bool|string Returns true if valid or an error message if invalid
454 */
455 public function validate()
456 {
457 $name = $this->getName();
458 if ($name === '') {
459 return 'The cookie name must not be empty';
460 }
461
462 // Check if any of the invalid characters are present in the cookie name
463 if (\preg_match(
464 '/[\x00-\x20\x22\x28-\x29\x2c\x2f\x3a-\x40\x5c\x7b\x7d\x7f]/',
465 $name
466 )) {
467 return 'Cookie name must not contain invalid characters: ASCII '
468 .'Control characters (0-31;127), space, tab and the '
469 .'following characters: ()<>@,;:\"/?={}';
470 }
471
472 // Value must not be null. 0 and empty string are valid. Empty strings
473 // are technically against RFC 6265, but known to happen in the wild.
474 $value = $this->getValue();
475 if ($value === null) {
476 return 'The cookie value must not be empty';
477 }
478
479 // Domains must not be empty, but can be 0. "0" is not a valid internet
480 // domain, but may be used as server name in a private network.
481 $domain = $this->getDomain();
482 if ($domain === null || $domain === '') {
483 return 'The cookie domain must not be empty';
484 }
485
486 return true;
487 }
488}
Note: See TracBrowser for help on using the repository browser.