Cookie::validate()   C
last analyzed

Complexity

Conditions 8
Paths 5

Size

Total Lines 28
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 13
nc 5
nop 0
1
<?php
2
3
namespace Guzzle\Plugin\Cookie;
4
5
use Guzzle\Common\ToArrayInterface;
6
7
/**
8
 * Set-Cookie object
9
 */
10
class Cookie implements ToArrayInterface
11
{
12
    /** @var array Cookie data */
13
    protected $data;
14
15
    /**
16
     * @var string ASCII codes not valid for for use in a cookie name
17
     *
18
     * Cookie names are defined as 'token', according to RFC 2616, Section 2.2
19
     * A valid token may contain any CHAR except CTLs (ASCII 0 - 31 or 127)
20
     * or any of the following separators
21
     */
22
    protected static $invalidCharString;
23
24
    /**
25
     * Gets an array of invalid cookie characters
26
     *
27
     * @return array
28
     */
29
    protected static function getInvalidCharacters()
30
    {
31
        if (!self::$invalidCharString) {
32
            self::$invalidCharString = implode('', array_map('chr', array_merge(
33
                range(0, 32),
34
                array(34, 40, 41, 44, 47),
35
                array(58, 59, 60, 61, 62, 63, 64, 91, 92, 93, 123, 125, 127)
36
            )));
37
        }
38
39
        return self::$invalidCharString;
40
    }
41
42
    /**
43
     * @param array $data Array of cookie data provided by a Cookie parser
44
     */
45
    public function __construct(array $data = array())
46
    {
47
        static $defaults = array(
48
            'name'        => '',
49
            'value'       => '',
50
            'domain'      => '',
51
            'path'        => '/',
52
            'expires'     => null,
53
            'max_age'     => 0,
54
            'comment'     => null,
55
            'comment_url' => null,
56
            'port'        => array(),
57
            'version'     => null,
58
            'secure'      => false,
59
            'discard'     => false,
60
            'http_only'   => false
61
        );
62
63
        $this->data = array_merge($defaults, $data);
64
        // Extract the expires value and turn it into a UNIX timestamp if needed
65
        if (!$this->getExpires() && $this->getMaxAge()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getMaxAge() of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
66
            // Calculate the expires date
67
            $this->setExpires(time() + (int) $this->getMaxAge());
68
        } elseif ($this->getExpires() && !is_numeric($this->getExpires())) {
69
            $this->setExpires(strtotime($this->getExpires()));
70
        }
71
    }
72
73
    /**
74
     * Get the cookie as an array
75
     *
76
     * @return array
77
     */
78
    public function toArray()
79
    {
80
        return $this->data;
81
    }
82
83
    /**
84
     * Get the cookie name
85
     *
86
     * @return string
87
     */
88
    public function getName()
89
    {
90
        return $this->data['name'];
91
    }
92
93
    /**
94
     * Set the cookie name
95
     *
96
     * @param string $name Cookie name
97
     *
98
     * @return Cookie
99
     */
100
    public function setName($name)
101
    {
102
        return $this->setData('name', $name);
103
    }
104
105
    /**
106
     * Get the cookie value
107
     *
108
     * @return string
109
     */
110
    public function getValue()
111
    {
112
        return $this->data['value'];
113
    }
114
115
    /**
116
     * Set the cookie value
117
     *
118
     * @param string $value Cookie value
119
     *
120
     * @return Cookie
121
     */
122
    public function setValue($value)
123
    {
124
        return $this->setData('value', $value);
125
    }
126
127
    /**
128
     * Get the domain
129
     *
130
     * @return string|null
131
     */
132
    public function getDomain()
133
    {
134
        return $this->data['domain'];
135
    }
136
137
    /**
138
     * Set the domain of the cookie
139
     *
140
     * @param string $domain
141
     *
142
     * @return Cookie
143
     */
144
    public function setDomain($domain)
145
    {
146
        return $this->setData('domain', $domain);
147
    }
148
149
    /**
150
     * Get the path
151
     *
152
     * @return string
153
     */
154
    public function getPath()
155
    {
156
        return $this->data['path'];
157
    }
158
159
    /**
160
     * Set the path of the cookie
161
     *
162
     * @param string $path Path of the cookie
163
     *
164
     * @return Cookie
165
     */
166
    public function setPath($path)
167
    {
168
        return $this->setData('path', $path);
169
    }
170
171
    /**
172
     * Maximum lifetime of the cookie in seconds
173
     *
174
     * @return int|null
175
     */
176
    public function getMaxAge()
177
    {
178
        return $this->data['max_age'];
179
    }
180
181
    /**
182
     * Set the max-age of the cookie
183
     *
184
     * @param int $maxAge Max age of the cookie in seconds
185
     *
186
     * @return Cookie
187
     */
188
    public function setMaxAge($maxAge)
189
    {
190
        return $this->setData('max_age', $maxAge);
191
    }
192
193
    /**
194
     * The UNIX timestamp when the cookie expires
195
     *
196
     * @return mixed
197
     */
198
    public function getExpires()
199
    {
200
        return $this->data['expires'];
201
    }
202
203
    /**
204
     * Set the unix timestamp for which the cookie will expire
205
     *
206
     * @param int $timestamp Unix timestamp
207
     *
208
     * @return Cookie
209
     */
210
    public function setExpires($timestamp)
211
    {
212
        return $this->setData('expires', $timestamp);
213
    }
214
215
    /**
216
     * Version of the cookie specification. RFC 2965 is 1
217
     *
218
     * @return mixed
219
     */
220
    public function getVersion()
221
    {
222
        return $this->data['version'];
223
    }
224
225
    /**
226
     * Set the cookie version
227
     *
228
     * @param string|int $version Version to set
229
     *
230
     * @return Cookie
231
     */
232
    public function setVersion($version)
233
    {
234
        return $this->setData('version', $version);
235
    }
236
237
    /**
238
     * Get whether or not this is a secure cookie
239
     *
240
     * @return null|bool
241
     */
242
    public function getSecure()
243
    {
244
        return $this->data['secure'];
245
    }
246
247
    /**
248
     * Set whether or not the cookie is secure
249
     *
250
     * @param bool $secure Set to true or false if secure
251
     *
252
     * @return Cookie
253
     */
254
    public function setSecure($secure)
255
    {
256
        return $this->setData('secure', (bool) $secure);
0 ignored issues
show
Documentation introduced by
(bool) $secure is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
257
    }
258
259
    /**
260
     * Get whether or not this is a session cookie
261
     *
262
     * @return null|bool
263
     */
264
    public function getDiscard()
265
    {
266
        return $this->data['discard'];
267
    }
268
269
    /**
270
     * Set whether or not this is a session cookie
271
     *
272
     * @param bool $discard Set to true or false if this is a session cookie
273
     *
274
     * @return Cookie
275
     */
276
    public function setDiscard($discard)
277
    {
278
        return $this->setData('discard', $discard);
0 ignored issues
show
Documentation introduced by
$discard is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
279
    }
280
281
    /**
282
     * Get the comment
283
     *
284
     * @return string|null
285
     */
286
    public function getComment()
287
    {
288
        return $this->data['comment'];
289
    }
290
291
    /**
292
     * Set the comment of the cookie
293
     *
294
     * @param string $comment Cookie comment
295
     *
296
     * @return Cookie
297
     */
298
    public function setComment($comment)
299
    {
300
        return $this->setData('comment', $comment);
301
    }
302
303
    /**
304
     * Get the comment URL of the cookie
305
     *
306
     * @return string|null
307
     */
308
    public function getCommentUrl()
309
    {
310
        return $this->data['comment_url'];
311
    }
312
313
    /**
314
     * Set the comment URL of the cookie
315
     *
316
     * @param string $commentUrl Cookie comment URL for more information
317
     *
318
     * @return Cookie
319
     */
320
    public function setCommentUrl($commentUrl)
321
    {
322
        return $this->setData('comment_url', $commentUrl);
323
    }
324
325
    /**
326
     * Get an array of acceptable ports this cookie can be used with
327
     *
328
     * @return array
329
     */
330
    public function getPorts()
331
    {
332
        return $this->data['port'];
333
    }
334
335
    /**
336
     * Set a list of acceptable ports this cookie can be used with
337
     *
338
     * @param array $ports Array of acceptable ports
339
     *
340
     * @return Cookie
341
     */
342
    public function setPorts(array $ports)
343
    {
344
        return $this->setData('port', $ports);
0 ignored issues
show
Documentation introduced by
$ports is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
345
    }
346
347
    /**
348
     * Get whether or not this is an HTTP only cookie
349
     *
350
     * @return bool
351
     */
352
    public function getHttpOnly()
353
    {
354
        return $this->data['http_only'];
355
    }
356
357
    /**
358
     * Set whether or not this is an HTTP only cookie
359
     *
360
     * @param bool $httpOnly Set to true or false if this is HTTP only
361
     *
362
     * @return Cookie
363
     */
364
    public function setHttpOnly($httpOnly)
365
    {
366
        return $this->setData('http_only', $httpOnly);
0 ignored issues
show
Documentation introduced by
$httpOnly is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
367
    }
368
369
    /**
370
     * Get an array of extra cookie data
371
     *
372
     * @return array
373
     */
374
    public function getAttributes()
375
    {
376
        return $this->data['data'];
377
    }
378
379
    /**
380
     * Get a specific data point from the extra cookie data
381
     *
382
     * @param string $name Name of the data point to retrieve
383
     *
384
     * @return null|string
385
     */
386
    public function getAttribute($name)
387
    {
388
        return array_key_exists($name, $this->data['data']) ? $this->data['data'][$name] : null;
389
    }
390
391
    /**
392
     * Set a cookie data attribute
393
     *
394
     * @param string $name  Name of the attribute to set
395
     * @param string $value Value to set
396
     *
397
     * @return Cookie
398
     */
399
    public function setAttribute($name, $value)
400
    {
401
        $this->data['data'][$name] = $value;
402
403
        return $this;
404
    }
405
406
    /**
407
     * Check if the cookie matches a path value
408
     *
409
     * @param string $path Path to check against
410
     *
411
     * @return bool
412
     */
413
    public function matchesPath($path)
414
    {
415
        // RFC6265 http://tools.ietf.org/search/rfc6265#section-5.1.4
416
        // A request-path path-matches a given cookie-path if at least one of
417
        // the following conditions holds:
418
419
        // o  The cookie-path and the request-path are identical.
420
        if ($path == $this->getPath()) {
421
            return true;
422
        }
423
424
        $pos = stripos($path, $this->getPath());
425
        if ($pos === 0) {
426
            // o  The cookie-path is a prefix of the request-path, and the last
427
            // character of the cookie-path is %x2F ("/").
428
            if (substr($this->getPath(), -1, 1) === "/") {
429
                return true;
430
            }
431
432
            // o  The cookie-path is a prefix of the request-path, and the first
433
            // character of the request-path that is not included in the cookie-
434
            // path is a %x2F ("/") character.
435
            if (substr($path, strlen($this->getPath()), 1) === "/") {
436
                return true;
437
            }
438
        }
439
440
        return false;
441
    }
442
443
    /**
444
     * Check if the cookie matches a domain value
445
     *
446
     * @param string $domain Domain to check against
447
     *
448
     * @return bool
449
     */
450
    public function matchesDomain($domain)
451
    {
452
        // Remove the leading '.' as per spec in RFC 6265: http://tools.ietf.org/html/rfc6265#section-5.2.3
453
        $cookieDomain = ltrim($this->getDomain(), '.');
454
455
        // Domain not set or exact match.
456
        if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) {
457
            return true;
458
        }
459
460
        // Matching the subdomain according to RFC 6265: http://tools.ietf.org/html/rfc6265#section-5.1.3
461
        if (filter_var($domain, FILTER_VALIDATE_IP)) {
462
            return false;
463
        }
464
465
        return (bool) preg_match('/\.' . preg_quote($cookieDomain, '/') . '$/i', $domain);
466
    }
467
468
    /**
469
     * Check if the cookie is compatible with a specific port
470
     *
471
     * @param int $port Port to check
472
     *
473
     * @return bool
474
     */
475
    public function matchesPort($port)
476
    {
477
        return count($this->getPorts()) == 0 || in_array($port, $this->getPorts());
478
    }
479
480
    /**
481
     * Check if the cookie is expired
482
     *
483
     * @return bool
484
     */
485
    public function isExpired()
486
    {
487
        return $this->getExpires() && time() > $this->getExpires();
488
    }
489
490
    /**
491
     * Check if the cookie is valid according to RFC 6265
492
     *
493
     * @return bool|string Returns true if valid or an error message if invalid
494
     */
495
    public function validate()
496
    {
497
        // Names must not be empty, but can be 0
498
        $name = $this->getName();
499
        if (empty($name) && !is_numeric($name)) {
500
            return 'The cookie name must not be empty';
501
        }
502
503
        // Check if any of the invalid characters are present in the cookie name
504
        if (strpbrk($name, self::getInvalidCharacters()) !== false) {
505
            return 'The cookie name must not contain invalid characters: ' . $name;
506
        }
507
508
        // Value must not be empty, but can be 0
509
        $value = $this->getValue();
510
        if (empty($value) && !is_numeric($value)) {
511
            return 'The cookie value must not be empty';
512
        }
513
514
        // Domains must not be empty, but can be 0
515
        // A "0" is not a valid internet domain, but may be used as server name in a private network
516
        $domain = $this->getDomain();
517
        if (empty($domain) && !is_numeric($domain)) {
518
            return 'The cookie domain must not be empty';
519
        }
520
521
        return true;
522
    }
523
524
    /**
525
     * Set a value and return the cookie object
526
     *
527
     * @param string $key   Key to set
528
     * @param string $value Value to set
529
     *
530
     * @return Cookie
531
     */
532
    private function setData($key, $value)
533
    {
534
        $this->data[$key] = $value;
535
536
        return $this;
537
    }
538
}
539