Completed
Pull Request — master (#68)
by Antonio J.
06:31
created

Cookie::withPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 2
cts 2
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 1
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
     * Validation state
58
     *
59
     * @var bool
60
     */
61
    private $valid;
62
63
    /**
64
     * @param string         $name
65
     * @param string|null    $value
66
     * @param int            $maxAge
67
     * @param string|null    $domain
68 57
     * @param string|null    $path
69
     * @param bool           $secure
70
     * @param bool           $httpOnly
71
     * @param \DateTime|null $expires  Expires attribute is HTTP 1.0 only and should be avoided.
72
     * @param bool           $requireValidation deprecated since version 1.5. Will be removed in 2.0
73
     *
74
     * @throws \InvalidArgumentException If name, value or max age is not valid. Attributes validation during instantiation is deprecated since 1.5 and will be removed in 2.0.
75
     */
76
    public function __construct(
77
        $name,
78 57
        $value = null,
79 53
        $maxAge = null,
80 50
        $domain = null,
81
        $path = null,
82 50
        $secure = false,
83 50
        $httpOnly = false,
84 50
        \DateTime $expires = null,
85 50
        $requireValidation = true
86 50
    ) {
87 50
        if ($requireValidation) {
88 50
            @trigger_error('Attributes validation during instantiation is deprecated since 1.5 and will be removed in 2.0', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
89 50
            $this->validateName($name);
90 50
            $this->validateValue($value);
91
            $this->validateMaxAge($maxAge);
92
            $this->valid = true;
93
        }
94
95
        $this->name = $name;
96
        $this->value = $value;
97 2
        $this->maxAge = $maxAge;
98
        $this->expires = $expires;
99 2
        $this->domain = $this->normalizeDomain($domain);
100
        $this->path = $this->normalizePath($path);
101
        $this->secure = (bool) $secure;
102
        $this->httpOnly = (bool) $httpOnly;
103
    }
104
105
    public function createWithoutValidation(
106
        $name,
107 5
        $value = null,
108
        $maxAge = null,
109 5
        $domain = null,
110
        $path = null,
111
        $secure = false,
112
        $httpOnly = false,
113
        \DateTime $expires = null
114
    ) {
115
        return new self($name, $value, $maxAge, $domain, $path, $secure, $httpOnly, $expires, false);
116
    }
117 13
118
    /**
119 13
     * Returns the name.
120
     *
121
     * @return string
122
     */
123
    public function getName()
124
    {
125
        return $this->name;
126
    }
127
128
    /**
129 8
     * Returns the value.
130
     *
131 8
     * @return string|null
132
     */
133 5
    public function getValue()
134 5
    {
135
        return $this->value;
136 5
    }
137
138
    /**
139
     * Checks if there is a value.
140
     *
141
     * @return bool
142
     */
143
    public function hasValue()
144 3
    {
145
        return isset($this->value);
146 3
    }
147
148
    /**
149
     * Sets the value.
150
     *
151
     * @param string|null $value
152
     *
153
     * @return Cookie
154 1
     */
155
    public function withValue($value)
156 1
    {
157
        $this->validateValue($value);
158
159
        $new = clone $this;
160
        $new->value = $value;
161
162
        return $new;
163
    }
164
165
    /**
166 2
     * Returns the max age.
167
     *
168 2
     * @return int|null
169
     */
170 1
    public function getMaxAge()
171 1
    {
172
        return $this->maxAge;
173 1
    }
174
175
    /**
176
     * Checks if there is a max age.
177
     *
178
     * @return bool
179
     */
180
    public function hasMaxAge()
181 3
    {
182
        return isset($this->maxAge);
183 3
    }
184
185
    /**
186
     * Sets the max age.
187
     *
188
     * @param int|null $maxAge
189
     *
190
     * @return Cookie
191 2
     */
192
    public function withMaxAge($maxAge)
193 2
    {
194
        $this->validateMaxAge($maxAge);
195
196
        $new = clone $this;
197
        $new->maxAge = $maxAge;
198
199
        return $new;
200
    }
201
202
    /**
203 1
     * Returns the expiration time.
204
     *
205 1
     * @return \DateTime|null
206 1
     */
207
    public function getExpires()
208 1
    {
209
        return $this->expires;
210
    }
211
212
    /**
213
     * Checks if there is an expiration time.
214
     *
215
     * @return bool
216 2
     */
217
    public function hasExpires()
218 2
    {
219
        return isset($this->expires);
220
    }
221
222
    /**
223
     * Sets the expires.
224
     *
225
     * @param \DateTime|null $expires
226 3
     *
227
     * @return Cookie
228 3
     */
229
    public function withExpires(\DateTime $expires = null)
230
    {
231
        $new = clone $this;
232
        $new->expires = $expires;
233
234
        return $new;
235
    }
236 4
237
    /**
238 4
     * Checks if the cookie is expired.
239
     *
240
     * @return bool
241
     */
242
    public function isExpired()
243
    {
244
        return isset($this->expires) and $this->expires < new \DateTime();
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
245
    }
246
247
    /**
248 1
     * Returns the domain.
249
     *
250 1
     * @return string|null
251 1
     */
252
    public function getDomain()
253 1
    {
254
        return $this->domain;
255
    }
256
257
    /**
258
     * Checks if there is a domain.
259
     *
260
     * @return bool
261
     */
262
    public function hasDomain()
263
    {
264
        return isset($this->domain);
265 2
    }
266
267
    /**
268 2
     * Sets the domain.
269 2
     *
270
     * @param string|null $domain
271
     *
272
     * @return Cookie
273 1
     */
274 1
    public function withDomain($domain)
275
    {
276
        $new = clone $this;
277 1
        $new->domain = $this->normalizeDomain($domain);
278
279
        return $new;
280
    }
281
282
    /**
283
     * Checks whether this cookie is meant for this domain.
284
     *
285 2
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.3
286
     *
287 2
     * @param string $domain
288
     *
289
     * @return bool
290
     */
291
    public function matchDomain($domain)
292
    {
293
        // Domain is not set or exact match
294
        if (!$this->hasDomain() || strcasecmp($domain, $this->domain) === 0) {
295
            return true;
296
        }
297 1
298
        // Domain is not an IP address
299 1
        if (filter_var($domain, FILTER_VALIDATE_IP)) {
300 1
            return false;
301
        }
302 1
303
        return (bool) preg_match(sprintf('/\b%s$/i', preg_quote($this->domain)), $domain);
304
    }
305
306
    /**
307
     * Returns the path.
308
     *
309
     * @return string
310
     */
311
    public function getPath()
312
    {
313
        return $this->path;
314 3
    }
315
316 3
    /**
317
     * Sets the path.
318
     *
319
     * @param string|null $path
320
     *
321
     * @return Cookie
322
     */
323
    public function withPath($path)
324 2
    {
325
        $new = clone $this;
326 2
        $new->path = $this->normalizePath($path);
327
328
        return $new;
329
    }
330
331
    /**
332
     * Checks whether this cookie is meant for this path.
333
     *
334
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.4
335
     *
336 1
     * @param string $path
337
     *
338 1
     * @return bool
339 1
     */
340
    public function matchPath($path)
341 1
    {
342
        return $this->path === $path || (strpos($path, rtrim($this->path, '/').'/') === 0);
343
    }
344
345
    /**
346
     * Checks whether this cookie may only be sent over HTTPS.
347
     *
348
     * @return bool
349 2
     */
350
    public function isSecure()
351 2
    {
352
        return $this->secure;
353
    }
354
355
    /**
356
     * Sets whether this cookie should only be sent over HTTPS.
357
     *
358
     * @param bool $secure
359
     *
360
     * @return Cookie
361 1
     */
362
    public function withSecure($secure)
363 1
    {
364 1
        $new = clone $this;
365
        $new->secure = (bool) $secure;
366 1
367
        return $new;
368
    }
369
370
    /**
371
     * Check whether this cookie may not be accessed through Javascript.
372
     *
373
     * @return bool
374
     */
375
    public function isHttpOnly()
376
    {
377
        return $this->httpOnly;
378 10
    }
379
380 10
    /**
381
     * Sets whether this cookie may not be accessed through Javascript.
382
     *
383
     * @param bool $httpOnly
384
     *
385
     * @return Cookie
386
     */
387
    public function withHttpOnly($httpOnly)
388
    {
389
        $new = clone $this;
390
        $new->httpOnly = (bool) $httpOnly;
391
392 57
        return $new;
393
    }
394 57
395 1
    /**
396
     * Checks if this cookie represents the same cookie as $cookie.
397
     *
398
     * This does not compare the values, only name, domain and path.
399 56
     *
400 3
     * @param Cookie $cookie
401
     *
402 53
     * @return bool
403
     */
404
    public function match(Cookie $cookie)
405
    {
406
        return $this->name === $cookie->name && $this->domain === $cookie->domain and $this->path === $cookie->path;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
407
    }
408
409
    /**
410
     * Validates cookie attributes
411
     *
412
     * @return bool
413 53
     */
414
    public function isValid()
415 53
    {
416 49
        if (null === $this->valid) {
417 6
            try {
418
                $this->validateName($this->name);
419 46
                $this->validateValue($this->value);
420 50
                $this->validateMaxAge($this->maxAge);
421
                $this->valid = true;
422
            } catch (\InvalidArgumentException $e) {
423
                $this->valid = false;
424
            }
425
        }
426
427
        return $this->valid;
428
    }
429 50
430
    /**
431 50
     * Validates the name attribute.
432 7
     *
433 1
     * @see http://tools.ietf.org/search/rfc2616#section-2.2
434
     *
435 6
     * @param string $name
436 50
     *
437
     * @throws \InvalidArgumentException If the name is empty or contains invalid characters.
438
     */
439
    private function validateName($name)
440
    {
441
        if (strlen($name) < 1) {
442
            throw new \InvalidArgumentException('The name cannot be empty');
443
        }
444
445
        // Name attribute is a token as per spec in RFC 2616
446
        if (preg_match('/[\x00-\x20\x22\x28-\x29\x2C\x2F\x3A-\x40\x5B-\x5D\x7B\x7D\x7F]/', $name)) {
447
            throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
448
        }
449 50
    }
450
451 50
    /**
452 6
     * Validates a value.
453 6
     *
454
     * @see http://tools.ietf.org/html/rfc6265#section-4.1.1
455 50
     *
456
     * @param string|null $value
457
     *
458
     * @throws \InvalidArgumentException If the value contains invalid characters.
459
     */
460
    private function validateValue($value)
461
    {
462
        if (isset($value)) {
463
            if (preg_match('/[^\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/', $value)) {
464
                throw new \InvalidArgumentException(sprintf('The cookie value "%s" contains invalid characters.', $value));
465
            }
466
        }
467
    }
468 50
469
    /**
470 50
     * Validates a Max-Age attribute.
471
     *
472 50
     * @param int|null $maxAge
473 49
     *
474 49
     * @throws \InvalidArgumentException If the Max-Age is not an empty or integer value.
475
     */
476 50
    private function validateMaxAge($maxAge)
477
    {
478
        if (isset($maxAge)) {
479
            if (!is_int($maxAge)) {
480
                throw new \InvalidArgumentException('Max-Age must be integer');
481
            }
482
        }
483
    }
484
485
    /**
486
     * Remove the leading '.' and lowercase the domain as per spec in RFC 6265.
487
     *
488
     * @see http://tools.ietf.org/html/rfc6265#section-4.1.2.3
489
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.3
490
     * @see http://tools.ietf.org/html/rfc6265#section-5.2.3
491
     *
492
     * @param string|null $domain
493
     *
494
     * @return string
495
     */
496
    private function normalizeDomain($domain)
497
    {
498
        if (isset($domain)) {
499
            $domain = ltrim(strtolower($domain), '.');
500
        }
501
502
        return $domain;
503
    }
504
505
    /**
506
     * Processes path as per spec in RFC 6265.
507
     *
508
     * @see http://tools.ietf.org/html/rfc6265#section-5.1.4
509
     * @see http://tools.ietf.org/html/rfc6265#section-5.2.4
510
     *
511
     * @param string|null $path
512
     *
513
     * @return string
514
     */
515
    private function normalizePath($path)
516
    {
517
        $path = rtrim($path, '/');
518
519
        if (empty($path) or substr($path, 0, 1) !== '/') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
520
            $path = '/';
521
        }
522
523
        return $path;
524
    }
525
}
526