1: <?php
2: /* SVN FILE: $Id$ */
3: /**
4: * Short description for file.
5: *
6: * Long description for file
7: *
8: * PHP versions 4 and 5
9: *
10: * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
11: * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
12: *
13: * Licensed under The MIT License
14: * Redistributions of files must retain the above copyright notice.
15: *
16: * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
17: * @link http://cakephp.org CakePHP(tm) Project
18: * @package cake
19: * @subpackage cake.cake.libs.controller.components
20: * @since CakePHP(tm) v 1.2.0.4213
21: * @version $Revision$
22: * @modifiedby $LastChangedBy$
23: * @lastmodified $Date$
24: * @license http://www.opensource.org/licenses/mit-license.php The MIT License
25: */
26: /**
27: * Load Security class
28: */
29: App::import('Core', 'Security');
30: /**
31: * Cookie Component.
32: *
33: * Cookie handling for the controller.
34: *
35: * @package cake
36: * @subpackage cake.cake.libs.controller.components
37: *
38: */
39: class CookieComponent extends Object {
40: /**
41: * The name of the cookie.
42: *
43: * Overridden with the controller beforeFilter();
44: * $this->Cookie->name = 'CookieName';
45: *
46: * @var string
47: * @access public
48: */
49: var $name = 'CakeCookie';
50: /**
51: * The time a cookie will remain valid.
52: *
53: * Can be either integer Unix timestamp or a date string.
54: *
55: * Overridden with the controller beforeFilter();
56: * $this->Cookie->time = '5 Days';
57: *
58: * @var mixed
59: * @access public
60: */
61: var $time = null;
62: /**
63: * Cookie path.
64: *
65: * Overridden with the controller beforeFilter();
66: * $this->Cookie->path = '/';
67: *
68: * The path on the server in which the cookie will be available on.
69: * If var $cookiePath is set to '/foo/', the cookie will only be available
70: * within the /foo/ directory and all sub-directories such as /foo/bar/ of domain.
71: * The default value is the entire domain.
72: *
73: * @var string
74: * @access public
75: */
76: var $path = '/';
77: /**
78: * Domain path.
79: *
80: * The domain that the cookie is available.
81: *
82: * Overridden with the controller beforeFilter();
83: * $this->Cookie->domain = '.example.com';
84: *
85: * To make the cookie available on all subdomains of example.com.
86: * Set $this->Cookie->domain = '.example.com'; in your controller beforeFilter
87: *
88: * @var string
89: * @access public
90: */
91: var $domain = '';
92: /**
93: * Secure HTTPS only cookie.
94: *
95: * Overridden with the controller beforeFilter();
96: * $this->Cookie->secure = true;
97: *
98: * Indicates that the cookie should only be transmitted over a secure HTTPS connection.
99: * When set to true, the cookie will only be set if a secure connection exists.
100: *
101: * @var boolean
102: * @access public
103: */
104: var $secure = false;
105: /**
106: * Encryption key.
107: *
108: * Overridden with the controller beforeFilter();
109: * $this->Cookie->key = 'SomeRandomString';
110: *
111: * @var string
112: * @access protected
113: */
114: var $key = null;
115: /**
116: * Values stored in the cookie.
117: *
118: * Accessed in the controller using $this->Cookie->read('Name.key');
119: *
120: * @see CookieComponent::read();
121: * @var string
122: * @access private
123: */
124: var $__values = array();
125: /**
126: * Type of encryption to use.
127: *
128: * Currently only one method is available
129: * Defaults to Security::cipher();
130: *
131: * @var string
132: * @access private
133: * @todo add additional encryption methods
134: */
135: var $__type = 'cipher';
136: /**
137: * Used to reset cookie time if $expire is passed to CookieComponent::write()
138: *
139: * @var string
140: * @access private
141: */
142: var $__reset = null;
143: /**
144: * Expire time of the cookie
145: *
146: * This is controlled by CookieComponent::time;
147: *
148: * @var string
149: * @access private
150: */
151: var $__expires = 0;
152: /**
153: * Main execution method.
154: *
155: * @param object $controller A reference to the instantiating controller object
156: * @access public
157: */
158: function initialize(&$controller, $settings) {
159: $this->key = Configure::read('Security.salt');
160: $this->_set($settings);
161: }
162: /**
163: * Start CookieComponent for use in the controller
164: *
165: * @access public
166: */
167: function startup() {
168: $this->__expire($this->time);
169:
170: if (isset($_COOKIE[$this->name])) {
171: $this->__values = $this->__decrypt($_COOKIE[$this->name]);
172: }
173: }
174: /**
175: * Write a value to the $_COOKIE[$key];
176: *
177: * Optional [Name.], required key, optional $value, optional $encrypt, optional $expires
178: * $this->Cookie->write('[Name.]key, $value);
179: *
180: * By default all values are encrypted.
181: * You must pass $encrypt false to store values in clear test
182: *
183: * You must use this method before any output is sent to the browser.
184: * Failure to do so will result in header already sent errors.
185: *
186: * @param mixed $key Key for the value
187: * @param mixed $value Value
188: * @param boolean $encrypt Set to true to encrypt value, false otherwise
189: * @param string $expires Can be either Unix timestamp, or date string
190: * @access public
191: */
192: function write($key, $value = null, $encrypt = true, $expires = null) {
193: if (is_null($encrypt)) {
194: $encrypt = true;
195: }
196:
197: $this->__encrypted = $encrypt;
198: $this->__expire($expires);
199:
200: if (!is_array($key) && $value !== null) {
201: $name = $this->__cookieVarNames($key);
202:
203: if (count($name) > 1) {
204: $this->__values[$name[0]][$name[1]] = $value;
205: $this->__write("[" . $name[0] . "][" . $name[1] . "]", $value);
206: } else {
207: $this->__values[$name[0]] = $value;
208: $this->__write("[" . $name[0] . "]", $value);
209: }
210: } else {
211: foreach ($key as $names => $value) {
212: $name = $this->__cookieVarNames($names);
213:
214: if (count($name) > 1) {
215: $this->__values[$name[0]][$name[1]] = $value;
216: $this->__write("[" . $name[0] . "][" . $name[1] . "]", $value);
217: } else {
218: $this->__values[$name[0]] = $value;
219: $this->__write("[" . $name[0] . "]", $value);
220: }
221: }
222: }
223: $this->__encrypted = true;
224: }
225: /**
226: * Read the value of the $_COOKIE[$key];
227: *
228: * Optional [Name.], required key
229: * $this->Cookie->read(Name.key);
230: *
231: * @param mixed $key Key of the value to be obtained. If none specified, obtain map key => values
232: * @return string or null, value for specified key
233: * @access public
234: */
235: function read($key = null) {
236: if (empty($this->__values) && isset($_COOKIE[$this->name])) {
237: $this->__values = $this->__decrypt($_COOKIE[$this->name]);
238: }
239:
240: if (is_null($key)) {
241: return $this->__values;
242: }
243: $name = $this->__cookieVarNames($key);
244:
245: if (count($name) > 1) {
246: if (isset($this->__values[$name[0]])) {
247: if (isset($this->__values[$name[0]][$name[1]])) {
248: return $this->__values[$name[0]][$name[1]];
249: }
250: }
251: return null;
252: } else {
253: if (isset($this->__values[$name[0]])) {
254: $value = $this->__values[$name[0]];
255: return $value;
256: }
257: return null;
258: }
259: }
260: /**
261: * Delete a cookie value
262: *
263: * Optional [Name.], required key
264: * $this->Cookie->read('Name.key);
265: *
266: * You must use this method before any output is sent to the browser.
267: * Failure to do so will result in header already sent errors.
268: *
269: * @param string $key Key of the value to be deleted
270: * @return void
271: * @access public
272: */
273: function del($key) {
274: if (empty($this->__values)) {
275: $this->read();
276: }
277: $name = $this->__cookieVarNames($key);
278: if (count($name) > 1) {
279: if (isset($this->__values[$name[0]])) {
280: $this->__delete("[" . $name[0] . "][" . $name[1] . "]");
281: unset($this->__values[$name[0]][$name[1]]);
282: }
283: } else {
284: if (isset($this->__values[$name[0]])) {
285: if (is_array($this->__values[$name[0]])) {
286: foreach ($this->__values[$name[0]] as $key => $value) {
287: $this->__delete("[" . $name[0] . "][" . $key . "]");
288: }
289: }
290: $this->__delete("[" . $name[0] . "]");
291: unset($this->__values[$name[0]]);
292: }
293: }
294: }
295: /**
296: * Destroy current cookie
297: *
298: * You must use this method before any output is sent to the browser.
299: * Failure to do so will result in header already sent errors.
300: *
301: * @return void
302: * @access public
303: */
304: function destroy() {
305: if (isset($_COOKIE[$this->name])) {
306: $this->__values = $this->__decrypt($_COOKIE[$this->name]);
307: }
308:
309: foreach ($this->__values as $name => $value) {
310: if (is_array($value)) {
311: foreach ($value as $key => $val) {
312: unset($this->__values[$name][$key]);
313: $this->__delete("[$name][$key]");
314: }
315: }
316: unset($this->__values[$name]);
317: $this->__delete("[$name]");
318: }
319: }
320: /**
321: * Will allow overriding default encryption method.
322: *
323: * @param string $type Encryption method
324: * @access public
325: * @todo NOT IMPLEMENTED
326: */
327: function type($type = 'cipher') {
328: $this->__type = 'cipher';
329: }
330: /**
331: * Set the expire time for a session variable.
332: *
333: * Creates a new expire time for a session variable.
334: * $expire can be either integer Unix timestamp or a date string.
335: *
336: * Used by write()
337: * CookieComponent::write(string, string, boolean, 8400);
338: * CookieComponent::write(string, string, boolean, '5 Days');
339: *
340: * @param mixed $expires Can be either Unix timestamp, or date string
341: * @return int Unix timestamp
342: * @access private
343: */
344: function __expire($expires = null) {
345: $now = time();
346: if (is_null($expires)) {
347: return $this->__expires;
348: }
349: $this->__reset = $this->__expires;
350: if (is_int($expires) || is_numeric($expires)) {
351: return $this->__expires = $now + intval($expires);
352: }
353: return $this->__expires = strtotime($expires, $now);
354: }
355: /**
356: * Set cookie
357: *
358: * @param string $name Name for cookie
359: * @param string $value Value for cookie
360: * @access private
361: */
362: function __write($name, $value) {
363: setcookie($this->name . "$name", $this->__encrypt($value), $this->__expires, $this->path, $this->domain, $this->secure);
364:
365: if (!is_null($this->__reset)) {
366: $this->__expires = $this->__reset;
367: $this->__reset = null;
368: }
369: }
370: /**
371: * Sets a cookie expire time to remove cookie value
372: *
373: * @param string $name Name of cookie
374: * @access private
375: */
376: function __delete($name) {
377: setcookie($this->name . $name, '', time() - 42000, $this->path, $this->domain, $this->secure);
378: }
379: /**
380: * Encrypts $value using var $type method in Security class
381: *
382: * @param string $value Value to encrypt
383: * @return string encrypted string
384: * @access private
385: */
386: function __encrypt($value) {
387: if (is_array($value)) {
388: $value = $this->__implode($value);
389: }
390:
391: if ($this->__encrypted === true) {
392: $type = $this->__type;
393: $value = "Q2FrZQ==." .base64_encode(Security::$type($value, $this->key));
394: }
395: return($value);
396: }
397: /**
398: * Decrypts $value using var $type method in Security class
399: *
400: * @param array $values Values to decrypt
401: * @return string decrypted string
402: * @access private
403: */
404: function __decrypt($values) {
405: $decrypted = array();
406: $type = $this->__type;
407:
408: foreach ((array)$values as $name => $value) {
409: if (is_array($value)) {
410: foreach ($value as $key => $val) {
411: $pos = strpos($val, 'Q2FrZQ==.');
412: $decrypted[$name][$key] = $this->__explode($val);
413:
414: if ($pos !== false) {
415: $val = substr($val, 8);
416: $decrypted[$name][$key] = $this->__explode(Security::$type(base64_decode($val), $this->key));
417: }
418: }
419: } else {
420: $pos = strpos($value, 'Q2FrZQ==.');
421: $decrypted[$name] = $this->__explode($value);
422:
423: if ($pos !== false) {
424: $value = substr($value, 8);
425: $decrypted[$name] = $this->__explode(Security::$type(base64_decode($value), $this->key));
426: }
427: }
428: }
429:
430: return($decrypted);
431: }
432:
433: /**
434: * Creates an array from the $name parameter which allows the dot notation
435: * similar to one used by Session and Configure classes
436: *
437: * @param string $name Name with or without dot notation
438: * @return array Extracted names
439: * @access private
440: */
441: function __cookieVarNames($name) {
442: if (is_string($name)) {
443: if (strpos($name, ".")) {
444: $name = explode(".", $name);
445: } else {
446: $name = array($name);
447: }
448: }
449: return $name;
450: }
451: /**
452: * Implode method to keep keys are multidimensional arrays
453: *
454: * @param array $array Map of key and values
455: * @return string String in the form key1|value1,key2|value2
456: * @access private
457: */
458: function __implode($array) {
459: $string = '';
460: foreach ($array as $key => $value) {
461: $string .= ',' . $key . '|' . $value;
462: }
463: return substr($string, 1);
464: }
465: /**
466: * Explode method to return array from string set in CookieComponent::__implode()
467: *
468: * @param string $string String in the form key1|value1,key2|value2
469: * @return array Map of key and values
470: * @access private
471: */
472: function __explode($string) {
473: $array = array();
474: foreach (explode(',', $string) as $pair) {
475: $key = explode('|', $pair);
476: if (!isset($key[1])) {
477: return $key[0];
478: }
479: $array[$key[0]] = $key[1];
480: }
481: return $array;
482: }
483: }
484: ?>
485: