PageRenderTime 55ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/ext/standard/session.c

http://github.com/infusion/PHP
C | 2307 lines | 1913 code | 229 blank | 165 comment | 247 complexity | 6e50ea360920561c38ba8906f90942e7 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-2.1, BSD-3-Clause

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. +----------------------------------------------------------------------+
  3. | PHP Version 5 |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 1997-2011 The PHP Group |
  6. +----------------------------------------------------------------------+
  7. | This source file is subject to version 3.01 of the PHP license, |
  8. | that is bundled with this package in the file LICENSE, and is |
  9. | available through the world-wide-web at the following url: |
  10. | http://www.php.net/license/3_01.txt |
  11. | If you did not receive a copy of the PHP license and are unable to |
  12. | obtain it through the world-wide-web, please send a note to |
  13. | license@php.net so we can mail you a copy immediately. |
  14. +----------------------------------------------------------------------+
  15. | Authors: Sascha Schumann <sascha@schumann.cx> |
  16. | Andrei Zmievski <andrei@php.net> |
  17. +----------------------------------------------------------------------+
  18. */
  19. /* $Id: session.c 307671 2011-01-23 10:02:06Z pajoye $ */
  20. #ifdef HAVE_CONFIG_H
  21. #include "config.h"
  22. #endif
  23. #include "php.h"
  24. #ifdef PHP_WIN32
  25. # include "win32/winutil.h"
  26. # include "win32/time.h"
  27. #else
  28. #include <sys/time.h>
  29. #endif
  30. #include <sys/stat.h>
  31. #include <fcntl.h>
  32. #include "php_ini.h"
  33. #include "SAPI.h"
  34. #include "php_session.h"
  35. #include "ext/standard/md5.h"
  36. #include "ext/standard/sha1.h"
  37. #include "ext/standard/php_var.h"
  38. #include "ext/date/php_date.h"
  39. #include "ext/standard/php_lcg.h"
  40. #include "ext/standard/url_scanner_ex.h"
  41. #include "ext/standard/php_rand.h" /* for RAND_MAX */
  42. #include "ext/standard/info.h"
  43. #include "ext/standard/php_smart_str.h"
  44. #include "ext/standard/url.h"
  45. #include "mod_files.h"
  46. #include "mod_user.h"
  47. #ifdef HAVE_LIBMM
  48. #include "mod_mm.h"
  49. #endif
  50. PHPAPI ZEND_DECLARE_MODULE_GLOBALS(ps);
  51. /* ***********
  52. * Helpers *
  53. *********** */
  54. #define IF_SESSION_VARS() \
  55. if (PS(http_session_vars) && PS(http_session_vars)->type == IS_ARRAY)
  56. #define SESSION_CHECK_ACTIVE_STATE \
  57. if (PS(session_status) == php_session_active) { \
  58. php_error_docref(NULL TSRMLS_CC, E_WARNING, "A session is active. You cannot change the session module's ini settings at this time"); \
  59. return FAILURE; \
  60. }
  61. /* Dispatched by RINIT and by php_session_destroy */
  62. static inline void php_rinit_session_globals(TSRMLS_D) /* {{{ */
  63. {
  64. PS(id) = NULL;
  65. PS(session_status) = php_session_none;
  66. PS(mod_data) = NULL;
  67. /* Do NOT init PS(mod_user_names) here! */
  68. PS(http_session_vars) = NULL;
  69. }
  70. /* }}} */
  71. /* Dispatched by RSHUTDOWN and by php_session_destroy */
  72. static inline void php_rshutdown_session_globals(TSRMLS_D) /* {{{ */
  73. {
  74. if (PS(http_session_vars)) {
  75. zval_ptr_dtor(&PS(http_session_vars));
  76. PS(http_session_vars) = NULL;
  77. }
  78. /* Do NOT destroy PS(mod_user_names) here! */
  79. if (PS(mod_data)) {
  80. zend_try {
  81. PS(mod)->s_close(&PS(mod_data) TSRMLS_CC);
  82. } zend_end_try();
  83. }
  84. if (PS(id)) {
  85. efree(PS(id));
  86. }
  87. }
  88. /* }}} */
  89. static int php_session_destroy(TSRMLS_D) /* {{{ */
  90. {
  91. int retval = SUCCESS;
  92. if (PS(session_status) != php_session_active) {
  93. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Trying to destroy uninitialized session");
  94. return FAILURE;
  95. }
  96. if (PS(mod)->s_destroy(&PS(mod_data), PS(id) TSRMLS_CC) == FAILURE) {
  97. retval = FAILURE;
  98. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Session object destruction failed");
  99. }
  100. php_rshutdown_session_globals(TSRMLS_C);
  101. php_rinit_session_globals(TSRMLS_C);
  102. return retval;
  103. }
  104. /* }}} */
  105. PHPAPI void php_add_session_var(char *name, size_t namelen TSRMLS_DC) /* {{{ */
  106. {
  107. zval **sym_track = NULL;
  108. IF_SESSION_VARS() {
  109. zend_hash_find(Z_ARRVAL_P(PS(http_session_vars)), name, namelen + 1, (void *) &sym_track);
  110. } else {
  111. return;
  112. }
  113. /* Set up a proper reference between $_SESSION["x"] and $x. */
  114. if (PG(register_globals)) {
  115. zval **sym_global = NULL;
  116. if (zend_hash_find(&EG(symbol_table), name, namelen + 1, (void *) &sym_global) == SUCCESS) {
  117. if ((Z_TYPE_PP(sym_global) == IS_ARRAY && Z_ARRVAL_PP(sym_global) == &EG(symbol_table)) || *sym_global == PS(http_session_vars)) {
  118. return;
  119. }
  120. }
  121. if (sym_global == NULL && sym_track == NULL) {
  122. zval *empty_var;
  123. ALLOC_INIT_ZVAL(empty_var); /* this sets refcount to 1 */
  124. Z_SET_REFCOUNT_P(empty_var, 0); /* our module does not maintain a ref */
  125. /* The next call will increase refcount by NR_OF_SYM_TABLES==2 */
  126. zend_set_hash_symbol(empty_var, name, namelen, 1, 2, Z_ARRVAL_P(PS(http_session_vars)), &EG(symbol_table));
  127. } else if (sym_global == NULL) {
  128. SEPARATE_ZVAL_IF_NOT_REF(sym_track);
  129. zend_set_hash_symbol(*sym_track, name, namelen, 1, 1, &EG(symbol_table));
  130. } else if (sym_track == NULL) {
  131. SEPARATE_ZVAL_IF_NOT_REF(sym_global);
  132. zend_set_hash_symbol(*sym_global, name, namelen, 1, 1, Z_ARRVAL_P(PS(http_session_vars)));
  133. }
  134. } else {
  135. if (sym_track == NULL) {
  136. zval *empty_var;
  137. ALLOC_INIT_ZVAL(empty_var);
  138. ZEND_SET_SYMBOL_WITH_LENGTH(Z_ARRVAL_P(PS(http_session_vars)), name, namelen+1, empty_var, 1, 0);
  139. }
  140. }
  141. }
  142. /* }}} */
  143. PHPAPI void php_set_session_var(char *name, size_t namelen, zval *state_val, php_unserialize_data_t *var_hash TSRMLS_DC) /* {{{ */
  144. {
  145. if (PG(register_globals)) {
  146. zval **old_symbol;
  147. if (zend_hash_find(&EG(symbol_table),name,namelen+1,(void *)&old_symbol) == SUCCESS) {
  148. if ((Z_TYPE_PP(old_symbol) == IS_ARRAY && Z_ARRVAL_PP(old_symbol) == &EG(symbol_table)) || *old_symbol == PS(http_session_vars)) {
  149. return;
  150. }
  151. /* A global symbol with the same name exists already. That
  152. * symbol might have been created by other means (e.g. $_GET).
  153. *
  154. * hash_update in zend_set_hash_symbol is not good, because
  155. * it will leave referenced variables (such as local instances
  156. * of a global variable) dangling.
  157. *
  158. * BTW: if you use register_globals references between
  159. * session-vars won't work because of this very reason! */
  160. REPLACE_ZVAL_VALUE(old_symbol,state_val,1);
  161. /* The following line will update the reference table used for
  162. * unserialization. It is optional, because some storage
  163. * formats may not be able to represent references. */
  164. if (var_hash) {
  165. PHP_VAR_UNSERIALIZE_ZVAL_CHANGED(var_hash,state_val,*old_symbol);
  166. }
  167. zend_set_hash_symbol(*old_symbol, name, namelen, 1, 1, Z_ARRVAL_P(PS(http_session_vars)));
  168. } else {
  169. zend_set_hash_symbol(state_val, name, namelen, 1, 2, Z_ARRVAL_P(PS(http_session_vars)), &EG(symbol_table));
  170. }
  171. } else IF_SESSION_VARS() {
  172. zend_set_hash_symbol(state_val, name, namelen, PZVAL_IS_REF(state_val), 1, Z_ARRVAL_P(PS(http_session_vars)));
  173. }
  174. }
  175. /* }}} */
  176. PHPAPI int php_get_session_var(char *name, size_t namelen, zval ***state_var TSRMLS_DC) /* {{{ */
  177. {
  178. int ret = FAILURE;
  179. IF_SESSION_VARS() {
  180. ret = zend_hash_find(Z_ARRVAL_P(PS(http_session_vars)), name, namelen + 1, (void **) state_var);
  181. /* If register_globals is enabled, and
  182. * if there is an entry for the slot in $_SESSION, and
  183. * if that entry is still set to NULL, and
  184. * if the global var exists, then
  185. * we prefer the same key in the global sym table. */
  186. if (PG(register_globals) && ret == SUCCESS && Z_TYPE_PP(*state_var) == IS_NULL) {
  187. zval **tmp;
  188. if (zend_hash_find(&EG(symbol_table), name, namelen + 1, (void **) &tmp) == SUCCESS) {
  189. *state_var = tmp;
  190. }
  191. }
  192. }
  193. return ret;
  194. }
  195. /* }}} */
  196. static void php_session_track_init(TSRMLS_D) /* {{{ */
  197. {
  198. zval *session_vars = NULL;
  199. /* Unconditionally destroy existing arrays -- possible dirty data */
  200. zend_delete_global_variable("_SESSION", sizeof("_SESSION")-1 TSRMLS_CC);
  201. if (PS(http_session_vars)) {
  202. zval_ptr_dtor(&PS(http_session_vars));
  203. }
  204. MAKE_STD_ZVAL(session_vars);
  205. array_init(session_vars);
  206. PS(http_session_vars) = session_vars;
  207. ZEND_SET_GLOBAL_VAR_WITH_LENGTH("_SESSION", sizeof("_SESSION"), PS(http_session_vars), 2, 1);
  208. }
  209. /* }}} */
  210. static char *php_session_encode(int *newlen TSRMLS_DC) /* {{{ */
  211. {
  212. char *ret = NULL;
  213. IF_SESSION_VARS() {
  214. if (!PS(serializer)) {
  215. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown session.serialize_handler. Failed to encode session object");
  216. ret = NULL;
  217. } else if (PS(serializer)->encode(&ret, newlen TSRMLS_CC) == FAILURE) {
  218. ret = NULL;
  219. }
  220. } else {
  221. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot encode non-existent session");
  222. }
  223. return ret;
  224. }
  225. /* }}} */
  226. static void php_session_decode(const char *val, int vallen TSRMLS_DC) /* {{{ */
  227. {
  228. if (!PS(serializer)) {
  229. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown session.serialize_handler. Failed to decode session object");
  230. return;
  231. }
  232. if (PS(serializer)->decode(val, vallen TSRMLS_CC) == FAILURE) {
  233. php_session_destroy(TSRMLS_C);
  234. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to decode session object. Session has been destroyed");
  235. }
  236. }
  237. /* }}} */
  238. /*
  239. * Note that we cannot use the BASE64 alphabet here, because
  240. * it contains "/" and "+": both are unacceptable for simple inclusion
  241. * into URLs.
  242. */
  243. static char hexconvtab[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,-";
  244. enum {
  245. PS_HASH_FUNC_MD5,
  246. PS_HASH_FUNC_SHA1,
  247. PS_HASH_FUNC_OTHER
  248. };
  249. /* returns a pointer to the byte after the last valid character in out */
  250. static char *bin_to_readable(char *in, size_t inlen, char *out, char nbits) /* {{{ */
  251. {
  252. unsigned char *p, *q;
  253. unsigned short w;
  254. int mask;
  255. int have;
  256. p = (unsigned char *) in;
  257. q = (unsigned char *)in + inlen;
  258. w = 0;
  259. have = 0;
  260. mask = (1 << nbits) - 1;
  261. while (1) {
  262. if (have < nbits) {
  263. if (p < q) {
  264. w |= *p++ << have;
  265. have += 8;
  266. } else {
  267. /* consumed everything? */
  268. if (have == 0) break;
  269. /* No? We need a final round */
  270. have = nbits;
  271. }
  272. }
  273. /* consume nbits */
  274. *out++ = hexconvtab[w & mask];
  275. w >>= nbits;
  276. have -= nbits;
  277. }
  278. *out = '\0';
  279. return out;
  280. }
  281. /* }}} */
  282. PHPAPI char *php_session_create_id(PS_CREATE_SID_ARGS) /* {{{ */
  283. {
  284. PHP_MD5_CTX md5_context;
  285. PHP_SHA1_CTX sha1_context;
  286. #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
  287. void *hash_context;
  288. #endif
  289. unsigned char *digest;
  290. int digest_len;
  291. int j;
  292. char *buf, *outid;
  293. struct timeval tv;
  294. zval **array;
  295. zval **token;
  296. char *remote_addr = NULL;
  297. gettimeofday(&tv, NULL);
  298. if (zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void **) &array) == SUCCESS &&
  299. Z_TYPE_PP(array) == IS_ARRAY &&
  300. zend_hash_find(Z_ARRVAL_PP(array), "REMOTE_ADDR", sizeof("REMOTE_ADDR"), (void **) &token) == SUCCESS
  301. ) {
  302. remote_addr = Z_STRVAL_PP(token);
  303. }
  304. /* maximum 15+19+19+10 bytes */
  305. spprintf(&buf, 0, "%.15s%ld%ld%0.8F", remote_addr ? remote_addr : "", tv.tv_sec, (long int)tv.tv_usec, php_combined_lcg(TSRMLS_C) * 10);
  306. switch (PS(hash_func)) {
  307. case PS_HASH_FUNC_MD5:
  308. PHP_MD5Init(&md5_context);
  309. PHP_MD5Update(&md5_context, (unsigned char *) buf, strlen(buf));
  310. digest_len = 16;
  311. break;
  312. case PS_HASH_FUNC_SHA1:
  313. PHP_SHA1Init(&sha1_context);
  314. PHP_SHA1Update(&sha1_context, (unsigned char *) buf, strlen(buf));
  315. digest_len = 20;
  316. break;
  317. #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
  318. case PS_HASH_FUNC_OTHER:
  319. if (!PS(hash_ops)) {
  320. php_error_docref(NULL TSRMLS_CC, E_ERROR, "Invalid session hash function");
  321. efree(buf);
  322. return NULL;
  323. }
  324. hash_context = emalloc(PS(hash_ops)->context_size);
  325. PS(hash_ops)->hash_init(hash_context);
  326. PS(hash_ops)->hash_update(hash_context, (unsigned char *) buf, strlen(buf));
  327. digest_len = PS(hash_ops)->digest_size;
  328. break;
  329. #endif /* HAVE_HASH_EXT */
  330. default:
  331. php_error_docref(NULL TSRMLS_CC, E_ERROR, "Invalid session hash function");
  332. efree(buf);
  333. return NULL;
  334. }
  335. efree(buf);
  336. if (PS(entropy_length) > 0) {
  337. #ifdef PHP_WIN32
  338. unsigned char rbuf[2048];
  339. size_t toread = PS(entropy_length);
  340. if (php_win32_get_random_bytes(rbuf, (size_t) toread) == SUCCESS){
  341. switch (PS(hash_func)) {
  342. case PS_HASH_FUNC_MD5:
  343. PHP_MD5Update(&md5_context, rbuf, toread);
  344. break;
  345. case PS_HASH_FUNC_SHA1:
  346. PHP_SHA1Update(&sha1_context, rbuf, toread);
  347. break;
  348. # if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
  349. case PS_HASH_FUNC_OTHER:
  350. PS(hash_ops)->hash_update(hash_context, rbuf, toread);
  351. break;
  352. # endif /* HAVE_HASH_EXT */
  353. }
  354. }
  355. #else
  356. int fd;
  357. fd = VCWD_OPEN(PS(entropy_file), O_RDONLY);
  358. if (fd >= 0) {
  359. unsigned char rbuf[2048];
  360. int n;
  361. int to_read = PS(entropy_length);
  362. while (to_read > 0) {
  363. n = read(fd, rbuf, MIN(to_read, sizeof(rbuf)));
  364. if (n <= 0) break;
  365. switch (PS(hash_func)) {
  366. case PS_HASH_FUNC_MD5:
  367. PHP_MD5Update(&md5_context, rbuf, n);
  368. break;
  369. case PS_HASH_FUNC_SHA1:
  370. PHP_SHA1Update(&sha1_context, rbuf, n);
  371. break;
  372. #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
  373. case PS_HASH_FUNC_OTHER:
  374. PS(hash_ops)->hash_update(hash_context, rbuf, n);
  375. break;
  376. #endif /* HAVE_HASH_EXT */
  377. }
  378. to_read -= n;
  379. }
  380. close(fd);
  381. }
  382. #endif
  383. }
  384. digest = emalloc(digest_len + 1);
  385. switch (PS(hash_func)) {
  386. case PS_HASH_FUNC_MD5:
  387. PHP_MD5Final(digest, &md5_context);
  388. break;
  389. case PS_HASH_FUNC_SHA1:
  390. PHP_SHA1Final(digest, &sha1_context);
  391. break;
  392. #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
  393. case PS_HASH_FUNC_OTHER:
  394. PS(hash_ops)->hash_final(digest, hash_context);
  395. efree(hash_context);
  396. break;
  397. #endif /* HAVE_HASH_EXT */
  398. }
  399. if (PS(hash_bits_per_character) < 4
  400. || PS(hash_bits_per_character) > 6) {
  401. PS(hash_bits_per_character) = 4;
  402. php_error_docref(NULL TSRMLS_CC, E_WARNING, "The ini setting hash_bits_per_character is out of range (should be 4, 5, or 6) - using 4 for now");
  403. }
  404. outid = emalloc((size_t)((digest_len + 2) * ((8.0f / PS(hash_bits_per_character)) + 0.5)));
  405. j = (int) (bin_to_readable((char *)digest, digest_len, outid, (char)PS(hash_bits_per_character)) - outid);
  406. efree(digest);
  407. if (newlen) {
  408. *newlen = j;
  409. }
  410. return outid;
  411. }
  412. /* }}} */
  413. static void php_session_initialize(TSRMLS_D) /* {{{ */
  414. {
  415. char *val;
  416. int vallen;
  417. /* check session name for invalid characters */
  418. if (PS(id) && strpbrk(PS(id), "\r\n\t <>'\"\\")) {
  419. efree(PS(id));
  420. PS(id) = NULL;
  421. }
  422. if (!PS(mod)) {
  423. php_error_docref(NULL TSRMLS_CC, E_ERROR, "No storage module chosen - failed to initialize session");
  424. return;
  425. }
  426. /* Open session handler first */
  427. if (PS(mod)->s_open(&PS(mod_data), PS(save_path), PS(session_name) TSRMLS_CC) == FAILURE) {
  428. php_error_docref(NULL TSRMLS_CC, E_ERROR, "Failed to initialize storage module: %s (path: %s)", PS(mod)->s_name, PS(save_path));
  429. return;
  430. }
  431. /* If there is no ID, use session module to create one */
  432. if (!PS(id)) {
  433. new_session:
  434. PS(id) = PS(mod)->s_create_sid(&PS(mod_data), NULL TSRMLS_CC);
  435. if (PS(use_cookies)) {
  436. PS(send_cookie) = 1;
  437. }
  438. }
  439. /* Read data */
  440. /* Question: if you create a SID here, should you also try to read data?
  441. * I'm not sure, but while not doing so will remove one session operation
  442. * it could prove usefull for those sites which wish to have "default"
  443. * session information. */
  444. php_session_track_init(TSRMLS_C);
  445. PS(invalid_session_id) = 0;
  446. if (PS(mod)->s_read(&PS(mod_data), PS(id), &val, &vallen TSRMLS_CC) == SUCCESS) {
  447. php_session_decode(val, vallen TSRMLS_CC);
  448. efree(val);
  449. } else if (PS(invalid_session_id)) { /* address instances where the session read fails due to an invalid id */
  450. PS(invalid_session_id) = 0;
  451. efree(PS(id));
  452. PS(id) = NULL;
  453. goto new_session;
  454. }
  455. }
  456. /* }}} */
  457. static int migrate_global(HashTable *ht, HashPosition *pos TSRMLS_DC) /* {{{ */
  458. {
  459. char *str;
  460. uint str_len;
  461. ulong num_key;
  462. int n;
  463. zval **val;
  464. int ret = 0;
  465. n = zend_hash_get_current_key_ex(ht, &str, &str_len, &num_key, 0, pos);
  466. switch (n) {
  467. case HASH_KEY_IS_STRING:
  468. if (zend_hash_find(&EG(symbol_table), str, str_len, (void **) &val) == SUCCESS &&
  469. val && Z_TYPE_PP(val) != IS_NULL
  470. ) {
  471. ZEND_SET_SYMBOL_WITH_LENGTH(ht, str, str_len, *val, Z_REFCOUNT_PP(val) + 1, 1);
  472. ret = 1;
  473. }
  474. break;
  475. case HASH_KEY_IS_LONG:
  476. php_error_docref(NULL TSRMLS_CC, E_NOTICE, "The session bug compatibility code will not "
  477. "try to locate the global variable $%lu due to its "
  478. "numeric nature", num_key);
  479. break;
  480. }
  481. return ret;
  482. }
  483. /* }}} */
  484. static void php_session_save_current_state(TSRMLS_D) /* {{{ */
  485. {
  486. int ret = FAILURE;
  487. IF_SESSION_VARS() {
  488. if (PS(bug_compat) && !PG(register_globals)) {
  489. HashTable *ht = Z_ARRVAL_P(PS(http_session_vars));
  490. HashPosition pos;
  491. zval **val;
  492. int do_warn = 0;
  493. zend_hash_internal_pointer_reset_ex(ht, &pos);
  494. while (zend_hash_get_current_data_ex(ht, (void **) &val, &pos) != FAILURE) {
  495. if (Z_TYPE_PP(val) == IS_NULL) {
  496. if (migrate_global(ht, &pos TSRMLS_CC)) {
  497. do_warn = 1;
  498. }
  499. }
  500. zend_hash_move_forward_ex(ht, &pos);
  501. }
  502. if (do_warn && PS(bug_compat_warn)) {
  503. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Your script possibly relies on a session side-effect which existed until PHP 4.2.3. Please be advised that the session extension does not consider global variables as a source of data, unless register_globals is enabled. You can disable this functionality and this warning by setting session.bug_compat_42 or session.bug_compat_warn to off, respectively");
  504. }
  505. }
  506. if (PS(mod_data)) {
  507. char *val;
  508. int vallen;
  509. val = php_session_encode(&vallen TSRMLS_CC);
  510. if (val) {
  511. ret = PS(mod)->s_write(&PS(mod_data), PS(id), val, vallen TSRMLS_CC);
  512. efree(val);
  513. } else {
  514. ret = PS(mod)->s_write(&PS(mod_data), PS(id), "", 0 TSRMLS_CC);
  515. }
  516. }
  517. if (ret == FAILURE) {
  518. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to write session data (%s). Please "
  519. "verify that the current setting of session.save_path "
  520. "is correct (%s)",
  521. PS(mod)->s_name,
  522. PS(save_path));
  523. }
  524. }
  525. if (PS(mod_data)) {
  526. PS(mod)->s_close(&PS(mod_data) TSRMLS_CC);
  527. }
  528. }
  529. /* }}} */
  530. /* *************************
  531. * INI Settings/Handlers *
  532. ************************* */
  533. static PHP_INI_MH(OnUpdateSaveHandler) /* {{{ */
  534. {
  535. ps_module *tmp;
  536. SESSION_CHECK_ACTIVE_STATE;
  537. tmp = _php_find_ps_module(new_value TSRMLS_CC);
  538. if (PG(modules_activated) && !tmp) {
  539. int err_type;
  540. if (stage == ZEND_INI_STAGE_RUNTIME) {
  541. err_type = E_WARNING;
  542. } else {
  543. err_type = E_ERROR;
  544. }
  545. /* Do not output error when restoring ini options. */
  546. if (stage != ZEND_INI_STAGE_DEACTIVATE) {
  547. php_error_docref(NULL TSRMLS_CC, err_type, "Cannot find save handler '%s'", new_value);
  548. }
  549. return FAILURE;
  550. }
  551. PS(mod) = tmp;
  552. return SUCCESS;
  553. }
  554. /* }}} */
  555. static PHP_INI_MH(OnUpdateSerializer) /* {{{ */
  556. {
  557. const ps_serializer *tmp;
  558. SESSION_CHECK_ACTIVE_STATE;
  559. tmp = _php_find_ps_serializer(new_value TSRMLS_CC);
  560. if (PG(modules_activated) && !tmp) {
  561. int err_type;
  562. if (stage == ZEND_INI_STAGE_RUNTIME) {
  563. err_type = E_WARNING;
  564. } else {
  565. err_type = E_ERROR;
  566. }
  567. /* Do not output error when restoring ini options. */
  568. if (stage != ZEND_INI_STAGE_DEACTIVATE) {
  569. php_error_docref(NULL TSRMLS_CC, err_type, "Cannot find serialization handler '%s'", new_value);
  570. }
  571. return FAILURE;
  572. }
  573. PS(serializer) = tmp;
  574. return SUCCESS;
  575. }
  576. /* }}} */
  577. static PHP_INI_MH(OnUpdateTransSid) /* {{{ */
  578. {
  579. SESSION_CHECK_ACTIVE_STATE;
  580. if (!strncasecmp(new_value, "on", sizeof("on"))) {
  581. PS(use_trans_sid) = (zend_bool) 1;
  582. } else {
  583. PS(use_trans_sid) = (zend_bool) atoi(new_value);
  584. }
  585. return SUCCESS;
  586. }
  587. /* }}} */
  588. static PHP_INI_MH(OnUpdateSaveDir) /* {{{ */
  589. {
  590. /* Only do the safemode/open_basedir check at runtime */
  591. if (stage == PHP_INI_STAGE_RUNTIME || stage == PHP_INI_STAGE_HTACCESS) {
  592. char *p;
  593. if (memchr(new_value, '\0', new_value_length) != NULL) {
  594. return FAILURE;
  595. }
  596. /* we do not use zend_memrchr() since path can contain ; itself */
  597. if ((p = strchr(new_value, ';'))) {
  598. char *p2;
  599. p++;
  600. if ((p2 = strchr(p, ';'))) {
  601. p = p2 + 1;
  602. }
  603. } else {
  604. p = new_value;
  605. }
  606. if (PG(open_basedir) && *p && php_check_open_basedir(p TSRMLS_CC)) {
  607. return FAILURE;
  608. }
  609. }
  610. OnUpdateString(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
  611. return SUCCESS;
  612. }
  613. /* }}} */
  614. static PHP_INI_MH(OnUpdateHashFunc) /* {{{ */
  615. {
  616. long val;
  617. char *endptr = NULL;
  618. #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
  619. PS(hash_ops) = NULL;
  620. #endif
  621. val = strtol(new_value, &endptr, 10);
  622. if (endptr && (*endptr == '\0')) {
  623. /* Numeric value */
  624. PS(hash_func) = val ? 1 : 0;
  625. return SUCCESS;
  626. }
  627. if (new_value_length == (sizeof("md5") - 1) &&
  628. strncasecmp(new_value, "md5", sizeof("md5") - 1) == 0) {
  629. PS(hash_func) = PS_HASH_FUNC_MD5;
  630. return SUCCESS;
  631. }
  632. if (new_value_length == (sizeof("sha1") - 1) &&
  633. strncasecmp(new_value, "sha1", sizeof("sha1") - 1) == 0) {
  634. PS(hash_func) = PS_HASH_FUNC_SHA1;
  635. return SUCCESS;
  636. }
  637. #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH) /* {{{ */
  638. {
  639. php_hash_ops *ops = (php_hash_ops*)php_hash_fetch_ops(new_value, new_value_length);
  640. if (ops) {
  641. PS(hash_func) = PS_HASH_FUNC_OTHER;
  642. PS(hash_ops) = ops;
  643. return SUCCESS;
  644. }
  645. }
  646. #endif /* HAVE_HASH_EXT }}} */
  647. return FAILURE;
  648. }
  649. /* }}} */
  650. /* {{{ PHP_INI
  651. */
  652. PHP_INI_BEGIN()
  653. STD_PHP_INI_BOOLEAN("session.bug_compat_42", "1", PHP_INI_ALL, OnUpdateBool, bug_compat, php_ps_globals, ps_globals)
  654. STD_PHP_INI_BOOLEAN("session.bug_compat_warn", "1", PHP_INI_ALL, OnUpdateBool, bug_compat_warn, php_ps_globals, ps_globals)
  655. STD_PHP_INI_ENTRY("session.save_path", "", PHP_INI_ALL, OnUpdateSaveDir,save_path, php_ps_globals, ps_globals)
  656. STD_PHP_INI_ENTRY("session.name", "PHPSESSID", PHP_INI_ALL, OnUpdateString, session_name, php_ps_globals, ps_globals)
  657. PHP_INI_ENTRY("session.save_handler", "files", PHP_INI_ALL, OnUpdateSaveHandler)
  658. STD_PHP_INI_BOOLEAN("session.auto_start", "0", PHP_INI_ALL, OnUpdateBool, auto_start, php_ps_globals, ps_globals)
  659. STD_PHP_INI_ENTRY("session.gc_probability", "1", PHP_INI_ALL, OnUpdateLong, gc_probability, php_ps_globals, ps_globals)
  660. STD_PHP_INI_ENTRY("session.gc_divisor", "100", PHP_INI_ALL, OnUpdateLong, gc_divisor, php_ps_globals, ps_globals)
  661. STD_PHP_INI_ENTRY("session.gc_maxlifetime", "1440", PHP_INI_ALL, OnUpdateLong, gc_maxlifetime, php_ps_globals, ps_globals)
  662. PHP_INI_ENTRY("session.serialize_handler", "php", PHP_INI_ALL, OnUpdateSerializer)
  663. STD_PHP_INI_ENTRY("session.cookie_lifetime", "0", PHP_INI_ALL, OnUpdateLong, cookie_lifetime, php_ps_globals, ps_globals)
  664. STD_PHP_INI_ENTRY("session.cookie_path", "/", PHP_INI_ALL, OnUpdateString, cookie_path, php_ps_globals, ps_globals)
  665. STD_PHP_INI_ENTRY("session.cookie_domain", "", PHP_INI_ALL, OnUpdateString, cookie_domain, php_ps_globals, ps_globals)
  666. STD_PHP_INI_BOOLEAN("session.cookie_secure", "", PHP_INI_ALL, OnUpdateBool, cookie_secure, php_ps_globals, ps_globals)
  667. STD_PHP_INI_BOOLEAN("session.cookie_httponly", "", PHP_INI_ALL, OnUpdateBool, cookie_httponly, php_ps_globals, ps_globals)
  668. STD_PHP_INI_BOOLEAN("session.use_cookies", "1", PHP_INI_ALL, OnUpdateBool, use_cookies, php_ps_globals, ps_globals)
  669. STD_PHP_INI_BOOLEAN("session.use_only_cookies", "1", PHP_INI_ALL, OnUpdateBool, use_only_cookies, php_ps_globals, ps_globals)
  670. STD_PHP_INI_ENTRY("session.referer_check", "", PHP_INI_ALL, OnUpdateString, extern_referer_chk, php_ps_globals, ps_globals)
  671. STD_PHP_INI_ENTRY("session.entropy_file", "", PHP_INI_ALL, OnUpdateString, entropy_file, php_ps_globals, ps_globals)
  672. STD_PHP_INI_ENTRY("session.entropy_length", "0", PHP_INI_ALL, OnUpdateLong, entropy_length, php_ps_globals, ps_globals)
  673. STD_PHP_INI_ENTRY("session.cache_limiter", "nocache", PHP_INI_ALL, OnUpdateString, cache_limiter, php_ps_globals, ps_globals)
  674. STD_PHP_INI_ENTRY("session.cache_expire", "180", PHP_INI_ALL, OnUpdateLong, cache_expire, php_ps_globals, ps_globals)
  675. PHP_INI_ENTRY("session.use_trans_sid", "0", PHP_INI_ALL, OnUpdateTransSid)
  676. PHP_INI_ENTRY("session.hash_function", "0", PHP_INI_ALL, OnUpdateHashFunc)
  677. STD_PHP_INI_ENTRY("session.hash_bits_per_character", "4", PHP_INI_ALL, OnUpdateLong, hash_bits_per_character, php_ps_globals, ps_globals)
  678. /* Commented out until future discussion */
  679. /* PHP_INI_ENTRY("session.encode_sources", "globals,track", PHP_INI_ALL, NULL) */
  680. PHP_INI_END()
  681. /* }}} */
  682. /* ***************
  683. * Serializers *
  684. *************** */
  685. #define PS_BIN_NR_OF_BITS 8
  686. #define PS_BIN_UNDEF (1<<(PS_BIN_NR_OF_BITS-1))
  687. #define PS_BIN_MAX (PS_BIN_UNDEF-1)
  688. PS_SERIALIZER_ENCODE_FUNC(php_binary) /* {{{ */
  689. {
  690. smart_str buf = {0};
  691. php_serialize_data_t var_hash;
  692. PS_ENCODE_VARS;
  693. PHP_VAR_SERIALIZE_INIT(var_hash);
  694. PS_ENCODE_LOOP(
  695. if (key_length > PS_BIN_MAX) continue;
  696. smart_str_appendc(&buf, (unsigned char) key_length);
  697. smart_str_appendl(&buf, key, key_length);
  698. php_var_serialize(&buf, struc, &var_hash TSRMLS_CC);
  699. } else {
  700. if (key_length > PS_BIN_MAX) continue;
  701. smart_str_appendc(&buf, (unsigned char) (key_length & PS_BIN_UNDEF));
  702. smart_str_appendl(&buf, key, key_length);
  703. );
  704. if (newlen) {
  705. *newlen = buf.len;
  706. }
  707. smart_str_0(&buf);
  708. *newstr = buf.c;
  709. PHP_VAR_SERIALIZE_DESTROY(var_hash);
  710. return SUCCESS;
  711. }
  712. /* }}} */
  713. PS_SERIALIZER_DECODE_FUNC(php_binary) /* {{{ */
  714. {
  715. const char *p;
  716. char *name;
  717. const char *endptr = val + vallen;
  718. zval *current;
  719. int namelen;
  720. int has_value;
  721. php_unserialize_data_t var_hash;
  722. PHP_VAR_UNSERIALIZE_INIT(var_hash);
  723. for (p = val; p < endptr; ) {
  724. zval **tmp;
  725. namelen = ((unsigned char)(*p)) & (~PS_BIN_UNDEF);
  726. if (namelen < 0 || namelen > PS_BIN_MAX || (p + namelen) >= endptr) {
  727. return FAILURE;
  728. }
  729. has_value = *p & PS_BIN_UNDEF ? 0 : 1;
  730. name = estrndup(p + 1, namelen);
  731. p += namelen + 1;
  732. if (zend_hash_find(&EG(symbol_table), name, namelen + 1, (void **) &tmp) == SUCCESS) {
  733. if ((Z_TYPE_PP(tmp) == IS_ARRAY && Z_ARRVAL_PP(tmp) == &EG(symbol_table)) || *tmp == PS(http_session_vars)) {
  734. efree(name);
  735. continue;
  736. }
  737. }
  738. if (has_value) {
  739. ALLOC_INIT_ZVAL(current);
  740. if (php_var_unserialize(&current, (const unsigned char **) &p, (const unsigned char *) endptr, &var_hash TSRMLS_CC)) {
  741. php_set_session_var(name, namelen, current, &var_hash TSRMLS_CC);
  742. }
  743. zval_ptr_dtor(&current);
  744. }
  745. PS_ADD_VARL(name, namelen);
  746. efree(name);
  747. }
  748. PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
  749. return SUCCESS;
  750. }
  751. /* }}} */
  752. #define PS_DELIMITER '|'
  753. #define PS_UNDEF_MARKER '!'
  754. PS_SERIALIZER_ENCODE_FUNC(php) /* {{{ */
  755. {
  756. smart_str buf = {0};
  757. php_serialize_data_t var_hash;
  758. PS_ENCODE_VARS;
  759. PHP_VAR_SERIALIZE_INIT(var_hash);
  760. PS_ENCODE_LOOP(
  761. smart_str_appendl(&buf, key, key_length);
  762. if (memchr(key, PS_DELIMITER, key_length) || memchr(key, PS_UNDEF_MARKER, key_length)) {
  763. PHP_VAR_SERIALIZE_DESTROY(var_hash);
  764. smart_str_free(&buf);
  765. return FAILURE;
  766. }
  767. smart_str_appendc(&buf, PS_DELIMITER);
  768. php_var_serialize(&buf, struc, &var_hash TSRMLS_CC);
  769. } else {
  770. smart_str_appendc(&buf, PS_UNDEF_MARKER);
  771. smart_str_appendl(&buf, key, key_length);
  772. smart_str_appendc(&buf, PS_DELIMITER);
  773. );
  774. if (newlen) {
  775. *newlen = buf.len;
  776. }
  777. smart_str_0(&buf);
  778. *newstr = buf.c;
  779. PHP_VAR_SERIALIZE_DESTROY(var_hash);
  780. return SUCCESS;
  781. }
  782. /* }}} */
  783. PS_SERIALIZER_DECODE_FUNC(php) /* {{{ */
  784. {
  785. const char *p, *q;
  786. char *name;
  787. const char *endptr = val + vallen;
  788. zval *current;
  789. int namelen;
  790. int has_value;
  791. php_unserialize_data_t var_hash;
  792. PHP_VAR_UNSERIALIZE_INIT(var_hash);
  793. p = val;
  794. while (p < endptr) {
  795. zval **tmp;
  796. q = p;
  797. while (*q != PS_DELIMITER) {
  798. if (++q >= endptr) goto break_outer_loop;
  799. }
  800. if (p[0] == PS_UNDEF_MARKER) {
  801. p++;
  802. has_value = 0;
  803. } else {
  804. has_value = 1;
  805. }
  806. namelen = q - p;
  807. name = estrndup(p, namelen);
  808. q++;
  809. if (zend_hash_find(&EG(symbol_table), name, namelen + 1, (void **) &tmp) == SUCCESS) {
  810. if ((Z_TYPE_PP(tmp) == IS_ARRAY && Z_ARRVAL_PP(tmp) == &EG(symbol_table)) || *tmp == PS(http_session_vars)) {
  811. goto skip;
  812. }
  813. }
  814. if (has_value) {
  815. ALLOC_INIT_ZVAL(current);
  816. if (php_var_unserialize(&current, (const unsigned char **) &q, (const unsigned char *) endptr, &var_hash TSRMLS_CC)) {
  817. php_set_session_var(name, namelen, current, &var_hash TSRMLS_CC);
  818. }
  819. zval_ptr_dtor(&current);
  820. }
  821. PS_ADD_VARL(name, namelen);
  822. skip:
  823. efree(name);
  824. p = q;
  825. }
  826. break_outer_loop:
  827. PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
  828. return SUCCESS;
  829. }
  830. /* }}} */
  831. #define MAX_SERIALIZERS 10
  832. #define PREDEFINED_SERIALIZERS 2
  833. static ps_serializer ps_serializers[MAX_SERIALIZERS + 1] = {
  834. PS_SERIALIZER_ENTRY(php),
  835. PS_SERIALIZER_ENTRY(php_binary)
  836. };
  837. PHPAPI int php_session_register_serializer(const char *name, int (*encode)(PS_SERIALIZER_ENCODE_ARGS), int (*decode)(PS_SERIALIZER_DECODE_ARGS)) /* {{{ */
  838. {
  839. int ret = -1;
  840. int i;
  841. for (i = 0; i < MAX_SERIALIZERS; i++) {
  842. if (ps_serializers[i].name == NULL) {
  843. ps_serializers[i].name = name;
  844. ps_serializers[i].encode = encode;
  845. ps_serializers[i].decode = decode;
  846. ps_serializers[i + 1].name = NULL;
  847. ret = 0;
  848. break;
  849. }
  850. }
  851. return ret;
  852. }
  853. /* }}} */
  854. /* *******************
  855. * Storage Modules *
  856. ******************* */
  857. #define MAX_MODULES 10
  858. #define PREDEFINED_MODULES 2
  859. static ps_module *ps_modules[MAX_MODULES + 1] = {
  860. ps_files_ptr,
  861. ps_user_ptr
  862. };
  863. PHPAPI int php_session_register_module(ps_module *ptr) /* {{{ */
  864. {
  865. int ret = -1;
  866. int i;
  867. for (i = 0; i < MAX_MODULES; i++) {
  868. if (!ps_modules[i]) {
  869. ps_modules[i] = ptr;
  870. ret = 0;
  871. break;
  872. }
  873. }
  874. return ret;
  875. }
  876. /* }}} */
  877. /* ******************
  878. * Cache Limiters *
  879. ****************** */
  880. typedef struct {
  881. char *name;
  882. void (*func)(TSRMLS_D);
  883. } php_session_cache_limiter_t;
  884. #define CACHE_LIMITER(name) _php_cache_limiter_##name
  885. #define CACHE_LIMITER_FUNC(name) static void CACHE_LIMITER(name)(TSRMLS_D)
  886. #define CACHE_LIMITER_ENTRY(name) { #name, CACHE_LIMITER(name) },
  887. #define ADD_HEADER(a) sapi_add_header(a, strlen(a), 1);
  888. #define MAX_STR 512
  889. static char *month_names[] = {
  890. "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  891. "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  892. };
  893. static char *week_days[] = {
  894. "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
  895. };
  896. static inline void strcpy_gmt(char *ubuf, time_t *when) /* {{{ */
  897. {
  898. char buf[MAX_STR];
  899. struct tm tm, *res;
  900. int n;
  901. res = php_gmtime_r(when, &tm);
  902. if (!res) {
  903. buf[0] = '\0';
  904. return;
  905. }
  906. n = slprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT", /* SAFE */
  907. week_days[tm.tm_wday], tm.tm_mday,
  908. month_names[tm.tm_mon], tm.tm_year + 1900,
  909. tm.tm_hour, tm.tm_min,
  910. tm.tm_sec);
  911. memcpy(ubuf, buf, n);
  912. ubuf[n] = '\0';
  913. }
  914. /* }}} */
  915. static inline void last_modified(TSRMLS_D) /* {{{ */
  916. {
  917. const char *path;
  918. struct stat sb;
  919. char buf[MAX_STR + 1];
  920. path = SG(request_info).path_translated;
  921. if (path) {
  922. if (VCWD_STAT(path, &sb) == -1) {
  923. return;
  924. }
  925. #define LAST_MODIFIED "Last-Modified: "
  926. memcpy(buf, LAST_MODIFIED, sizeof(LAST_MODIFIED) - 1);
  927. strcpy_gmt(buf + sizeof(LAST_MODIFIED) - 1, &sb.st_mtime);
  928. ADD_HEADER(buf);
  929. }
  930. }
  931. /* }}} */
  932. #define EXPIRES "Expires: "
  933. CACHE_LIMITER_FUNC(public) /* {{{ */
  934. {
  935. char buf[MAX_STR + 1];
  936. struct timeval tv;
  937. time_t now;
  938. gettimeofday(&tv, NULL);
  939. now = tv.tv_sec + PS(cache_expire) * 60;
  940. memcpy(buf, EXPIRES, sizeof(EXPIRES) - 1);
  941. strcpy_gmt(buf + sizeof(EXPIRES) - 1, &now);
  942. ADD_HEADER(buf);
  943. snprintf(buf, sizeof(buf) , "Cache-Control: public, max-age=%ld", PS(cache_expire) * 60); /* SAFE */
  944. ADD_HEADER(buf);
  945. last_modified(TSRMLS_C);
  946. }
  947. /* }}} */
  948. CACHE_LIMITER_FUNC(private_no_expire) /* {{{ */
  949. {
  950. char buf[MAX_STR + 1];
  951. snprintf(buf, sizeof(buf), "Cache-Control: private, max-age=%ld, pre-check=%ld", PS(cache_expire) * 60, PS(cache_expire) * 60); /* SAFE */
  952. ADD_HEADER(buf);
  953. last_modified(TSRMLS_C);
  954. }
  955. /* }}} */
  956. CACHE_LIMITER_FUNC(private) /* {{{ */
  957. {
  958. ADD_HEADER("Expires: Sat, 23 Jan 1988 14:56:00 GMT");
  959. CACHE_LIMITER(private_no_expire)(TSRMLS_C);
  960. }
  961. /* }}} */
  962. CACHE_LIMITER_FUNC(nocache) /* {{{ */
  963. {
  964. ADD_HEADER("Expires: Sat, 23 Jan 1988 14:56:00 GMT");
  965. /* For HTTP/1.1 conforming clients and the rest (MSIE 5) */
  966. ADD_HEADER("Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
  967. /* For HTTP/1.0 conforming clients */
  968. ADD_HEADER("Pragma: no-cache");
  969. }
  970. /* }}} */
  971. static php_session_cache_limiter_t php_session_cache_limiters[] = {
  972. CACHE_LIMITER_ENTRY(public)
  973. CACHE_LIMITER_ENTRY(private)
  974. CACHE_LIMITER_ENTRY(private_no_expire)
  975. CACHE_LIMITER_ENTRY(nocache)
  976. {0}
  977. };
  978. static int php_session_cache_limiter(TSRMLS_D) /* {{{ */
  979. {
  980. php_session_cache_limiter_t *lim;
  981. if (PS(cache_limiter)[0] == '\0') return 0;
  982. if (SG(headers_sent)) {
  983. char *output_start_filename = php_get_output_start_filename(TSRMLS_C);
  984. int output_start_lineno = php_get_output_start_lineno(TSRMLS_C);
  985. if (output_start_filename) {
  986. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot send session cache limiter - headers already sent (output started at %s:%d)", output_start_filename, output_start_lineno);
  987. } else {
  988. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot send session cache limiter - headers already sent");
  989. }
  990. return -2;
  991. }
  992. for (lim = php_session_cache_limiters; lim->name; lim++) {
  993. if (!strcasecmp(lim->name, PS(cache_limiter))) {
  994. lim->func(TSRMLS_C);
  995. return 0;
  996. }
  997. }
  998. return -1;
  999. }
  1000. /* }}} */
  1001. /* *********************
  1002. * Cookie Management *
  1003. ********************* */
  1004. #define COOKIE_SET_COOKIE "Set-Cookie: "
  1005. #define COOKIE_EXPIRES "; expires="
  1006. #define COOKIE_PATH "; path="
  1007. #define COOKIE_DOMAIN "; domain="
  1008. #define COOKIE_SECURE "; secure"
  1009. #define COOKIE_HTTPONLY "; HttpOnly"
  1010. static void php_session_send_cookie(TSRMLS_D) /* {{{ */
  1011. {
  1012. smart_str ncookie = {0};
  1013. char *date_fmt = NULL;
  1014. char *e_session_name, *e_id;
  1015. if (SG(headers_sent)) {
  1016. char *output_start_filename = php_get_output_start_filename(TSRMLS_C);
  1017. int output_start_lineno = php_get_output_start_lineno(TSRMLS_C);
  1018. if (output_start_filename) {
  1019. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot send session cookie - headers already sent by (output started at %s:%d)", output_start_filename, output_start_lineno);
  1020. } else {
  1021. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot send session cookie - headers already sent");
  1022. }
  1023. return;
  1024. }
  1025. /* URL encode session_name and id because they might be user supplied */
  1026. e_session_name = php_url_encode(PS(session_name), strlen(PS(session_name)), NULL);
  1027. e_id = php_url_encode(PS(id), strlen(PS(id)), NULL);
  1028. smart_str_appends(&ncookie, COOKIE_SET_COOKIE);
  1029. smart_str_appends(&ncookie, e_session_name);
  1030. smart_str_appendc(&ncookie, '=');
  1031. smart_str_appends(&ncookie, e_id);
  1032. efree(e_session_name);
  1033. efree(e_id);
  1034. if (PS(cookie_lifetime) > 0) {
  1035. struct timeval tv;
  1036. time_t t;
  1037. gettimeofday(&tv, NULL);
  1038. t = tv.tv_sec + PS(cookie_lifetime);
  1039. if (t > 0) {
  1040. date_fmt = php_format_date("D, d-M-Y H:i:s T", sizeof("D, d-M-Y H:i:s T")-1, t, 0 TSRMLS_CC);
  1041. smart_str_appends(&ncookie, COOKIE_EXPIRES);
  1042. smart_str_appends(&ncookie, date_fmt);
  1043. efree(date_fmt);
  1044. }
  1045. }
  1046. if (PS(cookie_path)[0]) {
  1047. smart_str_appends(&ncookie, COOKIE_PATH);
  1048. smart_str_appends(&ncookie, PS(cookie_path));
  1049. }
  1050. if (PS(cookie_domain)[0]) {
  1051. smart_str_appends(&ncookie, COOKIE_DOMAIN);
  1052. smart_str_appends(&ncookie, PS(cookie_domain));
  1053. }
  1054. if (PS(cookie_secure)) {
  1055. smart_str_appends(&ncookie, COOKIE_SECURE);
  1056. }
  1057. if (PS(cookie_httponly)) {
  1058. smart_str_appends(&ncookie, COOKIE_HTTPONLY);
  1059. }
  1060. smart_str_0(&ncookie);
  1061. /* 'replace' must be 0 here, else a previous Set-Cookie
  1062. header, probably sent with setcookie() will be replaced! */
  1063. sapi_add_header_ex(ncookie.c, ncookie.len, 0, 0 TSRMLS_CC);
  1064. }
  1065. /* }}} */
  1066. PHPAPI ps_module *_php_find_ps_module(char *name TSRMLS_DC) /* {{{ */
  1067. {
  1068. ps_module *ret = NULL;
  1069. ps_module **mod;
  1070. int i;
  1071. for (i = 0, mod = ps_modules; i < MAX_MODULES; i++, mod++) {
  1072. if (*mod && !strcasecmp(name, (*mod)->s_name)) {
  1073. ret = *mod;
  1074. break;
  1075. }
  1076. }
  1077. return ret;
  1078. }
  1079. /* }}} */
  1080. PHPAPI const ps_serializer *_php_find_ps_serializer(char *name TSRMLS_DC) /* {{{ */
  1081. {
  1082. const ps_serializer *ret = NULL;
  1083. const ps_serializer *mod;
  1084. for (mod = ps_serializers; mod->name; mod++) {
  1085. if (!strcasecmp(name, mod->name)) {
  1086. ret = mod;
  1087. break;
  1088. }
  1089. }
  1090. return ret;
  1091. }
  1092. /* }}} */
  1093. #define PPID2SID \
  1094. convert_to_string((*ppid)); \
  1095. PS(id) = estrndup(Z_STRVAL_PP(ppid), Z_STRLEN_PP(ppid))
  1096. static void php_session_reset_id(TSRMLS_D) /* {{{ */
  1097. {
  1098. int module_number = PS(module_number);
  1099. if (PS(use_cookies) && PS(send_cookie)) {
  1100. php_session_send_cookie(TSRMLS_C);
  1101. PS(send_cookie) = 0;
  1102. }
  1103. /* if the SID constant exists, destroy it. */
  1104. zend_hash_del(EG(zend_constants), "sid", sizeof("sid"));
  1105. if (PS(define_sid)) {
  1106. smart_str var = {0};
  1107. smart_str_appends(&var, PS(session_name));
  1108. smart_str_appendc(&var, '=');
  1109. smart_str_appends(&var, PS(id));
  1110. smart_str_0(&var);
  1111. REGISTER_STRINGL_CONSTANT("SID", var.c, var.len, 0);
  1112. } else {
  1113. REGISTER_STRINGL_CONSTANT("SID", STR_EMPTY_ALLOC(), 0, 0);
  1114. }
  1115. if (PS(apply_trans_sid)) {
  1116. php_url_scanner_reset_vars(TSRMLS_C);
  1117. php_url_scanner_add_var(PS(session_name), strlen(PS(session_name)), PS(id), strlen(PS(id)), 1 TSRMLS_CC);
  1118. }
  1119. }
  1120. /* }}} */
  1121. PHPAPI void php_session_start(TSRMLS_D) /* {{{ */
  1122. {
  1123. zval **ppid;
  1124. zval **data;
  1125. char *p, *value;
  1126. int nrand;
  1127. int lensess;
  1128. if (PS(use_only_cookies)) {
  1129. PS(apply_trans_sid) = 0;
  1130. } else {
  1131. PS(apply_trans_sid) = PS(use_trans_sid);
  1132. }
  1133. switch (PS(session_status)) {
  1134. case php_session_active:
  1135. php_error(E_NOTICE, "A session had already been started - ignoring session_start()");
  1136. return;
  1137. break;
  1138. case php_session_disabled:
  1139. value = zend_ini_string("session.save_handler", sizeof("session.save_handler"), 0);
  1140. if (!PS(mod) && value) {
  1141. PS(mod) = _php_find_ps_module(value TSRMLS_CC);
  1142. if (!PS(mod)) {
  1143. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot find save handler '%s' - session startup failed", value);
  1144. return;
  1145. }
  1146. }
  1147. value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler"), 0);
  1148. if (!PS(serializer) && value) {
  1149. PS(serializer) = _php_find_ps_serializer(value TSRMLS_CC);
  1150. if (!PS(serializer)) {
  1151. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot find serialization handler '%s' - session startup failed", value);
  1152. return;
  1153. }
  1154. }
  1155. PS(session_status) = php_session_none;
  1156. /* fallthrough */
  1157. default:
  1158. case php_session_none:
  1159. PS(define_sid) = 1;
  1160. PS(send_cookie) = 1;
  1161. }
  1162. lensess = strlen(PS(session_name));
  1163. /* Cookies are preferred, because initially
  1164. * cookie and get variables will be available. */
  1165. if (!PS(id)) {
  1166. if (PS(use_cookies) && zend_hash_find(&EG(symbol_table), "_COOKIE", sizeof("_COOKIE"), (void **) &data) == SUCCESS &&
  1167. Z_TYPE_PP(data) == IS_ARRAY &&
  1168. zend_hash_find(Z_ARRVAL_PP(data), PS(session_name), lensess + 1, (void **) &ppid) == SUCCESS
  1169. ) {
  1170. PPID2SID;
  1171. PS(apply_trans_sid) = 0;
  1172. PS(send_cookie) = 0;
  1173. PS(define_sid) = 0;
  1174. }
  1175. if (!PS(use_only_cookies) && !PS(id) &&
  1176. zend_hash_find(&EG(symbol_table), "_GET", sizeof("_GET"), (void **) &data) == SUCCESS &&
  1177. Z_TYPE_PP(data) == IS_ARRAY &&
  1178. zend_hash_find(Z_ARRVAL_PP(data), PS(session_name), lensess + 1, (void **) &ppid) == SUCCESS
  1179. ) {
  1180. PPID2SID;
  1181. PS(send_cookie) = 0;
  1182. }
  1183. if (!PS(use_only_cookies) && !PS(id) &&
  1184. zend_hash_find(&EG(symbol_table), "_POST", sizeof("_POST"), (void **) &data) == SUCCESS &&
  1185. Z_TYPE_PP(data) == IS_ARRAY &&
  1186. zend_hash_find(Z_ARRVAL_PP(data), PS(session_name), lensess + 1, (void **) &ppid) == SUCCESS
  1187. ) {
  1188. PPID2SID;
  1189. PS(send_cookie) = 0;
  1190. }
  1191. }
  1192. /* Check the REQUEST_URI symbol for a string of the form
  1193. * '<session-name>=<session-id>' to allow URLs of the form
  1194. * http://yoursite/<session-name>=<session-id>/script.php */
  1195. if (!PS(use_only_cookies) && !PS(id) && PG(http_globals)[TRACK_VARS_SERVER] &&
  1196. zend_hash_find(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_SERVER]), "REQUEST_URI", sizeof("REQUEST_URI"), (void **) &data) == SUCCESS &&
  1197. Z_TYPE_PP(data) == IS_STRING &&
  1198. (p = strstr(Z_STRVAL_PP(data), PS(session_name))) &&
  1199. p[lensess] == '='
  1200. ) {
  1201. char *q;
  1202. p += lensess + 1;
  1203. if ((q = strpbrk(p, "/?\\"))) {
  1204. PS(id) = estrndup(p, q - p);
  1205. PS(send_cookie) = 0;
  1206. }
  1207. }
  1208. /* Check whether the current request was referred to by
  1209. * an external site which invalidates the previously found id. */
  1210. if (PS(id) &&
  1211. PS(extern_referer_chk)[0] != '\0' &&
  1212. PG(http_globals)[TRACK_VARS_SERVER] &&
  1213. zend_hash_find(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_SERVER]), "HTTP_REFERER", sizeof("HTTP_REFERER"), (void **) &data) == SUCCESS &&
  1214. Z_TYPE_PP(data) == IS_STRING &&
  1215. Z_STRLEN_PP(data) != 0 &&
  1216. strstr(Z_STRVAL_PP(data), PS(extern_referer_chk)) == NULL
  1217. ) {
  1218. efree(PS(id));
  1219. PS(id) = NULL;
  1220. PS(send_cookie) = 1;
  1221. if (PS(use_trans_sid) && !PS(use_only_cookies)) {
  1222. PS(apply_trans_sid) = 1;
  1223. }
  1224. }
  1225. php_session_initialize(TSRMLS_C);
  1226. if (!PS(use_cookies) && PS(send_cookie)) {
  1227. if (PS(use_trans_sid) && !PS(use_only_cookies)) {
  1228. PS(apply_trans_sid) = 1;
  1229. }
  1230. PS(send_cookie) = 0;
  1231. }
  1232. php_session_reset_id(TSRMLS_C);
  1233. PS(session_status) = php_session_active;
  1234. php_session_cache_limiter(TSRMLS_C);
  1235. if (PS(mod_data) && PS(gc_probability) > 0) {
  1236. int nrdels = -1;
  1237. nrand = (int) ((float) PS(gc_divisor) * php_combined_lcg(TSRMLS_C));
  1238. if (nrand < PS(gc_probability)) {
  1239. PS(mod)->s_gc(&PS(mod_data), PS(gc_maxlifetime), &nrdels TSRMLS_CC);
  1240. #ifdef SESSION_DEBUG
  1241. if (nrdels != -1) {
  1242. php_error_docref(NULL TSRMLS_CC, E_NOTICE, "purged %d expired session objects", nrdels);
  1243. }
  1244. #endif
  1245. }
  1246. }
  1247. }
  1248. /* }}} */
  1249. static void php_session_flush(TSRMLS_D) /* {{{ */
  1250. {
  1251. if (PS(session_status) == php_session_active) {
  1252. PS(session_status) = php_session_none;
  1253. zend_try {
  1254. php_session_save_current_state(TSRMLS_C);
  1255. } zend_end_try();
  1256. }
  1257. }
  1258. /* }}} */
  1259. PHPAPI void session_adapt_url(const char *url, size_t urllen, char **new, size_t *newlen TSRMLS_DC) /* {{{ */
  1260. {
  1261. if (PS(apply_trans_sid) && (PS(session_status) == php_session_active)) {
  1262. *new = php_url_scanner_adapt_single_url(url, urllen, PS(session_name), PS(id), newlen TSRMLS_CC);
  1263. }
  1264. }
  1265. /* }}} */
  1266. /* ********************************
  1267. * Userspace exported functions *
  1268. ******************************** */
  1269. /* {{{ proto void session_set_cookie_params(int lifetime [, string path [, string domain [, bool secure[, bool httponly]]]])
  1270. Set session cookie parameters */
  1271. static PHP_FUNCTION(session_set_cookie_params)
  1272. {
  1273. zval **lifetime = NULL;
  1274. char *path = NULL, *domain = NULL;
  1275. int path_len, domain_len, argc = ZEND_NUM_ARGS();
  1276. zend_bool secure = 0, httponly = 0;
  1277. if (!PS(use_cookies) ||
  1278. zend_parse_parameters(argc TSRMLS_CC, "Z|ssbb", &lifetime, &path, &path_len, &domain, &domain_len, &secure, &httponly) == FAILURE) {
  1279. return;
  1280. }
  1281. convert_to_string_ex(lifetime);
  1282. zend_alter_ini_entry("session.cookie_lifetime", sizeof("session.cookie_lifetime"), Z_STRVAL_PP(lifetime), Z_STRLEN_PP(lifetime), PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
  1283. if (path) {
  1284. zend_alter_ini_entry("session.cookie_path", sizeof("session.cookie_path"), path, path_len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
  1285. }
  1286. if (domain) {
  1287. zend_alter_ini_entry("session.cookie_domain", sizeof("session.cookie_domain"), domain, domain_len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
  1288. }
  1289. if (argc > 3) {
  1290. zend_alter_ini_entry("session.cookie_secure", sizeof("session.cookie_secure"), secure ? "1" : "0", 1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
  1291. }
  1292. if (argc > 4) {
  1293. zend_alter_ini_entry("session.cookie_httponly", sizeof("session.cookie_httponly"), httponly ? "1" : "0", 1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
  1294. }
  1295. }
  1296. /* }}} */
  1297. /* {{{ proto array session_get_cookie_params(void)
  1298. Return the session cookie parameters */
  1299. static PHP_FUNCTION(session_get_cookie_params)
  1300. {
  1301. if (zend_parse_parameters_none() == FAILURE) {
  1302. return;
  1303. }
  1304. array_init(return_value);
  1305. add_assoc_long(return_value, "lifetime", PS(cookie_lifetime));
  1306. add_assoc_string(return_value, "path", PS(cookie_path), 1);
  1307. add_assoc_string(return_value, "domain", PS(cookie_domain), 1);
  1308. add_assoc_bool(return_value, "secure", PS(cookie_secure));
  1309. add_assoc_bool(return_value, "httponly", PS(cookie_httponly));
  1310. }
  1311. /* }}} */
  1312. /* {{{ proto string session_name([string newname])
  1313. Return the current session name. If newname is given, the session name is replaced with newname */
  1314. static PHP_FUNCTION(session_name)
  1315. {
  1316. char *name = NULL;
  1317. int name_len;
  1318. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &name, &name_len) == FAILURE) {
  1319. return;
  1320. }
  1321. RETVAL_STRING(PS(session_name), 1);
  1322. if (name) {
  1323. zend_alter_ini_entry("session.name", sizeof("session.name"), name, name_len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
  1324. }
  1325. }
  1326. /* }}} */
  1327. /* {{{ proto string session_module_name([string newname])
  1328. Return the current module name used for accessing session data. If newname is given, the module name is replaced with newname */
  1329. static PHP_FUNCTION(session_module_name)
  1330. {
  1331. char *name = NULL;
  1332. int name_len;
  1333. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &name, &name_len) == FAILURE) {
  1334. return;
  1335. }
  1336. /* Set return_value to current module name */
  1337. if (PS(mod) && PS(mod)->s_name) {
  1338. RETVAL_STRING(safe_estrdup(PS(mod)->s_name), 0);
  1339. } else {
  1340. RETVAL_EMPTY_STRING();
  1341. }
  1342. if (name) {
  1343. if (!_php_find_ps_module(name TSRMLS_CC)) {
  1344. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot find named PHP session module (%s)", name);
  1345. zval_dtor(return_value);
  1346. RETURN_FALSE;
  1347. }
  1348. if (PS(mod_data)) {
  1349. PS(mod)->s_close(&PS(mod_data) TSRMLS_CC);
  1350. }
  1351. PS(mod_data) = NULL;
  1352. zend_alter_ini_entry("session.save_handler", sizeof("session.save_handler"), name, name_len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
  1353. }
  1354. }
  1355. /* }}} */
  1356. /* {{{ proto void session_set_save_handler(string open, string close, string read, string write, string destroy, string gc)
  1357. Sets user-level functions */
  1358. static PHP_FUNCTION(session_set_save_handler)
  1359. {
  1360. zval ***args = NULL;
  1361. int i, num_args, argc = ZEND_NUM_ARGS();
  1362. char *name;
  1363. if (PS(session_status) != php_session_none) {
  1364. RETURN_FALSE;
  1365. }
  1366. if (argc != 6) {
  1367. WRONG_PARAM_COUNT;
  1368. }
  1369. if (zend_parse_parameters(argc TSRMLS_CC, "+", &args, &num_args) == FAILURE) {
  1370. return;
  1371. }
  1372. for (i = 0; i < 6; i++) {
  1373. if (!zend_is_callable(*args[i], 0, &name TSRMLS_CC)) {
  1374. efree(args);
  1375. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Argument %d is not a valid callback", i+1);
  1376. efree(name);
  1377. RETURN_FALSE;
  1378. }
  1379. efree(name);
  1380. }
  1381. zend_alter_ini_entry("session.save_handler", sizeof("session.save_handler"), "user", sizeof("user")-1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
  1382. for (i = 0; i < 6; i++) {
  1383. if (PS(mod_user_names).names[i] != NULL) {
  1384. zval_ptr_dtor(&PS(mod_user_names).names[i]);
  1385. }
  1386. Z_ADDREF_PP(args[i]);
  1387. PS(mod_user_names).names[i] = *args[i];
  1388. }
  1389. efree(args);
  1390. RETURN_TRUE;
  1391. }
  1392. /* }}} */
  1393. /* {{{ proto string session_save_path([string newname])
  1394. Return the current save path passed to module_name. If newname is given, the save path is replaced with newname */
  1395. static PHP_FUNCTION(session_save_path)
  1396. {
  1397. char *name = NULL;
  1398. int name_len;
  1399. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &name, &name_len) == FAILURE) {
  1400. return;
  1401. }
  1402. RETVAL_STRING(PS(save_path), 1);
  1403. if (name) {
  1404. if (memchr(name, '\0', name_len) != NULL) {
  1405. php_error_docref(NULL TSRMLS_CC, E_WARNING, "The save_path cannot contain NULL characters");
  1406. zval_dtor(return_value);
  1407. RETURN_FALSE;
  1408. }
  1409. zend_alter_ini_entry("session.save_path", sizeof("session.save_path"), name, name_len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
  1410. }
  1411. }
  1412. /* }}} */
  1413. /* {{{ proto string session_id([string newid])
  1414. Return the current session id. If newid is given, the session id is replaced with newid */
  1415. static PHP_FUNCTION(session_id)
  1416. {
  1417. char *name = NULL;
  1418. int name_len;
  1419. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &name, &name_len) == FAILURE) {
  1420. return;
  1421. }
  1422. if (PS(id)) {
  1423. RETVAL_STRING(PS(id), 1);
  1424. } else {
  1425. RETVAL_EMPTY_STRING();
  1426. }
  1427. if (name) {
  1428. if (PS(id)) {
  1429. efree(PS(id));
  1430. }
  1431. PS(id) = estrndup(name, name_len);
  1432. }
  1433. }
  1434. /* }}} */
  1435. /* {{{ proto bool session_regenerate_id([bool delete_old_session])
  1436. Update the current session id with a newly generated one. If delete_old_session is set to true, remove the old session. */
  1437. static PHP_FUNCTION(session_regenerate_id)
  1438. {
  1439. zend_bool del_ses = 0;
  1440. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &del_ses) == FAILURE) {
  1441. return;
  1442. }
  1443. if (SG(headers_sent)) {
  1444. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot regenerate session id - headers already sent");
  1445. RETURN_FALSE;
  1446. }
  1447. if (PS(session_status) == php_session_active) {
  1448. if (PS(id)) {
  1449. if (del_ses && PS(mod)->s_destroy(&PS(mod_data), PS(id) TSRMLS_CC) == FAILURE) {
  1450. php_error_docref(NULL TSRMLS_CC, E_WARNING, "Session object destruction failed");
  1451. RETURN_FALSE;
  1452. }
  1453. efree(PS(id));
  1454. PS(id) = NULL;
  1455. }
  1456. PS(id) = PS(mod)->s_create_sid(&PS(mod_data), NULL TSRMLS_CC);
  1457. PS(send_cookie) = 1;
  1458. php_session_reset_id(TSRMLS_C);
  1459. RETURN_TRUE;
  1460. }
  1461. RETURN_FALSE;
  1462. }
  1463. /* }}} */
  1464. /* {{{ proto string session_cache_limiter([string new_cache_limiter])
  1465. Return the current cache limiter. If new_cache_limited is given, the current cache_limiter is replaced with new_cache_limiter */
  1466. static PHP_FUNCTION(session_cache_limiter)
  1467. {
  1468. char *limiter = NULL;
  1469. int limiter_len;
  1470. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &limiter, &limiter_len) == FAILURE) {
  1471. return;
  1472. }
  1473. RETVAL_STRING(PS(cache_limiter), 1);
  1474. if (limiter) {
  1475. zend_alter_ini_entry("session.cache_limiter", sizeof("session.cache_limiter"), limiter, limiter_len, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
  1476. }
  1477. }
  1478. /* }}} */
  1479. /* {{{ proto int session_cacheā€¦

Large files files are truncated, but you can click here to view the full file