Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
namespace GuzzleHttp\Cookie;
4
 
5
/**
6
 * Set-Cookie object
7
 */
8
class SetCookie
9
{
10
    /**
11
     * @var array
12
     */
13
    private static $defaults = [
1441 ariadna 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,
1 efrain 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)) {
1441 ariadna 61
                        if ($search === 'Max-Age') {
62
                            if (is_numeric($value)) {
63
                                $data[$search] = (int) $value;
64
                            }
65
                        } else {
66
                            $data[$search] = $value;
67
                        }
1 efrain 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
    {
1441 ariadna 83
        $this->data = self::$defaults;
84
 
85
        if (isset($data['Name'])) {
86
            $this->setName($data['Name']);
1 efrain 87
        }
88
 
1441 ariadna 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
 
1 efrain 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
    {
1441 ariadna 137
        $str = $this->data['Name'].'='.($this->data['Value'] ?? '').'; ';
1 efrain 138
        foreach ($this->data as $k => $v) {
139
            if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) {
140
                if ($k === 'Expires') {
1441 ariadna 141
                    $str .= 'Expires='.\gmdate('D, d M Y H:i:s \G\M\T', $v).'; ';
1 efrain 142
                } else {
1441 ariadna 143
                    $str .= ($v === true ? $k : "{$k}={$v}").'; ';
1 efrain 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.
1441 ariadna 423
        // https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3
1 efrain 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.
1441 ariadna 434
        // https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3
1 efrain 435
        if (\filter_var($domain, \FILTER_VALIDATE_IP)) {
436
            return false;
437
        }
438
 
1441 ariadna 439
        return (bool) \preg_match('/\.'.\preg_quote($cookieDomain, '/').'$/', $domain);
1 efrain 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 '
1441 ariadna 468
                .'Control characters (0-31;127), space, tab and the '
469
                .'following characters: ()<>@,;:\"/?={}';
1 efrain 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
}