PageRenderTime 36ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/users/CookieStorage.php

http://showslow.googlecode.com/
PHP | 249 lines | 179 code | 20 blank | 50 comment | 19 complexity | 3ac50e0c9be4b0979120d021c495850b MD5 | raw file
  1. <?php
  2. /**
  3. * Store tamper-proof strings in an HTTP cookie
  4. *
  5. * Source: http://code.google.com/p/mrclay/source/browse/trunk/php/MrClay/CookieStorage.php
  6. *
  7. * <code>
  8. * $storage = new MrClay_CookieStorage(array(
  9. * 'secret' => '67676kmcuiekihbfyhbtfitfytrdo=op-p-=[hH8'
  10. * ));
  11. * if ($storage->store('user', 'id:62572,email:bob@yahoo.com,name:Bob')) {
  12. * // cookie OK length and no complaints from setcookie()
  13. * } else {
  14. * // check $storage->errors
  15. * }
  16. *
  17. * // later request
  18. * $user = $storage->fetch('user');
  19. * if (is_string($user)) {
  20. * // valid cookie
  21. * $age = time() - $storage->getTimestamp('user');
  22. * } else {
  23. * if (false === $user) {
  24. * // data was altered!
  25. * } else {
  26. * // cookie not present
  27. * }
  28. * }
  29. *
  30. * // encrypt cookie contents
  31. * $storage = new MrClay_CookieStorage(array(
  32. * 'secret' => '67676kmcuiekihbfyhbtfitfytrdo=op-p-=[hH8'
  33. * ,'mode' => MrClay_CookieStorage::MODE_ENCRYPT
  34. * ));
  35. * </code>
  36. */
  37. class MrClay_CookieStorage {
  38. // conservative storage limit considering variable-length Set-Cookie header
  39. const LENGTH_LIMIT = 3896;
  40. const MODE_VISIBLE = 0;
  41. const MODE_ENCRYPT = 1;
  42. /**
  43. * @var array errors that occured
  44. */
  45. public $errors = array();
  46. public function __construct($options = array())
  47. {
  48. $this->_o = array_merge(self::getDefaults(), $options);
  49. }
  50. public static function hash($input)
  51. {
  52. return str_replace('=', '', base64_encode(hash('ripemd160', $input, true)));
  53. }
  54. public static function encrypt($key, $str)
  55. {
  56. $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
  57. $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
  58. $data = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $str, MCRYPT_MODE_ECB, $iv);
  59. return base64_encode($data);
  60. }
  61. public static function decrypt($key, $data)
  62. {
  63. if (false === ($data = base64_decode($data))) {
  64. return false;
  65. }
  66. $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
  67. $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
  68. return mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_ECB, $iv);
  69. }
  70. public function getDefaults()
  71. {
  72. return array(
  73. 'secret' => ''
  74. ,'domain' => ''
  75. ,'secure' => false
  76. ,'path' => '/'
  77. ,'expire' => '2147368447' // Sun, 17-Jan-2038 19:14:07 GMT (Google)
  78. ,'hashFunc' => array('MrClay_CookieStorage', 'hash')
  79. ,'encryptFunc' => array('MrClay_CookieStorage', 'encrypt')
  80. ,'decryptFunc' => array('MrClay_CookieStorage', 'decrypt')
  81. ,'mode' => self::MODE_VISIBLE
  82. ,'httponly' => false
  83. );
  84. }
  85. public function setOption($name, $value)
  86. {
  87. $this->_o[$name] = $value;
  88. }
  89. /**
  90. * @return bool success
  91. */
  92. public function store($name, $str)
  93. {
  94. if (empty($this->_o['secret'])) {
  95. $this->errors[] = 'Must first set the option: secret.';
  96. return false;
  97. }
  98. return ($this->_o['mode'] === self::MODE_ENCRYPT)
  99. ? $this->_storeEncrypted($name, $str)
  100. : $this->_store($name, $str);
  101. }
  102. private function _store($name, $str)
  103. {
  104. if (! is_callable($this->_o['hashFunc'])) {
  105. $this->errors[] = 'Hash function not callable';
  106. return false;
  107. }
  108. $time = base_convert($_SERVER['REQUEST_TIME'], 10, 36); // pack time
  109. // tie sig to this cookie name
  110. $hashInput = $this->_o['secret'] . $name . $time . $str;
  111. $sig = call_user_func($this->_o['hashFunc'], $hashInput);
  112. $raw = $sig . '|' . $time . '|' . $str;
  113. if (strlen($name . $raw) > self::LENGTH_LIMIT) {
  114. $this->errors[] = 'Cookie is likely too large to store.';
  115. return false;
  116. }
  117. $res = setcookie($name, $raw, $this->_o['expire'], $this->_o['path'],
  118. $this->_o['domain'], $this->_o['secure'], $this->_o['httponly']);
  119. if ($res) {
  120. return true;
  121. } else {
  122. $this->errors[] = 'Setcookie() returned false. Headers may have been sent.';
  123. return false;
  124. }
  125. }
  126. private function _storeEncrypted($name, $str)
  127. {
  128. if (! is_callable($this->_o['encryptFunc'])) {
  129. $this->errors[] = 'Encrypt function not callable';
  130. return false;
  131. }
  132. $time = base_convert($_SERVER['REQUEST_TIME'], 10, 36); // pack time
  133. $key = self::hash($this->_o['secret']);
  134. $raw = call_user_func($this->_o['encryptFunc'], $key, $key . $time . '|' . $str);
  135. if (strlen($name . $raw) > self::LENGTH_LIMIT) {
  136. $this->errors[] = 'Cookie is likely too large to store.';
  137. return false;
  138. }
  139. $res = setcookie($name, $raw, $this->_o['expire'], $this->_o['path'],
  140. $this->_o['domain'], $this->_o['secure'], $this->_o['httponly']);
  141. if ($res) {
  142. return true;
  143. } else {
  144. $this->errors[] = 'Setcookie() returned false. Headers may have been sent.';
  145. return false;
  146. }
  147. }
  148. /**
  149. * @return string null if cookie not set, false if tampering occured
  150. */
  151. public function fetch($name)
  152. {
  153. if (!isset($_COOKIE[$name])) {
  154. return null;
  155. }
  156. return ($this->_o['mode'] === self::MODE_ENCRYPT)
  157. ? $this->_fetchEncrypted($name)
  158. : $this->_fetch($name);
  159. }
  160. private function _fetch($name)
  161. {
  162. if (isset($this->_returns[self::MODE_VISIBLE][$name])) {
  163. return $this->_returns[self::MODE_VISIBLE][$name][0];
  164. }
  165. $cookie = get_magic_quotes_gpc()
  166. ? stripslashes($_COOKIE[$name])
  167. : $_COOKIE[$name];
  168. $parts = explode('|', $cookie, 3);
  169. if (3 !== count($parts)) {
  170. $this->errors[] = 'Cookie was tampered with.';
  171. return false;
  172. }
  173. list($sig, $time, $str) = $parts;
  174. $hashInput = $this->_o['secret'] . $name . $time . $str;
  175. if ($sig !== call_user_func($this->_o['hashFunc'], $hashInput)) {
  176. $this->errors[] = 'Cookie was tampered with.';
  177. return false;
  178. }
  179. $time = base_convert($time, 36, 10); // unpack time
  180. $this->_returns[self::MODE_VISIBLE][$name] = array($str, $time);
  181. return $str;
  182. }
  183. private function _fetchEncrypted($name)
  184. {
  185. if (isset($this->_returns[self::MODE_ENCRYPT][$name])) {
  186. return $this->_returns[self::MODE_ENCRYPT][$name][0];
  187. }
  188. if (! is_callable($this->_o['decryptFunc'])) {
  189. $this->errors[] = 'Decrypt function not callable';
  190. return false;
  191. }
  192. $cookie = get_magic_quotes_gpc()
  193. ? stripslashes($_COOKIE[$name])
  194. : $_COOKIE[$name];
  195. $key = self::hash($this->_o['secret']);
  196. $timeStr = call_user_func($this->_o['decryptFunc'], $key, $cookie);
  197. if (! $timeStr) {
  198. $this->errors[] = 'Cookie was tampered with.';
  199. return false;
  200. }
  201. $timeStr = rtrim($timeStr, "\x00");
  202. // verify decryption
  203. if (0 !== strpos($timeStr, $key)) {
  204. $this->errors[] = 'Cookie was tampered with.';
  205. return false;
  206. }
  207. $timeStr = substr($timeStr, strlen($key));
  208. list($time, $str) = explode('|', $timeStr, 2);
  209. $time = base_convert($time, 36, 10); // unpack time
  210. $this->_returns[self::MODE_ENCRYPT][$name] = array($str, $time);
  211. return $str;
  212. }
  213. public function getTimestamp($name)
  214. {
  215. if (is_string($this->fetch($name))) {
  216. return $this->_returns[$this->_o['mode']][$name][1];
  217. }
  218. return false;
  219. }
  220. public function delete($name)
  221. {
  222. setcookie($name, '', time() - 3600, $this->_o['path'], $this->_o['domain'], $this->_o['secure'], $this->_o['httponly']);
  223. }
  224. /**
  225. * @var array options
  226. */
  227. private $_o;
  228. private $_returns = array();
  229. }