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

Cookie::withMaxAge()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

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