Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
16591 anikendra 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
 */
32
class CookieComponent extends Component {
33
 
34
/**
35
 * The name of the cookie.
36
 *
37
 * Overridden with the controller beforeFilter();
38
 * $this->Cookie->name = 'CookieName';
39
 *
40
 * @var string
41
 */
42
	public $name = 'CakeCookie';
43
 
44
/**
45
 * The time a cookie will remain valid.
46
 *
47
 * Can be either integer Unix timestamp or a date string.
48
 *
49
 * Overridden with the controller beforeFilter();
50
 * $this->Cookie->time = '5 Days';
51
 *
52
 * @var mixed
53
 */
54
	public $time = null;
55
 
56
/**
57
 * Cookie path.
58
 *
59
 * Overridden with the controller beforeFilter();
60
 * $this->Cookie->path = '/';
61
 *
62
 * The path on the server in which the cookie will be available on.
63
 * If public $cookiePath is set to '/foo/', the cookie will only be available
64
 * within the /foo/ directory and all sub-directories such as /foo/bar/ of domain.
65
 * The default value is the entire domain.
66
 *
67
 * @var string
68
 */
69
	public $path = '/';
70
 
71
/**
72
 * Domain path.
73
 *
74
 * The domain that the cookie is available.
75
 *
76
 * Overridden with the controller beforeFilter();
77
 * $this->Cookie->domain = '.example.com';
78
 *
79
 * To make the cookie available on all subdomains of example.com.
80
 * Set $this->Cookie->domain = '.example.com'; in your controller beforeFilter
81
 *
82
 * @var string
83
 */
84
	public $domain = '';
85
 
86
/**
87
 * Secure HTTPS only cookie.
88
 *
89
 * Overridden with the controller beforeFilter();
90
 * $this->Cookie->secure = true;
91
 *
92
 * Indicates that the cookie should only be transmitted over a secure HTTPS connection.
93
 * When set to true, the cookie will only be set if a secure connection exists.
94
 *
95
 * @var bool
96
 */
97
	public $secure = false;
98
 
99
/**
100
 * Encryption key.
101
 *
102
 * Overridden with the controller beforeFilter();
103
 * $this->Cookie->key = 'SomeRandomString';
104
 *
105
 * @var string
106
 */
107
	public $key = null;
108
 
109
/**
110
 * HTTP only cookie
111
 *
112
 * Set to true to make HTTP only cookies. Cookies that are HTTP only
113
 * are not accessible in JavaScript.
114
 *
115
 * @var bool
116
 */
117
	public $httpOnly = false;
118
 
119
/**
120
 * Values stored in the cookie.
121
 *
122
 * Accessed in the controller using $this->Cookie->read('Name.key');
123
 *
124
 * @see CookieComponent::read();
125
 * @var string
126
 */
127
	protected $_values = array();
128
 
129
/**
130
 * Type of encryption to use.
131
 *
132
 * Currently two methods are available: cipher and rijndael
133
 * Defaults to Security::cipher(). Cipher is horribly insecure and only
134
 * the default because of backwards compatibility. In new applications you should
135
 * always change this to 'aes' or 'rijndael'.
136
 *
137
 * @var string
138
 */
139
	protected $_type = 'cipher';
140
 
141
/**
142
 * Used to reset cookie time if $expire is passed to CookieComponent::write()
143
 *
144
 * @var string
145
 */
146
	protected $_reset = null;
147
 
148
/**
149
 * Expire time of the cookie
150
 *
151
 * This is controlled by CookieComponent::time;
152
 *
153
 * @var string
154
 */
155
	protected $_expires = 0;
156
 
157
/**
158
 * A reference to the Controller's CakeResponse object
159
 *
160
 * @var CakeResponse
161
 */
162
	protected $_response = null;
163
 
164
/**
165
 * Constructor
166
 *
167
 * @param ComponentCollection $collection A ComponentCollection for this component
168
 * @param array $settings Array of settings.
169
 */
170
	public function __construct(ComponentCollection $collection, $settings = array()) {
171
		$this->key = Configure::read('Security.salt');
172
		parent::__construct($collection, $settings);
173
		if (isset($this->time)) {
174
			$this->_expire($this->time);
175
		}
176
 
177
		$controller = $collection->getController();
178
		if ($controller && isset($controller->response)) {
179
			$this->_response = $controller->response;
180
		} else {
181
			$this->_response = new CakeResponse();
182
		}
183
	}
184
 
185
/**
186
 * Start CookieComponent for use in the controller
187
 *
188
 * @param Controller $controller Controller instance.
189
 * @return void
190
 */
191
	public function startup(Controller $controller) {
192
		$this->_expire($this->time);
193
 
194
		$this->_values[$this->name] = array();
195
	}
196
 
197
/**
198
 * Write a value to the $_COOKIE[$key];
199
 *
200
 * Optional [Name.], required key, optional $value, optional $encrypt, optional $expires
201
 * $this->Cookie->write('[Name.]key, $value);
202
 *
203
 * By default all values are encrypted.
204
 * You must pass $encrypt false to store values in clear test
205
 *
206
 * You must use this method before any output is sent to the browser.
207
 * Failure to do so will result in header already sent errors.
208
 *
209
 * @param string|array $key Key for the value
210
 * @param mixed $value Value
211
 * @param bool $encrypt Set to true to encrypt value, false otherwise
212
 * @param int|string $expires Can be either the number of seconds until a cookie
213
 *   expires, or a strtotime compatible time offset.
214
 * @return void
215
 * @link http://book.cakephp.org/2.0/en/core-libraries/components/cookie.html#CookieComponent::write
216
 */
217
	public function write($key, $value = null, $encrypt = true, $expires = null) {
218
		if (empty($this->_values[$this->name])) {
219
			$this->read();
220
		}
221
 
222
		if ($encrypt === null) {
223
			$encrypt = true;
224
		}
225
		$this->_encrypted = $encrypt;
226
		$this->_expire($expires);
227
 
228
		if (!is_array($key)) {
229
			$key = array($key => $value);
230
		}
231
 
232
		foreach ($key as $name => $value) {
233
			$names = array($name);
234
			if (strpos($name, '.') !== false) {
235
				$names = explode('.', $name, 2);
236
			}
237
			$firstName = $names[0];
238
			$isMultiValue = (is_array($value) || count($names) > 1);
239
 
240
			if (!isset($this->_values[$this->name][$firstName]) && $isMultiValue) {
241
				$this->_values[$this->name][$firstName] = array();
242
			}
243
 
244
			if (count($names) > 1) {
245
				$this->_values[$this->name][$firstName] = Hash::insert(
246
					$this->_values[$this->name][$firstName],
247
					$names[1],
248
					$value
249
				);
250
			} else {
251
				$this->_values[$this->name][$firstName] = $value;
252
			}
253
			$this->_write('[' . $firstName . ']', $this->_values[$this->name][$firstName]);
254
		}
255
		$this->_encrypted = true;
256
	}
257
 
258
/**
259
 * Read the value of the $_COOKIE[$key];
260
 *
261
 * Optional [Name.], required key
262
 * $this->Cookie->read(Name.key);
263
 *
264
 * @param string $key Key of the value to be obtained. If none specified, obtain map key => values
265
 * @return string|null Value for specified key
266
 * @link http://book.cakephp.org/2.0/en/core-libraries/components/cookie.html#CookieComponent::read
267
 */
268
	public function read($key = null) {
269
		if (empty($this->_values[$this->name]) && isset($_COOKIE[$this->name])) {
270
			$this->_values[$this->name] = $this->_decrypt($_COOKIE[$this->name]);
271
		}
272
		if (empty($this->_values[$this->name])) {
273
			$this->_values[$this->name] = array();
274
		}
275
		if ($key === null) {
276
			return $this->_values[$this->name];
277
		}
278
 
279
		if (strpos($key, '.') !== false) {
280
			$names = explode('.', $key, 2);
281
			$key = $names[0];
282
		}
283
		if (!isset($this->_values[$this->name][$key])) {
284
			return null;
285
		}
286
 
287
		if (!empty($names[1]) && is_array($this->_values[$this->name][$key])) {
288
			return Hash::get($this->_values[$this->name][$key], $names[1]);
289
		}
290
		return $this->_values[$this->name][$key];
291
	}
292
 
293
/**
294
 * Returns true if given variable is set in cookie.
295
 *
296
 * @param string $key Variable name to check for
297
 * @return bool True if variable is there
298
 */
299
	public function check($key = null) {
300
		if (empty($key)) {
301
			return false;
302
		}
303
		return $this->read($key) !== null;
304
	}
305
 
306
/**
307
 * Delete a cookie value
308
 *
309
 * Optional [Name.], required key
310
 * $this->Cookie->delete('Name.key);
311
 *
312
 * You must use this method before any output is sent to the browser.
313
 * Failure to do so will result in header already sent errors.
314
 *
315
 * This method will delete both the top level and 2nd level cookies set.
316
 * For example assuming that $name = App, deleting `User` will delete
317
 * both `App[User]` and any other cookie values like `App[User][email]`
318
 * This is done to clean up cookie storage from before 2.4.3, where cookies
319
 * were stored inconsistently.
320
 *
321
 * @param string $key Key of the value to be deleted
322
 * @return void
323
 * @link http://book.cakephp.org/2.0/en/core-libraries/components/cookie.html#CookieComponent::delete
324
 */
325
	public function delete($key) {
326
		if (empty($this->_values[$this->name])) {
327
			$this->read();
328
		}
329
		if (strpos($key, '.') === false) {
330
			if (isset($this->_values[$this->name][$key]) && is_array($this->_values[$this->name][$key])) {
331
				foreach ($this->_values[$this->name][$key] as $idx => $val) {
332
					$this->_delete("[$key][$idx]");
333
				}
334
			}
335
			$this->_delete("[$key]");
336
			unset($this->_values[$this->name][$key]);
337
			return;
338
		}
339
		$names = explode('.', $key, 2);
340
		if (isset($this->_values[$this->name][$names[0]])) {
341
			$this->_values[$this->name][$names[0]] = Hash::remove($this->_values[$this->name][$names[0]], $names[1]);
342
		}
343
		$this->_delete('[' . implode('][', $names) . ']');
344
	}
345
 
346
/**
347
 * Destroy current cookie
348
 *
349
 * You must use this method before any output is sent to the browser.
350
 * Failure to do so will result in header already sent errors.
351
 *
352
 * @return void
353
 * @link http://book.cakephp.org/2.0/en/core-libraries/components/cookie.html#CookieComponent::destroy
354
 */
355
	public function destroy() {
356
		if (isset($_COOKIE[$this->name])) {
357
			$this->_values[$this->name] = $this->_decrypt($_COOKIE[$this->name]);
358
		}
359
 
360
		foreach ($this->_values[$this->name] as $name => $value) {
361
			if (is_array($value)) {
362
				foreach ($value as $key => $val) {
363
					unset($this->_values[$this->name][$name][$key]);
364
					$this->_delete("[$name][$key]");
365
				}
366
			}
367
			unset($this->_values[$this->name][$name]);
368
			$this->_delete("[$name]");
369
		}
370
	}
371
 
372
/**
373
 * Will allow overriding default encryption method. Use this method
374
 * in ex: AppController::beforeFilter() before you have read or
375
 * written any cookies.
376
 *
377
 * @param string $type Encryption method
378
 * @return void
379
 */
380
	public function type($type = 'cipher') {
381
		$availableTypes = array(
382
			'cipher',
383
			'rijndael',
384
			'aes'
385
		);
386
		if (!in_array($type, $availableTypes)) {
387
			trigger_error(__d('cake_dev', 'You must use cipher, rijndael or aes for cookie encryption type'), E_USER_WARNING);
388
			$type = 'cipher';
389
		}
390
		$this->_type = $type;
391
	}
392
 
393
/**
394
 * Set the expire time for a session variable.
395
 *
396
 * Creates a new expire time for a session variable.
397
 * $expire can be either integer Unix timestamp or a date string.
398
 *
399
 * Used by write()
400
 * CookieComponent::write(string, string, boolean, 8400);
401
 * CookieComponent::write(string, string, boolean, '5 Days');
402
 *
403
 * @param int|string $expires Can be either Unix timestamp, or date string
404
 * @return int Unix timestamp
405
 */
406
	protected function _expire($expires = null) {
407
		if ($expires === null) {
408
			return $this->_expires;
409
		}
410
		$this->_reset = $this->_expires;
411
		if (!$expires) {
412
			return $this->_expires = 0;
413
		}
414
		$now = new DateTime();
415
 
416
		if (is_int($expires) || is_numeric($expires)) {
417
			return $this->_expires = $now->format('U') + (int)$expires;
418
		}
419
		$now->modify($expires);
420
		return $this->_expires = $now->format('U');
421
	}
422
 
423
/**
424
 * Set cookie
425
 *
426
 * @param string $name Name for cookie
427
 * @param string $value Value for cookie
428
 * @return void
429
 */
430
	protected function _write($name, $value) {
431
		$this->_response->cookie(array(
432
			'name' => $this->name . $name,
433
			'value' => $this->_encrypt($value),
434
			'expire' => $this->_expires,
435
			'path' => $this->path,
436
			'domain' => $this->domain,
437
			'secure' => $this->secure,
438
			'httpOnly' => $this->httpOnly
439
		));
440
 
441
		if (!empty($this->_reset)) {
442
			$this->_expires = $this->_reset;
443
			$this->_reset = null;
444
		}
445
	}
446
 
447
/**
448
 * Sets a cookie expire time to remove cookie value
449
 *
450
 * @param string $name Name of cookie
451
 * @return void
452
 */
453
	protected function _delete($name) {
454
		$this->_response->cookie(array(
455
			'name' => $this->name . $name,
456
			'value' => '',
457
			'expire' => time() - 42000,
458
			'path' => $this->path,
459
			'domain' => $this->domain,
460
			'secure' => $this->secure,
461
			'httpOnly' => $this->httpOnly
462
		));
463
	}
464
 
465
/**
466
 * Encrypts $value using public $type method in Security class
467
 *
468
 * @param string $value Value to encrypt
469
 * @return string Encoded values
470
 */
471
	protected function _encrypt($value) {
472
		if (is_array($value)) {
473
			$value = $this->_implode($value);
474
		}
475
		if (!$this->_encrypted) {
476
			return $value;
477
		}
478
		$prefix = "Q2FrZQ==.";
479
		if ($this->_type === 'rijndael') {
480
			$cipher = Security::rijndael($value, $this->key, 'encrypt');
481
		}
482
		if ($this->_type === 'cipher') {
483
			$cipher = Security::cipher($value, $this->key);
484
		}
485
		if ($this->_type === 'aes') {
486
			$cipher = Security::encrypt($value, $this->key);
487
		}
488
		return $prefix . base64_encode($cipher);
489
	}
490
 
491
/**
492
 * Decrypts $value using public $type method in Security class
493
 *
494
 * @param array $values Values to decrypt
495
 * @return string decrypted string
496
 */
497
	protected function _decrypt($values) {
498
		$decrypted = array();
499
		$type = $this->_type;
500
 
501
		foreach ((array)$values as $name => $value) {
502
			if (is_array($value)) {
503
				foreach ($value as $key => $val) {
504
					$decrypted[$name][$key] = $this->_decode($val);
505
				}
506
			} else {
507
				$decrypted[$name] = $this->_decode($value);
508
			}
509
		}
510
		return $decrypted;
511
	}
512
 
513
/**
514
 * Decodes and decrypts a single value.
515
 *
516
 * @param string $value The value to decode & decrypt.
517
 * @return string Decoded value.
518
 */
519
	protected function _decode($value) {
520
		$prefix = 'Q2FrZQ==.';
521
		$pos = strpos($value, $prefix);
522
		if ($pos === false) {
523
			return $this->_explode($value);
524
		}
525
		$value = base64_decode(substr($value, strlen($prefix)));
526
		if ($this->_type === 'rijndael') {
527
			$plain = Security::rijndael($value, $this->key, 'decrypt');
528
		}
529
		if ($this->_type === 'cipher') {
530
			$plain = Security::cipher($value, $this->key);
531
		}
532
		if ($this->_type === 'aes') {
533
			$plain = Security::decrypt($value, $this->key);
534
		}
535
		return $this->_explode($plain);
536
	}
537
 
538
/**
539
 * Implode method to keep keys are multidimensional arrays
540
 *
541
 * @param array $array Map of key and values
542
 * @return string A json encoded string.
543
 */
544
	protected function _implode(array $array) {
545
		return json_encode($array);
546
	}
547
 
548
/**
549
 * Explode method to return array from string set in CookieComponent::_implode()
550
 * Maintains reading backwards compatibility with 1.x CookieComponent::_implode().
551
 *
552
 * @param string $string A string containing JSON encoded data, or a bare string.
553
 * @return array Map of key and values
554
 */
555
	protected function _explode($string) {
556
		$first = substr($string, 0, 1);
557
		if ($first === '{' || $first === '[') {
558
			$ret = json_decode($string, true);
559
			return ($ret !== null) ? $ret : $string;
560
		}
561
		$array = array();
562
		foreach (explode(',', $string) as $pair) {
563
			$key = explode('|', $pair);
564
			if (!isset($key[1])) {
565
				return $key[0];
566
			}
567
			$array[$key[0]] = $key[1];
568
		}
569
		return $array;
570
	}
571
}