CookieComponent.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 1.2.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Controller\Component;
  16. use Cake\Controller\Component;
  17. use Cake\Http\ServerRequest;
  18. use Cake\I18n\Time;
  19. use Cake\Utility\CookieCryptTrait;
  20. use Cake\Utility\Hash;
  21. use Cake\Utility\Security;
  22. /**
  23. * Cookie Component.
  24. *
  25. * Provides enhanced cookie handling features for use in the controller layer.
  26. * In addition to the basic features offered be Cake\Http\Response, this class lets you:
  27. *
  28. * - Create and read encrypted cookies.
  29. * - Store non-scalar data.
  30. * - Use hash compatible syntax to read/write/delete values.
  31. *
  32. * @link https://book.cakephp.org/3.0/en/controllers/components/cookie.html
  33. * @deprecated 3.5.0 Use Cake\Http\Middleware\EncryptedCookieMiddleware and Cake\Http\Cookie\Cookie methods instead.
  34. */
  35. class CookieComponent extends Component
  36. {
  37. use CookieCryptTrait;
  38. /**
  39. * Default config
  40. *
  41. * - `expires` - How long the cookies should last for. Defaults to 1 month.
  42. * - `path` - The path on the server in which the cookie will be available on.
  43. * If path is set to '/foo/', the cookie will only be available within the
  44. * /foo/ directory and all sub-directories such as /foo/bar/ of domain.
  45. * The default value is base path of app. For e.g. if your app is running
  46. * under a subfolder "cakeapp" of document root the path would be "/cakeapp/"
  47. * else it would be "/".
  48. * - `domain` - The domain that the cookie is available. To make the cookie
  49. * available on all subdomains of example.com set domain to '.example.com'.
  50. * - `secure` - Indicates that the cookie should only be transmitted over a
  51. * secure HTTPS connection. When set to true, the cookie will only be set if
  52. * a secure connection exists.
  53. * - `key` - Encryption key used when encrypted cookies are enabled. Defaults to Security.salt.
  54. * - `httpOnly` - Set to true to make HTTP only cookies. Cookies that are HTTP only
  55. * are not accessible in JavaScript. Default false.
  56. * - `encryption` - Type of encryption to use. Defaults to 'aes'.
  57. *
  58. * @var array
  59. */
  60. protected $_defaultConfig = [
  61. 'path' => null,
  62. 'domain' => '',
  63. 'secure' => false,
  64. 'key' => null,
  65. 'httpOnly' => false,
  66. 'encryption' => 'aes',
  67. 'expires' => '+1 month',
  68. ];
  69. /**
  70. * Config specific to a given top level key name.
  71. *
  72. * The values in this array are merged with the general config
  73. * to generate the configuration for a given top level cookie name.
  74. *
  75. * @var array
  76. */
  77. protected $_keyConfig = [];
  78. /**
  79. * Values stored in the cookie.
  80. *
  81. * Accessed in the controller using $this->Cookie->read('Name.key');
  82. *
  83. * @var array
  84. */
  85. protected $_values = [];
  86. /**
  87. * A map of keys that have been loaded.
  88. *
  89. * Since CookieComponent lazily reads cookie data,
  90. * we need to track which cookies have been read to account for
  91. * read, delete, read patterns.
  92. *
  93. * @var array
  94. */
  95. protected $_loaded = [];
  96. /**
  97. * A reference to the Controller's Cake\Http\Response object.
  98. * Currently unused.
  99. *
  100. * @var \Cake\Http\Response|null
  101. * @deprecated 3.4.0 Will be removed in 4.0.0
  102. */
  103. protected $_response;
  104. /**
  105. * Initialize config data and properties.
  106. *
  107. * @param array $config The config data.
  108. * @return void
  109. */
  110. public function initialize(array $config)
  111. {
  112. if (!$this->_config['key']) {
  113. $this->setConfig('key', Security::getSalt());
  114. }
  115. $controller = $this->_registry->getController();
  116. if ($controller === null) {
  117. $this->request = ServerRequest::createFromGlobals();
  118. }
  119. if (empty($this->_config['path'])) {
  120. $this->setConfig('path', $this->request->webroot);
  121. }
  122. }
  123. /**
  124. * Set the configuration for a specific top level key.
  125. *
  126. * ### Examples:
  127. *
  128. * Set a single config option for a key:
  129. *
  130. * ```
  131. * $this->Cookie->configKey('User', 'expires', '+3 months');
  132. * ```
  133. *
  134. * Set multiple options:
  135. *
  136. * ```
  137. * $this->Cookie->configKey('User', [
  138. * 'expires', '+3 months',
  139. * 'httpOnly' => true,
  140. * ]);
  141. * ```
  142. *
  143. * @param string $keyname The top level keyname to configure.
  144. * @param null|string|array $option Either the option name to set, or an array of options to set,
  145. * or null to read config options for a given key.
  146. * @param string|null $value Either the value to set, or empty when $option is an array.
  147. * @return array|null
  148. */
  149. public function configKey($keyname, $option = null, $value = null)
  150. {
  151. if ($option === null) {
  152. $default = $this->_config;
  153. $local = isset($this->_keyConfig[$keyname]) ? $this->_keyConfig[$keyname] : [];
  154. return $local + $default;
  155. }
  156. if (!is_array($option)) {
  157. $option = [$option => $value];
  158. }
  159. $this->_keyConfig[$keyname] = $option;
  160. return null;
  161. }
  162. /**
  163. * Events supported by this component.
  164. *
  165. * @return array
  166. */
  167. public function implementedEvents()
  168. {
  169. return [];
  170. }
  171. /**
  172. * Write a value to the response cookies.
  173. *
  174. * You must use this method before any output is sent to the browser.
  175. * Failure to do so will result in header already sent errors.
  176. *
  177. * @param string|array $key Key for the value
  178. * @param mixed $value Value
  179. * @return void
  180. */
  181. public function write($key, $value = null)
  182. {
  183. if (!is_array($key)) {
  184. $key = [$key => $value];
  185. }
  186. $keys = [];
  187. foreach ($key as $name => $value) {
  188. $this->_load($name);
  189. $this->_values = Hash::insert($this->_values, $name, $value);
  190. $parts = explode('.', $name);
  191. $keys[] = $parts[0];
  192. }
  193. foreach ($keys as $name) {
  194. $this->_write($name, $this->_values[$name]);
  195. }
  196. }
  197. /**
  198. * Read the value of key path from request cookies.
  199. *
  200. * This method will also allow you to read cookies that have been written in this
  201. * request, but not yet sent to the client.
  202. *
  203. * @param string|null $key Key of the value to be obtained.
  204. * @return string or null, value for specified key
  205. */
  206. public function read($key = null)
  207. {
  208. $this->_load($key);
  209. return Hash::get($this->_values, $key);
  210. }
  211. /**
  212. * Load the cookie data from the request and response objects.
  213. *
  214. * Based on the configuration data, cookies will be decrypted. When cookies
  215. * contain array data, that data will be expanded.
  216. *
  217. * @param string|array $key The key to load.
  218. * @return void
  219. */
  220. protected function _load($key)
  221. {
  222. $parts = explode('.', $key);
  223. $first = array_shift($parts);
  224. if (isset($this->_loaded[$first])) {
  225. return;
  226. }
  227. if (!isset($this->request->cookies[$first])) {
  228. return;
  229. }
  230. $cookie = $this->request->cookies[$first];
  231. $config = $this->configKey($first);
  232. $this->_loaded[$first] = true;
  233. $this->_values[$first] = $this->_decrypt($cookie, $config['encryption'], $config['key']);
  234. }
  235. /**
  236. * Returns true if given key is set in the cookie.
  237. *
  238. * @param string|null $key Key to check for
  239. * @return bool True if the key exists
  240. */
  241. public function check($key = null)
  242. {
  243. if (empty($key)) {
  244. return false;
  245. }
  246. return $this->read($key) !== null;
  247. }
  248. /**
  249. * Delete a cookie value
  250. *
  251. * You must use this method before any output is sent to the browser.
  252. * Failure to do so will result in header already sent errors.
  253. *
  254. * Deleting a top level key will delete all keys nested within that key.
  255. * For example deleting the `User` key, will also delete `User.email`.
  256. *
  257. * @param string $key Key of the value to be deleted
  258. * @return void
  259. */
  260. public function delete($key)
  261. {
  262. $this->_load($key);
  263. $this->_values = Hash::remove($this->_values, $key);
  264. $parts = explode('.', $key);
  265. $top = $parts[0];
  266. if (isset($this->_values[$top])) {
  267. $this->_write($top, $this->_values[$top]);
  268. } else {
  269. $this->_delete($top);
  270. }
  271. }
  272. /**
  273. * Set cookie
  274. *
  275. * @param string $name Name for cookie
  276. * @param string $value Value for cookie
  277. * @return void
  278. */
  279. protected function _write($name, $value)
  280. {
  281. $config = $this->configKey($name);
  282. $expires = new Time($config['expires']);
  283. $response = $this->getController()->response;
  284. $response->cookie([
  285. 'name' => $name,
  286. 'value' => $this->_encrypt($value, $config['encryption'], $config['key']),
  287. 'expire' => $expires->format('U'),
  288. 'path' => $config['path'],
  289. 'domain' => $config['domain'],
  290. 'secure' => (bool)$config['secure'],
  291. 'httpOnly' => (bool)$config['httpOnly']
  292. ]);
  293. }
  294. /**
  295. * Sets a cookie expire time to remove cookie value.
  296. *
  297. * This is only done once all values in a cookie key have been
  298. * removed with delete.
  299. *
  300. * @param string $name Name of cookie
  301. * @return void
  302. */
  303. protected function _delete($name)
  304. {
  305. $config = $this->configKey($name);
  306. $expires = new Time('now');
  307. $response = $this->getController()->response;
  308. $response->cookie([
  309. 'name' => $name,
  310. 'value' => '',
  311. 'expire' => $expires->format('U') - 42000,
  312. 'path' => $config['path'],
  313. 'domain' => $config['domain'],
  314. 'secure' => $config['secure'],
  315. 'httpOnly' => $config['httpOnly']
  316. ]);
  317. }
  318. /**
  319. * Returns the encryption key to be used.
  320. *
  321. * @return string
  322. */
  323. protected function _getCookieEncryptionKey()
  324. {
  325. return $this->_config['key'];
  326. }
  327. }