Subversion Repositories php-qbpwcf

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 liveuser 1
<?php
2
 
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Symfony\Component\HttpFoundation;
13
 
14
/**
15
 * Represents a cookie.
16
 *
17
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
18
 */
19
class Cookie
20
{
21
    const SAMESITE_NONE = 'none';
22
    const SAMESITE_LAX = 'lax';
23
    const SAMESITE_STRICT = 'strict';
24
 
25
    protected $name;
26
    protected $value;
27
    protected $domain;
28
    protected $expire;
29
    protected $path;
30
    protected $secure;
31
    protected $httpOnly;
32
 
33
    private $raw;
34
    private $sameSite;
35
    private $secureDefault = false;
36
 
37
    private static $reservedCharsList = "=,; \t\r\n\v\f";
38
    private static $reservedCharsFrom = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"];
39
    private static $reservedCharsTo = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C'];
40
 
41
    /**
42
     * Creates cookie from raw header string.
43
     *
44
     * @return static
45
     */
46
    public static function fromString(string $cookie, bool $decode = false)
47
    {
48
        $data = [
49
            'expires' => 0,
50
            'path' => '/',
51
            'domain' => null,
52
            'secure' => false,
53
            'httponly' => false,
54
            'raw' => !$decode,
55
            'samesite' => null,
56
        ];
57
 
58
        $parts = HeaderUtils::split($cookie, ';=');
59
        $part = array_shift($parts);
60
 
61
        $name = $decode ? urldecode($part[0]) : $part[0];
62
        $value = isset($part[1]) ? ($decode ? urldecode($part[1]) : $part[1]) : null;
63
 
64
        $data = HeaderUtils::combine($parts) + $data;
65
 
66
        if (isset($data['max-age'])) {
67
            $data['expires'] = time() + (int) $data['max-age'];
68
        }
69
 
70
        return new static($name, $value, $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']);
71
    }
72
 
73
    public static function create(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX): self
74
    {
75
        return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite);
76
    }
77
 
78
    /**
79
     * @param string                        $name     The name of the cookie
80
     * @param string|null                   $value    The value of the cookie
81
     * @param int|string|\DateTimeInterface $expire   The time the cookie expires
82
     * @param string                        $path     The path on the server in which the cookie will be available on
83
     * @param string|null                   $domain   The domain that the cookie is available to
84
     * @param bool|null                     $secure   Whether the client should send back the cookie only over HTTPS or null to auto-enable this when the request is already using HTTPS
85
     * @param bool                          $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
86
     * @param bool                          $raw      Whether the cookie value should be sent with no url encoding
87
     * @param string|null                   $sameSite Whether the cookie will be available for cross-site requests
88
     *
89
     * @throws \InvalidArgumentException
90
     */
91
    public function __construct(string $name, string $value = null, $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = 'lax')
92
    {
93
        // from PHP source code
94
        if ($raw && false !== strpbrk($name, self::$reservedCharsList)) {
95
            throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
96
        }
97
 
98
        if (empty($name)) {
99
            throw new \InvalidArgumentException('The cookie name cannot be empty.');
100
        }
101
 
102
        $this->name = $name;
103
        $this->value = $value;
104
        $this->domain = $domain;
105
        $this->expire = $this->withExpires($expire)->expire;
106
        $this->path = empty($path) ? '/' : $path;
107
        $this->secure = $secure;
108
        $this->httpOnly = $httpOnly;
109
        $this->raw = $raw;
110
        $this->sameSite = $this->withSameSite($sameSite)->sameSite;
111
    }
112
 
113
    /**
114
     * Creates a cookie copy with a new value.
115
     *
116
     * @return static
117
     */
118
    public function withValue(?string $value): self
119
    {
120
        $cookie = clone $this;
121
        $cookie->value = $value;
122
 
123
        return $cookie;
124
    }
125
 
126
    /**
127
     * Creates a cookie copy with a new domain that the cookie is available to.
128
     *
129
     * @return static
130
     */
131
    public function withDomain(?string $domain): self
132
    {
133
        $cookie = clone $this;
134
        $cookie->domain = $domain;
135
 
136
        return $cookie;
137
    }
138
 
139
    /**
140
     * Creates a cookie copy with a new time the cookie expires.
141
     *
142
     * @param int|string|\DateTimeInterface $expire
143
     *
144
     * @return static
145
     */
146
    public function withExpires($expire = 0): self
147
    {
148
        // convert expiration time to a Unix timestamp
149
        if ($expire instanceof \DateTimeInterface) {
150
            $expire = $expire->format('U');
151
        } elseif (!is_numeric($expire)) {
152
            $expire = strtotime($expire);
153
 
154
            if (false === $expire) {
155
                throw new \InvalidArgumentException('The cookie expiration time is not valid.');
156
            }
157
        }
158
 
159
        $cookie = clone $this;
160
        $cookie->expire = 0 < $expire ? (int) $expire : 0;
161
 
162
        return $cookie;
163
    }
164
 
165
    /**
166
     * Creates a cookie copy with a new path on the server in which the cookie will be available on.
167
     *
168
     * @return static
169
     */
170
    public function withPath(string $path): self
171
    {
172
        $cookie = clone $this;
173
        $cookie->path = '' === $path ? '/' : $path;
174
 
175
        return $cookie;
176
    }
177
 
178
    /**
179
     * Creates a cookie copy that only be transmitted over a secure HTTPS connection from the client.
180
     *
181
     * @return static
182
     */
183
    public function withSecure(bool $secure = true): self
184
    {
185
        $cookie = clone $this;
186
        $cookie->secure = $secure;
187
 
188
        return $cookie;
189
    }
190
 
191
    /**
192
     * Creates a cookie copy that be accessible only through the HTTP protocol.
193
     *
194
     * @return static
195
     */
196
    public function withHttpOnly(bool $httpOnly = true): self
197
    {
198
        $cookie = clone $this;
199
        $cookie->httpOnly = $httpOnly;
200
 
201
        return $cookie;
202
    }
203
 
204
    /**
205
     * Creates a cookie copy that uses no url encoding.
206
     *
207
     * @return static
208
     */
209
    public function withRaw(bool $raw = true): self
210
    {
211
        if ($raw && false !== strpbrk($this->name, self::$reservedCharsList)) {
212
            throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $this->name));
213
        }
214
 
215
        $cookie = clone $this;
216
        $cookie->raw = $raw;
217
 
218
        return $cookie;
219
    }
220
 
221
    /**
222
     * Creates a cookie copy with SameSite attribute.
223
     *
224
     * @return static
225
     */
226
    public function withSameSite(?string $sameSite): self
227
    {
228
        if ('' === $sameSite) {
229
            $sameSite = null;
230
        } elseif (null !== $sameSite) {
231
            $sameSite = strtolower($sameSite);
232
        }
233
 
234
        if (!\in_array($sameSite, [self::SAMESITE_LAX, self::SAMESITE_STRICT, self::SAMESITE_NONE, null], true)) {
235
            throw new \InvalidArgumentException('The "sameSite" parameter value is not valid.');
236
        }
237
 
238
        $cookie = clone $this;
239
        $cookie->sameSite = $sameSite;
240
 
241
        return $cookie;
242
    }
243
 
244
    /**
245
     * Returns the cookie as a string.
246
     *
247
     * @return string The cookie
248
     */
249
    public function __toString()
250
    {
251
        if ($this->isRaw()) {
252
            $str = $this->getName();
253
        } else {
254
            $str = str_replace(self::$reservedCharsFrom, self::$reservedCharsTo, $this->getName());
255
        }
256
 
257
        $str .= '=';
258
 
259
        if ('' === (string) $this->getValue()) {
260
            $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0';
261
        } else {
262
            $str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue());
263
 
264
            if (0 !== $this->getExpiresTime()) {
265
                $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge();
266
            }
267
        }
268
 
269
        if ($this->getPath()) {
270
            $str .= '; path='.$this->getPath();
271
        }
272
 
273
        if ($this->getDomain()) {
274
            $str .= '; domain='.$this->getDomain();
275
        }
276
 
277
        if (true === $this->isSecure()) {
278
            $str .= '; secure';
279
        }
280
 
281
        if (true === $this->isHttpOnly()) {
282
            $str .= '; httponly';
283
        }
284
 
285
        if (null !== $this->getSameSite()) {
286
            $str .= '; samesite='.$this->getSameSite();
287
        }
288
 
289
        return $str;
290
    }
291
 
292
    /**
293
     * Gets the name of the cookie.
294
     *
295
     * @return string
296
     */
297
    public function getName()
298
    {
299
        return $this->name;
300
    }
301
 
302
    /**
303
     * Gets the value of the cookie.
304
     *
305
     * @return string|null
306
     */
307
    public function getValue()
308
    {
309
        return $this->value;
310
    }
311
 
312
    /**
313
     * Gets the domain that the cookie is available to.
314
     *
315
     * @return string|null
316
     */
317
    public function getDomain()
318
    {
319
        return $this->domain;
320
    }
321
 
322
    /**
323
     * Gets the time the cookie expires.
324
     *
325
     * @return int
326
     */
327
    public function getExpiresTime()
328
    {
329
        return $this->expire;
330
    }
331
 
332
    /**
333
     * Gets the max-age attribute.
334
     *
335
     * @return int
336
     */
337
    public function getMaxAge()
338
    {
339
        $maxAge = $this->expire - time();
340
 
341
        return 0 >= $maxAge ? 0 : $maxAge;
342
    }
343
 
344
    /**
345
     * Gets the path on the server in which the cookie will be available on.
346
     *
347
     * @return string
348
     */
349
    public function getPath()
350
    {
351
        return $this->path;
352
    }
353
 
354
    /**
355
     * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client.
356
     *
357
     * @return bool
358
     */
359
    public function isSecure()
360
    {
361
        return $this->secure ?? $this->secureDefault;
362
    }
363
 
364
    /**
365
     * Checks whether the cookie will be made accessible only through the HTTP protocol.
366
     *
367
     * @return bool
368
     */
369
    public function isHttpOnly()
370
    {
371
        return $this->httpOnly;
372
    }
373
 
374
    /**
375
     * Whether this cookie is about to be cleared.
376
     *
377
     * @return bool
378
     */
379
    public function isCleared()
380
    {
381
        return 0 !== $this->expire && $this->expire < time();
382
    }
383
 
384
    /**
385
     * Checks if the cookie value should be sent with no url encoding.
386
     *
387
     * @return bool
388
     */
389
    public function isRaw()
390
    {
391
        return $this->raw;
392
    }
393
 
394
    /**
395
     * Gets the SameSite attribute.
396
     *
397
     * @return string|null
398
     */
399
    public function getSameSite()
400
    {
401
        return $this->sameSite;
402
    }
403
 
404
    /**
405
     * @param bool $default The default value of the "secure" flag when it is set to null
406
     */
407
    public function setSecureDefault(bool $default): void
408
    {
409
        $this->secureDefault = $default;
410
    }
411
}