Cookie::hasDomain()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 1
cts 1
cp 1
crap 1
rs 10
c 1
b 0
f 0
1
<?php
2
3
namespace Http\Message;
4
5
/**
6
 * Cookie Value Object.
7
 *
8
 * @author Márk Sági-Kazár <[email protected]>
9
 *
10
 * @see http://tools.ietf.org/search/rfc6265
11
 */
12
final class Cookie
13
{
14
    /**
15
     * @var string
16
     */
17
    private $name;
18
19
    /**
20
     * @var string|null
21
     */
22
    private $value;
23
24
    /**
25
     * @var int|null
26
     */
27
    private $maxAge;
28
29
    /**
30
     * @var string|null
31
     */
32
    private $domain;
33
34
    /**
35
     * @var string
36
     */
37
    private $path;
38
39
    /**
40
     * @var bool
41
     */
42
    private $secure;
43
44
    /**
45
     * @var bool
46
     */
47
    private $httpOnly;
48
49
    /**
50
     * Expires attribute is HTTP 1.0 only and should be avoided.
51
     *
52
     * @var \DateTime|null
53
     */
54
    private $expires;
55
56
    /**
57
     * @param string         $name
58
     * @param string|null    $value
59
     * @param int|null       $maxAge
60
     * @param string|null    $domain
61
     * @param string|null    $path
62
     * @param bool           $secure
63
     * @param bool           $httpOnly
64
     * @param \DateTime|null $expires  Expires attribute is HTTP 1.0 only and should be avoided.
65
     *
66
     * @throws \InvalidArgumentException if name, value or max age is not valid
67
     */
68 61
    public function __construct(
69
        $name,
70
        $value = null,
71
        $maxAge = null,
72
        $domain = null,
73
        $path = null,
74
        $secure = false,
75
        $httpOnly = false,
76
        \DateTime $expires = null
77
    ) {
78 61
        $this->validateName($name);
79 57
        $this->validateValue($value);
80 54
        $this->validateMaxAge($maxAge);
81
82 54
        $this->name = $name;
83 54
        $this->value = $value;
84 54
        $this->maxAge = $maxAge;
85 54
        $this->expires = $expires;
86 54
        $this->domain = $this->normalizeDomain($domain);
87 54
        $this->path = $this->normalizePath($path);
88 54
        $this->secure = (bool) $secure;
89 54
        $this->httpOnly = (bool) $httpOnly;
90 54
    }
91
92
    /**
93
     * Creates a new cookie without any attribute validation.
94
     *
95
     * @param string         $name
96
     * @param string|null    $value
97
     * @param int            $maxAge
98
     * @param string|null    $domain
99
     * @param string|null    $path
100
     * @param bool           $secure
101
     * @param bool           $httpOnly
102
     * @param \DateTime|null $expires  Expires attribute is HTTP 1.0 only and should be avoided.
103
     */
104 3
    public static function createWithoutValidation(
105
        $name,
106
        $value = null,
107
        $maxAge = null,
108
        $domain = null,
109
        $path = null,
110
        $secure = false,
111
        $httpOnly = false,
112
        \DateTime $expires = null
113
    ) {
114 3
        $cookie = new self('name', null, null, $domain, $path, $secure, $httpOnly, $expires);
115 3
        $cookie->name = $name;
116 3
        $cookie->value = $value;
117 3
        $cookie->maxAge = $maxAge;
118
119 3
        return $cookie;
120
    }
121
122
    /**
123
     * Returns the name.
124
     *
125
     * @return string
126
     */
127 2
    public function getName()
128
    {
129 2
        return $this->name;
130
    }
131
132
    /**
133
     * Returns the value.
134
     *
135
     * @return string|null
136
     */
137 5
    public function getValue()
138
    {
139 5
        return $this->value;
140
    }
141
142
    /**
143
     * Checks if there is a value.
144
     *
145
     * @return bool
146
     */
147 13
    public function hasValue()
148
    {
149 13
        return isset($this->value);
150
    }
151
152
    /**
153
     * Sets the value.
154
     *
155
     * @param string|null $value
156
     *
157
     * @return Cookie
158
     */
159 8
    public function withValue($value)
160
    {
161 8
        $this->validateValue($value);
162
163 5
        $new = clone $this;
164 5
        $new->value = $value;
165
166 5
        return $new;
167
    }
168
169
    /**
170
     * Returns the max age.
171
     *
172
     * @return int|null
173
     */
174 3
    public function getMaxAge()
175
    {
176 3
        return $this->maxAge;
177
    }
178
179
    /**
180
     * Checks if there is a max age.
181
     *
182
     * @return bool
183
     */
184 1
    public function hasMaxAge()
185
    {
186 1
        return isset($this->maxAge);
187
    }
188
189
    /**
190
     * Sets the max age.
191
     *
192
     * @param int|null $maxAge
193
     *
194
     * @return Cookie
195
     */
196 2
    public function withMaxAge($maxAge)
197
    {
198 2
        $this->validateMaxAge($maxAge);
199
200 1
        $new = clone $this;
201 1
        $new->maxAge = $maxAge;
202
203 1
        return $new;
204
    }
205
206
    /**
207
     * Returns the expiration time.
208
     *
209
     * @return \DateTime|null
210
     */
211 3
    public function getExpires()
212
    {
213 3
        return $this->expires;
214
    }
215
216
    /**
217
     * Checks if there is an expiration time.
218
     *
219
     * @return bool
220
     */
221 2
    public function hasExpires()
222
    {
223 2
        return isset($this->expires);
224
    }
225
226
    /**
227
     * Sets the expires.
228
     *
229
     * @return Cookie
230
     */
231
    public function withExpires(\DateTime $expires = null)
232
    {
233 1
        $new = clone $this;
234
        $new->expires = $expires;
235 1
236 1
        return $new;
237
    }
238 1
239
    /**
240
     * Checks if the cookie is expired.
241
     *
242
     * @return bool
243
     */
244
    public function isExpired()
245
    {
246 2
        return isset($this->expires) and $this->expires < new \DateTime();
247
    }
248 2
249
    /**
250
     * Returns the domain.
251
     *
252
     * @return string|null
253
     */
254
    public function getDomain()
255
    {
256 3
        return $this->domain;
257
    }
258 3
259
    /**
260
     * Checks if there is a domain.
261
     *
262
     * @return bool
263
     */
264
    public function hasDomain()
265
    {
266 4
        return isset($this->domain);
267
    }
268 4
269
    /**
270
     * Sets the domain.
271
     *
272
     * @param string|null $domain
273
     *
274
     * @return Cookie
275
     */
276
    public function withDomain($domain)
277
    {
278 1
        $new = clone $this;
279
        $new->domain = $this->normalizeDomain($domain);
280 1
281 1
        return $new;
282
    }
283 1
284
    /**
285
     * Checks whether this cookie is meant for this domain.
286
     *
287
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.3
288
     *
289
     * @param string $domain
290
     *
291
     * @return bool
292
     */
293
    public function matchDomain($domain)
294
    {
295 2
        // Domain is not set or exact match
296
        if (!$this->hasDomain() || 0 === strcasecmp($domain, $this->domain)) {
297
            return true;
298 2
        }
299 2
300
        // Domain is not an IP address
301
        if (filter_var($domain, FILTER_VALIDATE_IP)) {
302
            return false;
303 1
        }
304 1
305
        return (bool) preg_match(sprintf('/\b%s$/i', preg_quote($this->domain)), $domain);
306
    }
307 1
308
    /**
309
     * Returns the path.
310
     *
311
     * @return string
312
     */
313
    public function getPath()
314
    {
315 2
        return $this->path;
316
    }
317 2
318
    /**
319
     * Sets the path.
320
     *
321
     * @param string|null $path
322
     *
323
     * @return Cookie
324
     */
325
    public function withPath($path)
326
    {
327 1
        $new = clone $this;
328
        $new->path = $this->normalizePath($path);
329 1
330 1
        return $new;
331
    }
332 1
333
    /**
334
     * Checks whether this cookie is meant for this path.
335
     *
336
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.4
337
     *
338
     * @param string $path
339
     *
340
     * @return bool
341
     */
342
    public function matchPath($path)
343
    {
344 3
        return $this->path === $path || (0 === strpos($path, rtrim($this->path, '/').'/'));
345
    }
346 3
347
    /**
348
     * Checks whether this cookie may only be sent over HTTPS.
349
     *
350
     * @return bool
351
     */
352
    public function isSecure()
353
    {
354 2
        return $this->secure;
355
    }
356 2
357
    /**
358
     * Sets whether this cookie should only be sent over HTTPS.
359
     *
360
     * @param bool $secure
361
     *
362
     * @return Cookie
363
     */
364
    public function withSecure($secure)
365
    {
366 1
        $new = clone $this;
367
        $new->secure = (bool) $secure;
368 1
369 1
        return $new;
370
    }
371 1
372
    /**
373
     * Check whether this cookie may not be accessed through Javascript.
374
     *
375
     * @return bool
376
     */
377
    public function isHttpOnly()
378
    {
379 2
        return $this->httpOnly;
380
    }
381 2
382
    /**
383
     * Sets whether this cookie may not be accessed through Javascript.
384
     *
385
     * @param bool $httpOnly
386
     *
387
     * @return Cookie
388
     */
389
    public function withHttpOnly($httpOnly)
390
    {
391 1
        $new = clone $this;
392
        $new->httpOnly = (bool) $httpOnly;
393 1
394 1
        return $new;
395
    }
396 1
397
    /**
398
     * Checks if this cookie represents the same cookie as $cookie.
399
     *
400
     * This does not compare the values, only name, domain and path.
401
     *
402
     * @param Cookie $cookie
403
     *
404
     * @return bool
405
     */
406
    public function match(self $cookie)
407
    {
408 10
        return $this->name === $cookie->name && $this->domain === $cookie->domain and $this->path === $cookie->path;
409
    }
410 10
411
    /**
412
     * Validates cookie attributes.
413
     *
414
     * @return bool
415
     */
416
    public function isValid()
417
    {
418 4
        try {
419
            $this->validateName($this->name);
420
            $this->validateValue($this->value);
421 4
            $this->validateMaxAge($this->maxAge);
422 3
        } catch (\InvalidArgumentException $e) {
423 2
            return false;
424 3
        }
425 3
426
        return true;
427
    }
428 1
429
    /**
430
     * Validates the name attribute.
431
     *
432
     * @see http://tools.ietf.org/search/rfc2616#section-2.2
433
     *
434
     * @param string $name
435
     *
436
     * @throws \InvalidArgumentException if the name is empty or contains invalid characters
437
     */
438
    private function validateName($name)
439
    {
440 61
        if (strlen($name) < 1) {
441
            throw new \InvalidArgumentException('The name cannot be empty');
442 61
        }
443 1
444
        // Name attribute is a token as per spec in RFC 2616
445
        if (preg_match('/[\x00-\x20\x22\x28-\x29\x2C\x2F\x3A-\x40\x5B-\x5D\x7B\x7D\x7F]/', $name)) {
446
            throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
447 60
        }
448 4
    }
449
450 57
    /**
451
     * Validates a value.
452
     *
453
     * @see http://tools.ietf.org/html/rfc6265#section-4.1.1
454
     *
455
     * @param string|null $value
456
     *
457
     * @throws \InvalidArgumentException if the value contains invalid characters
458
     */
459
    private function validateValue($value)
460
    {
461 57
        if (isset($value)) {
462
            if (preg_match('/[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/', $value)) {
463 57
                throw new \InvalidArgumentException(sprintf('The cookie value "%s" contains invalid characters.', $value));
464 52
            }
465 7
        }
466
    }
467
468 54
    /**
469
     * Validates a Max-Age attribute.
470
     *
471
     * @param int|null $maxAge
472
     *
473
     * @throws \InvalidArgumentException if the Max-Age is not an empty or integer value
474
     */
475
    private function validateMaxAge($maxAge)
476
    {
477 54
        if (isset($maxAge)) {
478
            if (!is_int($maxAge)) {
0 ignored issues
show
introduced by
The condition is_int($maxAge) is always true.
Loading history...
479 54
                throw new \InvalidArgumentException('Max-Age must be integer');
480 8
            }
481 2
        }
482
    }
483
484 54
    /**
485
     * Remove the leading '.' and lowercase the domain as per spec in RFC 6265.
486
     *
487
     * @see http://tools.ietf.org/html/rfc6265#section-4.1.2.3
488
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.3
489
     * @see http://tools.ietf.org/html/rfc6265#section-5.2.3
490
     *
491
     * @param string|null $domain
492
     *
493
     * @return string
494
     */
495
    private function normalizeDomain($domain)
496
    {
497 54
        if (isset($domain)) {
498
            $domain = ltrim(strtolower($domain), '.');
499 54
        }
500 6
501
        return $domain;
502
    }
503 54
504
    /**
505
     * Processes path as per spec in RFC 6265.
506
     *
507
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.4
508
     * @see http://tools.ietf.org/html/rfc6265#section-5.2.4
509
     *
510
     * @param string|null $path
511
     *
512
     * @return string
513
     */
514
    private function normalizePath($path)
515
    {
516 54
        $path = rtrim($path, '/');
517
518 54
        if (empty($path) or '/' !== substr($path, 0, 1)) {
519
            $path = '/';
520 54
        }
521 53
522
        return $path;
523
    }
524
}
525