Completed
Push — master ( 84dee3...084275 )
by Michal
36:03
created

CookieComponent   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 320
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 25
c 2
b 0
f 0
lcom 1
cbo 9
dl 0
loc 320
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
B initialize() 0 21 5
A configKey() 0 13 4
A implementedEvents() 0 4 1
A write() 0 19 4
A read() 0 5 1
A _load() 0 15 3
A check() 0 7 2
A delete() 0 14 2
A _write() 0 15 1
A _delete() 0 15 1
A _getCookieEncryptionKey() 0 4 1
1
<?php
2
/**
3
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
4
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
5
 *
6
 * Licensed under The MIT License
7
 * For full copyright and license information, please see the LICENSE.txt
8
 * Redistributions of files must retain the above copyright notice.
9
 *
10
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
11
 * @link          http://cakephp.org CakePHP(tm) Project
12
 * @since         1.2.0
13
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
14
 */
15
namespace Cake\Controller\Component;
16
17
use Cake\Controller\Component;
18
use Cake\I18n\Time;
19
use Cake\Network\Request;
20
use Cake\Network\Response;
21
use Cake\Utility\CookieCryptTrait;
22
use Cake\Utility\Hash;
23
use Cake\Utility\Security;
24
25
/**
26
 * Cookie Component.
27
 *
28
 * Provides enhanced cookie handling features for use in the controller layer.
29
 * In addition to the basic features offered be Cake\Network\Response, this class lets you:
30
 *
31
 * - Create and read encrypted cookies.
32
 * - Store non-scalar data.
33
 * - Use hash compatible syntax to read/write/delete values.
34
 *
35
 * @link http://book.cakephp.org/3.0/en/controllers/components/cookie.html
36
 */
37
class CookieComponent extends Component
38
{
39
    use CookieCryptTrait;
40
41
    /**
42
     * Default config
43
     *
44
     * - `expires` - How long the cookies should last for. Defaults to 1 month.
45
     * - `path` - The path on the server in which the cookie will be available on.
46
     *   If path is set to '/foo/', the cookie will only be available within the
47
     *   /foo/ directory and all sub-directories such as /foo/bar/ of domain.
48
     *   The default value is base path of app. For e.g. if your app is running
49
     *   under a subfolder "cakeapp" of document root the path would be "/cakeapp/"
50
     *   else it would be "/".
51
     * - `domain` - The domain that the cookie is available. To make the cookie
52
     *   available on all subdomains of example.com set domain to '.example.com'.
53
     * - `secure` - Indicates that the cookie should only be transmitted over a
54
     *   secure HTTPS connection. When set to true, the cookie will only be set if
55
     *   a secure connection exists.
56
     * - `key` - Encryption key used when encrypted cookies are enabled. Defaults to Security.salt.
57
     * - `httpOnly` - Set to true to make HTTP only cookies. Cookies that are HTTP only
58
     *   are not accessible in JavaScript. Default false.
59
     * - `encryption` - Type of encryption to use. Defaults to 'aes'.
60
     *
61
     * @var array
62
     */
63
    protected $_defaultConfig = [
64
        'path' => null,
65
        'domain' => '',
66
        'secure' => false,
67
        'key' => null,
68
        'httpOnly' => false,
69
        'encryption' => 'aes',
70
        'expires' => '+1 month',
71
    ];
72
73
    /**
74
     * Config specific to a given top level key name.
75
     *
76
     * The values in this array are merged with the general config
77
     * to generate the configuration for a given top level cookie name.
78
     *
79
     * @var array
80
     */
81
    protected $_keyConfig = [];
82
83
    /**
84
     * Values stored in the cookie.
85
     *
86
     * Accessed in the controller using $this->Cookie->read('Name.key');
87
     *
88
     * @var string
89
     */
90
    protected $_values = [];
91
92
    /**
93
     * A map of keys that have been loaded.
94
     *
95
     * Since CookieComponent lazily reads cookie data,
96
     * we need to track which cookies have been read to account for
97
     * read, delete, read patterns.
98
     *
99
     * @var array
100
     */
101
    protected $_loaded = [];
102
103
    /**
104
     * A reference to the Controller's Cake\Network\Response object
105
     *
106
     * @var \Cake\Network\Response
107
     */
108
    protected $_response = null;
109
110
    /**
111
     * Initialize config data and properties.
112
     *
113
     * @param array $config The config data.
114
     * @return void
115
     */
116
    public function initialize(array $config)
117
    {
118
        if (!$this->_config['key']) {
119
            $this->config('key', Security::salt());
120
        }
121
122
        $controller = $this->_registry->getController();
123
124
        if ($controller !== null) {
125
            $this->_response =& $controller->response;
126
        }
127
128
        if ($controller === null) {
129
            $this->request = Request::createFromGlobals();
130
            $this->_response = new Response();
131
        }
132
133
        if (empty($this->_config['path'])) {
134
            $this->config('path', $this->request->webroot);
135
        }
136
    }
137
138
    /**
139
     * Set the configuration for a specific top level key.
140
     *
141
     * ### Examples:
142
     *
143
     * Set a single config option for a key:
144
     *
145
     * ```
146
     * $this->Cookie->configKey('User', 'expires', '+3 months');
147
     * ```
148
     *
149
     * Set multiple options:
150
     *
151
     * ```
152
     * $this->Cookie->configKey('User', [
153
     *   'expires', '+3 months',
154
     *   'httpOnly' => true,
155
     * ]);
156
     * ```
157
     *
158
     * @param string $keyname The top level keyname to configure.
159
     * @param null|string|array $option Either the option name to set, or an array of options to set,
160
     *   or null to read config options for a given key.
161
     * @param string|null $value Either the value to set, or empty when $option is an array.
162
     * @return array|null
163
     */
164
    public function configKey($keyname, $option = null, $value = null)
165
    {
166
        if ($option === null) {
167
            $default = $this->_config;
168
            $local = isset($this->_keyConfig[$keyname]) ? $this->_keyConfig[$keyname] : [];
169
            return $local + $default;
170
        }
171
        if (!is_array($option)) {
172
            $option = [$option => $value];
173
        }
174
        $this->_keyConfig[$keyname] = $option;
175
        return null;
176
    }
177
178
    /**
179
     * Events supported by this component.
180
     *
181
     * @return array
182
     */
183
    public function implementedEvents()
184
    {
185
        return [];
186
    }
187
188
    /**
189
     * Write a value to the response cookies.
190
     *
191
     * You must use this method before any output is sent to the browser.
192
     * Failure to do so will result in header already sent errors.
193
     *
194
     * @param string|array $key Key for the value
195
     * @param mixed $value Value
196
     * @return void
197
     */
198
    public function write($key, $value = null)
199
    {
200
        if (!is_array($key)) {
201
            $key = [$key => $value];
202
        }
203
204
        $keys = [];
205
        foreach ($key as $name => $value) {
206
            $this->_load($name);
207
208
            $this->_values = Hash::insert($this->_values, $name, $value);
0 ignored issues
show
Bug introduced by
It seems like $this->_values can also be of type null or string; however, Cake\Utility\Hash::insert() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Documentation Bug introduced by
It seems like \Cake\Utility\Hash::inse..._values, $name, $value) can also be of type array. However, the property $_values is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
209
            $parts = explode('.', $name);
210
            $keys[] = $parts[0];
211
        }
212
213
        foreach ($keys as $name) {
214
            $this->_write($name, $this->_values[$name]);
215
        }
216
    }
217
218
    /**
219
     * Read the value of key path from request cookies.
220
     *
221
     * This method will also allow you to read cookies that have been written in this
222
     * request, but not yet sent to the client.
223
     *
224
     * @param string|null $key Key of the value to be obtained.
225
     * @return string or null, value for specified key
226
     */
227
    public function read($key = null)
228
    {
229
        $this->_load($key);
230
        return Hash::get($this->_values, $key);
231
    }
232
233
    /**
234
     * Load the cookie data from the request and response objects.
235
     *
236
     * Based on the configuration data, cookies will be decrypted. When cookies
237
     * contain array data, that data will be expanded.
238
     *
239
     * @param string|array $key The key to load.
240
     * @return void
241
     */
242
    protected function _load($key)
243
    {
244
        $parts = explode('.', $key);
245
        $first = array_shift($parts);
246
        if (isset($this->_loaded[$first])) {
247
            return;
248
        }
249
        if (!isset($this->request->cookies[$first])) {
250
            return;
251
        }
252
        $cookie = $this->request->cookies[$first];
253
        $config = $this->configKey($first);
254
        $this->_loaded[$first] = true;
255
        $this->_values[$first] = $this->_decrypt($cookie, $config['encryption']);
256
    }
257
258
    /**
259
     * Returns true if given key is set in the cookie.
260
     *
261
     * @param string|null $key Key to check for
262
     * @return bool True if the key exists
263
     */
264
    public function check($key = null)
265
    {
266
        if (empty($key)) {
267
            return false;
268
        }
269
        return $this->read($key) !== null;
270
    }
271
272
    /**
273
     * Delete a cookie value
274
     *
275
     * You must use this method before any output is sent to the browser.
276
     * Failure to do so will result in header already sent errors.
277
     *
278
     * Deleting a top level key will delete all keys nested within that key.
279
     * For example deleting the `User` key, will also delete `User.email`.
280
     *
281
     * @param string $key Key of the value to be deleted
282
     * @return void
283
     */
284
    public function delete($key)
285
    {
286
        $this->_load($key);
287
288
        $this->_values = Hash::remove($this->_values, $key);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Cake\Utility\Hash::remove($this->_values, $key) can also be of type array. However, the property $_values is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
289
        $parts = explode('.', $key);
290
        $top = $parts[0];
291
292
        if (isset($this->_values[$top])) {
293
            $this->_write($top, $this->_values[$top]);
294
        } else {
295
            $this->_delete($top);
296
        }
297
    }
298
299
    /**
300
     * Set cookie
301
     *
302
     * @param string $name Name for cookie
303
     * @param string $value Value for cookie
304
     * @return void
305
     */
306
    protected function _write($name, $value)
307
    {
308
        $config = $this->configKey($name);
309
        $expires = new Time($config['expires']);
310
311
        $this->_response->cookie([
312
            'name' => $name,
313
            'value' => $this->_encrypt($value, $config['encryption']),
314
            'expire' => $expires->format('U'),
315
            'path' => $config['path'],
316
            'domain' => $config['domain'],
317
            'secure' => $config['secure'],
318
            'httpOnly' => $config['httpOnly']
319
        ]);
320
    }
321
322
    /**
323
     * Sets a cookie expire time to remove cookie value.
324
     *
325
     * This is only done once all values in a cookie key have been
326
     * removed with delete.
327
     *
328
     * @param string $name Name of cookie
329
     * @return void
330
     */
331
    protected function _delete($name)
332
    {
333
        $config = $this->configKey($name);
334
        $expires = new Time('now');
335
336
        $this->_response->cookie([
337
            'name' => $name,
338
            'value' => '',
339
            'expire' => $expires->format('U') - 42000,
340
            'path' => $config['path'],
341
            'domain' => $config['domain'],
342
            'secure' => $config['secure'],
343
            'httpOnly' => $config['httpOnly']
344
        ]);
345
    }
346
347
    /**
348
     * Returns the encryption key to be used.
349
     *
350
     * @return string
351
     */
352
    protected function _getCookieEncryptionKey()
353
    {
354
        return $this->_config['key'];
355
    }
356
}
357