統計
| ブランチ: | リビジョン:

pictcode / lib / Cake / Controller / Component / CookieComponent.php @ 635eef61

履歴 | 表示 | アノテート | ダウンロード (14.843 KB)

1
<?php
2
/**
3
 * Cookie Component
4
 *
5
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
6
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
7
 *
8
 * Licensed under The MIT License
9
 * For full copyright and license information, please see the LICENSE.txt
10
 * Redistributions of files must retain the above copyright notice.
11
 *
12
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
13
 * @link          http://cakephp.org CakePHP(tm) Project
14
 * @package       Cake.Controller.Component
15
 * @since         CakePHP(tm) v 1.2.0.4213
16
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
17
 */
18

    
19
App::uses('Component', 'Controller');
20
App::uses('Security', 'Utility');
21
App::uses('Hash', 'Utility');
22

    
23
/**
24
 * Cookie Component.
25
 *
26
 * Cookie handling for the controller.
27
 *
28
 * @package       Cake.Controller.Component
29
 * @link http://book.cakephp.org/2.0/en/core-libraries/components/cookie.html
30
 */
31
class CookieComponent extends Component {
32

    
33
/**
34
 * The name of the cookie.
35
 *
36
 * Overridden with the controller beforeFilter();
37
 * $this->Cookie->name = 'CookieName';
38
 *
39
 * @var string
40
 */
41
        public $name = 'CakeCookie';
42

    
43
/**
44
 * The time a cookie will remain valid.
45
 *
46
 * Can be either integer Unix timestamp or a date string.
47
 *
48
 * Overridden with the controller beforeFilter();
49
 * $this->Cookie->time = '5 Days';
50
 *
51
 * @var mixed
52
 */
53
        public $time = null;
54

    
55
/**
56
 * Cookie path.
57
 *
58
 * Overridden with the controller beforeFilter();
59
 * $this->Cookie->path = '/';
60
 *
61
 * The path on the server in which the cookie will be available on.
62
 * If public $cookiePath is set to '/foo/', the cookie will only be available
63
 * within the /foo/ directory and all sub-directories such as /foo/bar/ of domain.
64
 * The default value is the entire domain.
65
 *
66
 * @var string
67
 */
68
        public $path = '/';
69

    
70
/**
71
 * Domain path.
72
 *
73
 * The domain that the cookie is available.
74
 *
75
 * Overridden with the controller beforeFilter();
76
 * $this->Cookie->domain = '.example.com';
77
 *
78
 * To make the cookie available on all subdomains of example.com.
79
 * Set $this->Cookie->domain = '.example.com'; in your controller beforeFilter
80
 *
81
 * @var string
82
 */
83
        public $domain = '';
84

    
85
/**
86
 * Secure HTTPS only cookie.
87
 *
88
 * Overridden with the controller beforeFilter();
89
 * $this->Cookie->secure = true;
90
 *
91
 * Indicates that the cookie should only be transmitted over a secure HTTPS connection.
92
 * When set to true, the cookie will only be set if a secure connection exists.
93
 *
94
 * @var bool
95
 */
96
        public $secure = false;
97

    
98
/**
99
 * Encryption key.
100
 *
101
 * Overridden with the controller beforeFilter();
102
 * $this->Cookie->key = 'SomeRandomString';
103
 *
104
 * @var string
105
 */
106
        public $key = null;
107

    
108
/**
109
 * HTTP only cookie
110
 *
111
 * Set to true to make HTTP only cookies. Cookies that are HTTP only
112
 * are not accessible in JavaScript.
113
 *
114
 * @var bool
115
 */
116
        public $httpOnly = false;
117

    
118
/**
119
 * Values stored in the cookie.
120
 *
121
 * Accessed in the controller using $this->Cookie->read('Name.key');
122
 *
123
 * @see CookieComponent::read();
124
 * @var string
125
 */
126
        protected $_values = array();
127

    
128
/**
129
 * Type of encryption to use.
130
 *
131
 * Currently two methods are available: cipher and rijndael
132
 * Defaults to Security::cipher(). Cipher is horribly insecure and only
133
 * the default because of backwards compatibility. In new applications you should
134
 * always change this to 'aes' or 'rijndael'.
135
 *
136
 * @var string
137
 */
138
        protected $_type = 'cipher';
139

    
140
/**
141
 * Used to reset cookie time if $expire is passed to CookieComponent::write()
142
 *
143
 * @var string
144
 */
145
        protected $_reset = null;
146

    
147
/**
148
 * Expire time of the cookie
149
 *
150
 * This is controlled by CookieComponent::time;
151
 *
152
 * @var string
153
 */
154
        protected $_expires = 0;
155

    
156
/**
157
 * A reference to the Controller's CakeResponse object
158
 *
159
 * @var CakeResponse
160
 */
161
        protected $_response = null;
162

    
163
/**
164
 * Constructor
165
 *
166
 * @param ComponentCollection $collection A ComponentCollection for this component
167
 * @param array $settings Array of settings.
168
 */
169
        public function __construct(ComponentCollection $collection, $settings = array()) {
170
                $this->key = Configure::read('Security.salt');
171
                parent::__construct($collection, $settings);
172
                if (isset($this->time)) {
173
                        $this->_expire($this->time);
174
                }
175

    
176
                $controller = $collection->getController();
177
                if ($controller && isset($controller->response)) {
178
                        $this->_response = $controller->response;
179
                } else {
180
                        $this->_response = new CakeResponse();
181
                }
182
        }
183

    
184
/**
185
 * Start CookieComponent for use in the controller
186
 *
187
 * @param Controller $controller Controller instance.
188
 * @return void
189
 */
190
        public function startup(Controller $controller) {
191
                $this->_expire($this->time);
192

    
193
                $this->_values[$this->name] = array();
194
        }
195

    
196
/**
197
 * Write a value to the $_COOKIE[$key];
198
 *
199
 * Optional [Name.], required key, optional $value, optional $encrypt, optional $expires
200
 * $this->Cookie->write('[Name.]key, $value);
201
 *
202
 * By default all values are encrypted.
203
 * You must pass $encrypt false to store values in clear test
204
 *
205
 * You must use this method before any output is sent to the browser.
206
 * Failure to do so will result in header already sent errors.
207
 *
208
 * @param string|array $key Key for the value
209
 * @param mixed $value Value
210
 * @param bool $encrypt Set to true to encrypt value, false otherwise
211
 * @param int|string $expires Can be either the number of seconds until a cookie
212
 *   expires, or a strtotime compatible time offset.
213
 * @return void
214
 * @link http://book.cakephp.org/2.0/en/core-libraries/components/cookie.html#CookieComponent::write
215
 */
216
        public function write($key, $value = null, $encrypt = true, $expires = null) {
217
                if (empty($this->_values[$this->name])) {
218
                        $this->read();
219
                }
220

    
221
                if ($encrypt === null) {
222
                        $encrypt = true;
223
                }
224
                $this->_encrypted = $encrypt;
225
                $this->_expire($expires);
226

    
227
                if (!is_array($key)) {
228
                        $key = array($key => $value);
229
                }
230

    
231
                foreach ($key as $name => $value) {
232
                        $names = array($name);
233
                        if (strpos($name, '.') !== false) {
234
                                $names = explode('.', $name, 2);
235
                        }
236
                        $firstName = $names[0];
237
                        $isMultiValue = (is_array($value) || count($names) > 1);
238

    
239
                        if (!isset($this->_values[$this->name][$firstName]) && $isMultiValue) {
240
                                $this->_values[$this->name][$firstName] = array();
241
                        }
242

    
243
                        if (count($names) > 1) {
244
                                $this->_values[$this->name][$firstName] = Hash::insert(
245
                                        $this->_values[$this->name][$firstName],
246
                                        $names[1],
247
                                        $value
248
                                );
249
                        } else {
250
                                $this->_values[$this->name][$firstName] = $value;
251
                        }
252
                        $this->_write('[' . $firstName . ']', $this->_values[$this->name][$firstName]);
253
                }
254
                $this->_encrypted = true;
255
        }
256

    
257
/**
258
 * Read the value of the $_COOKIE[$key];
259
 *
260
 * Optional [Name.], required key
261
 * $this->Cookie->read(Name.key);
262
 *
263
 * @param string $key Key of the value to be obtained. If none specified, obtain map key => values
264
 * @return string|null Value for specified key
265
 * @link http://book.cakephp.org/2.0/en/core-libraries/components/cookie.html#CookieComponent::read
266
 */
267
        public function read($key = null) {
268
                if (empty($this->_values[$this->name]) && isset($_COOKIE[$this->name])) {
269
                        $this->_values[$this->name] = $this->_decrypt($_COOKIE[$this->name]);
270
                }
271
                if (empty($this->_values[$this->name])) {
272
                        $this->_values[$this->name] = array();
273
                }
274
                if ($key === null) {
275
                        return $this->_values[$this->name];
276
                }
277

    
278
                if (strpos($key, '.') !== false) {
279
                        $names = explode('.', $key, 2);
280
                        $key = $names[0];
281
                }
282
                if (!isset($this->_values[$this->name][$key])) {
283
                        return null;
284
                }
285

    
286
                if (!empty($names[1]) && is_array($this->_values[$this->name][$key])) {
287
                        return Hash::get($this->_values[$this->name][$key], $names[1]);
288
                }
289
                return $this->_values[$this->name][$key];
290
        }
291

    
292
/**
293
 * Returns true if given variable is set in cookie.
294
 *
295
 * @param string $key Variable name to check for
296
 * @return bool True if variable is there
297
 */
298
        public function check($key = null) {
299
                if (empty($key)) {
300
                        return false;
301
                }
302
                return $this->read($key) !== null;
303
        }
304

    
305
/**
306
 * Delete a cookie value
307
 *
308
 * Optional [Name.], required key
309
 * $this->Cookie->delete('Name.key);
310
 *
311
 * You must use this method before any output is sent to the browser.
312
 * Failure to do so will result in header already sent errors.
313
 *
314
 * This method will delete both the top level and 2nd level cookies set.
315
 * For example assuming that $name = App, deleting `User` will delete
316
 * both `App[User]` and any other cookie values like `App[User][email]`
317
 * This is done to clean up cookie storage from before 2.4.3, where cookies
318
 * were stored inconsistently.
319
 *
320
 * @param string $key Key of the value to be deleted
321
 * @return void
322
 * @link http://book.cakephp.org/2.0/en/core-libraries/components/cookie.html#CookieComponent::delete
323
 */
324
        public function delete($key) {
325
                if (empty($this->_values[$this->name])) {
326
                        $this->read();
327
                }
328
                if (strpos($key, '.') === false) {
329
                        if (isset($this->_values[$this->name][$key]) && is_array($this->_values[$this->name][$key])) {
330
                                foreach ($this->_values[$this->name][$key] as $idx => $val) {
331
                                        $this->_delete("[$key][$idx]");
332
                                }
333
                        }
334
                        $this->_delete("[$key]");
335
                        unset($this->_values[$this->name][$key]);
336
                        return;
337
                }
338
                $names = explode('.', $key, 2);
339
                if (isset($this->_values[$this->name][$names[0]])) {
340
                        $this->_values[$this->name][$names[0]] = Hash::remove($this->_values[$this->name][$names[0]], $names[1]);
341
                }
342
                $this->_delete('[' . implode('][', $names) . ']');
343
        }
344

    
345
/**
346
 * Destroy current cookie
347
 *
348
 * You must use this method before any output is sent to the browser.
349
 * Failure to do so will result in header already sent errors.
350
 *
351
 * @return void
352
 * @link http://book.cakephp.org/2.0/en/core-libraries/components/cookie.html#CookieComponent::destroy
353
 */
354
        public function destroy() {
355
                if (isset($_COOKIE[$this->name])) {
356
                        $this->_values[$this->name] = $this->_decrypt($_COOKIE[$this->name]);
357
                }
358

    
359
                foreach ($this->_values[$this->name] as $name => $value) {
360
                        if (is_array($value)) {
361
                                foreach ($value as $key => $val) {
362
                                        unset($this->_values[$this->name][$name][$key]);
363
                                        $this->_delete("[$name][$key]");
364
                                }
365
                        }
366
                        unset($this->_values[$this->name][$name]);
367
                        $this->_delete("[$name]");
368
                }
369
        }
370

    
371
/**
372
 * Will allow overriding default encryption method. Use this method
373
 * in ex: AppController::beforeFilter() before you have read or
374
 * written any cookies.
375
 *
376
 * @param string $type Encryption method
377
 * @return void
378
 */
379
        public function type($type = 'cipher') {
380
                $availableTypes = array(
381
                        'cipher',
382
                        'rijndael',
383
                        'aes'
384
                );
385
                if (!in_array($type, $availableTypes)) {
386
                        trigger_error(__d('cake_dev', 'You must use cipher, rijndael or aes for cookie encryption type'), E_USER_WARNING);
387
                        $type = 'cipher';
388
                }
389
                $this->_type = $type;
390
        }
391

    
392
/**
393
 * Set the expire time for a session variable.
394
 *
395
 * Creates a new expire time for a session variable.
396
 * $expire can be either integer Unix timestamp or a date string.
397
 *
398
 * Used by write()
399
 * CookieComponent::write(string, string, boolean, 8400);
400
 * CookieComponent::write(string, string, boolean, '5 Days');
401
 *
402
 * @param int|string $expires Can be either Unix timestamp, or date string
403
 * @return int Unix timestamp
404
 */
405
        protected function _expire($expires = null) {
406
                if ($expires === null) {
407
                        return $this->_expires;
408
                }
409
                $this->_reset = $this->_expires;
410
                if (!$expires) {
411
                        return $this->_expires = 0;
412
                }
413
                $now = new DateTime();
414

    
415
                if (is_int($expires) || is_numeric($expires)) {
416
                        return $this->_expires = $now->format('U') + (int)$expires;
417
                }
418
                $now->modify($expires);
419
                return $this->_expires = $now->format('U');
420
        }
421

    
422
/**
423
 * Set cookie
424
 *
425
 * @param string $name Name for cookie
426
 * @param string $value Value for cookie
427
 * @return void
428
 */
429
        protected function _write($name, $value) {
430
                $this->_response->cookie(array(
431
                        'name' => $this->name . $name,
432
                        'value' => $this->_encrypt($value),
433
                        'expire' => $this->_expires,
434
                        'path' => $this->path,
435
                        'domain' => $this->domain,
436
                        'secure' => $this->secure,
437
                        'httpOnly' => $this->httpOnly
438
                ));
439

    
440
                if (!empty($this->_reset)) {
441
                        $this->_expires = $this->_reset;
442
                        $this->_reset = null;
443
                }
444
        }
445

    
446
/**
447
 * Sets a cookie expire time to remove cookie value
448
 *
449
 * @param string $name Name of cookie
450
 * @return void
451
 */
452
        protected function _delete($name) {
453
                $this->_response->cookie(array(
454
                        'name' => $this->name . $name,
455
                        'value' => '',
456
                        'expire' => time() - 42000,
457
                        'path' => $this->path,
458
                        'domain' => $this->domain,
459
                        'secure' => $this->secure,
460
                        'httpOnly' => $this->httpOnly
461
                ));
462
        }
463

    
464
/**
465
 * Encrypts $value using public $type method in Security class
466
 *
467
 * @param string $value Value to encrypt
468
 * @return string Encoded values
469
 */
470
        protected function _encrypt($value) {
471
                if (is_array($value)) {
472
                        $value = $this->_implode($value);
473
                }
474
                if (!$this->_encrypted) {
475
                        return $value;
476
                }
477
                $prefix = "Q2FrZQ==.";
478
                if ($this->_type === 'rijndael') {
479
                        $cipher = Security::rijndael($value, $this->key, 'encrypt');
480
                }
481
                if ($this->_type === 'cipher') {
482
                        $cipher = Security::cipher($value, $this->key);
483
                }
484
                if ($this->_type === 'aes') {
485
                        $cipher = Security::encrypt($value, $this->key);
486
                }
487
                return $prefix . base64_encode($cipher);
488
        }
489

    
490
/**
491
 * Decrypts $value using public $type method in Security class
492
 *
493
 * @param array $values Values to decrypt
494
 * @return string decrypted string
495
 */
496
        protected function _decrypt($values) {
497
                $decrypted = array();
498
                $type = $this->_type;
499

    
500
                foreach ((array)$values as $name => $value) {
501
                        if (is_array($value)) {
502
                                foreach ($value as $key => $val) {
503
                                        $decrypted[$name][$key] = $this->_decode($val);
504
                                }
505
                        } else {
506
                                $decrypted[$name] = $this->_decode($value);
507
                        }
508
                }
509
                return $decrypted;
510
        }
511

    
512
/**
513
 * Decodes and decrypts a single value.
514
 *
515
 * @param string $value The value to decode & decrypt.
516
 * @return string Decoded value.
517
 */
518
        protected function _decode($value) {
519
                $prefix = 'Q2FrZQ==.';
520
                $pos = strpos($value, $prefix);
521
                if ($pos === false) {
522
                        return $this->_explode($value);
523
                }
524
                $value = base64_decode(substr($value, strlen($prefix)));
525
                if ($this->_type === 'rijndael') {
526
                        $plain = Security::rijndael($value, $this->key, 'decrypt');
527
                }
528
                if ($this->_type === 'cipher') {
529
                        $plain = Security::cipher($value, $this->key);
530
                }
531
                if ($this->_type === 'aes') {
532
                        $plain = Security::decrypt($value, $this->key);
533
                }
534
                return $this->_explode($plain);
535
        }
536

    
537
/**
538
 * Implode method to keep keys are multidimensional arrays
539
 *
540
 * @param array $array Map of key and values
541
 * @return string A json encoded string.
542
 */
543
        protected function _implode(array $array) {
544
                return json_encode($array);
545
        }
546

    
547
/**
548
 * Explode method to return array from string set in CookieComponent::_implode()
549
 * Maintains reading backwards compatibility with 1.x CookieComponent::_implode().
550
 *
551
 * @param string $string A string containing JSON encoded data, or a bare string.
552
 * @return array Map of key and values
553
 */
554
        protected function _explode($string) {
555
                $first = substr($string, 0, 1);
556
                if ($first === '{' || $first === '[') {
557
                        $ret = json_decode($string, true);
558
                        return ($ret !== null) ? $ret : $string;
559
                }
560
                $array = array();
561
                foreach (explode(',', $string) as $pair) {
562
                        $key = explode('|', $pair);
563
                        if (!isset($key[1])) {
564
                                return $key[0];
565
                        }
566
                        $array[$key[0]] = $key[1];
567
                }
568
                return $array;
569
        }
570
}