root/ext/session/session.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. php_rinit_session_globals
  2. php_rshutdown_session_globals
  3. php_session_destroy
  4. php_add_session_var
  5. php_set_session_var
  6. php_get_session_var
  7. php_session_track_init
  8. php_session_encode
  9. php_session_decode
  10. bin_to_readable
  11. php_session_create_id
  12. php_session_valid_key
  13. php_session_gc
  14. php_session_initialize
  15. php_session_save_current_state
  16. PHP_INI_MH
  17. PHP_INI_MH
  18. PHP_INI_MH
  19. PHP_INI_MH
  20. PHP_INI_MH
  21. PHP_INI_MH
  22. PHP_INI_MH
  23. PHP_INI_BEGIN
  24. PS_SERIALIZER_DECODE_FUNC
  25. PS_SERIALIZER_ENCODE_FUNC
  26. PS_SERIALIZER_DECODE_FUNC
  27. PS_SERIALIZER_ENCODE_FUNC
  28. PS_SERIALIZER_DECODE_FUNC
  29. php_session_register_serializer
  30. php_session_register_module
  31. php_session_validate_sid
  32. php_session_update_timestamp
  33. strcpy_gmt
  34. last_modified
  35. CACHE_LIMITER_FUNC
  36. CACHE_LIMITER_FUNC
  37. CACHE_LIMITER_FUNC
  38. CACHE_LIMITER_FUNC
  39. php_session_cache_limiter
  40. php_session_remove_cookie
  41. php_session_send_cookie
  42. _php_find_ps_module
  43. _php_find_ps_serializer
  44. ppid2sid
  45. php_session_reset_id
  46. php_session_start
  47. php_session_flush
  48. php_session_abort
  49. php_session_reset
  50. session_adapt_url
  51. PHP_FUNCTION
  52. PHP_FUNCTION
  53. PHP_FUNCTION
  54. PHP_FUNCTION
  55. PHP_FUNCTION
  56. PHP_FUNCTION
  57. PHP_FUNCTION
  58. PHP_FUNCTION
  59. PHP_FUNCTION
  60. PHP_FUNCTION
  61. PHP_FUNCTION
  62. PHP_FUNCTION
  63. PHP_FUNCTION
  64. php_session_start_set_ini
  65. PHP_FUNCTION
  66. PHP_FUNCTION
  67. PHP_FUNCTION
  68. PHP_FUNCTION
  69. PHP_FUNCTION
  70. PHP_FUNCTION
  71. PHP_FUNCTION
  72. PHP_FUNCTION
  73. php_rinit_session
  74. PHP_RINIT_FUNCTION
  75. PHP_RSHUTDOWN_FUNCTION
  76. PHP_GINIT_FUNCTION
  77. PHP_MINIT_FUNCTION
  78. PHP_MSHUTDOWN_FUNCTION
  79. PHP_MINFO_FUNCTION
  80. early_find_sid_in
  81. php_session_rfc1867_early_find_sid
  82. php_check_cancel_upload
  83. php_session_rfc1867_update
  84. php_session_rfc1867_cleanup
  85. php_session_rfc1867_callback

   1 /*
   2    +----------------------------------------------------------------------+
   3    | PHP Version 7                                                        |
   4    +----------------------------------------------------------------------+
   5    | Copyright (c) 1997-2016 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 
  20 /* $Id$ */
  21 
  22 #ifdef HAVE_CONFIG_H
  23 #include "config.h"
  24 #endif
  25 
  26 #include "php.h"
  27 
  28 #ifdef PHP_WIN32
  29 # include "win32/winutil.h"
  30 # include "win32/time.h"
  31 #else
  32 # include <sys/time.h>
  33 #endif
  34 
  35 #include <sys/stat.h>
  36 #include <fcntl.h>
  37 
  38 #include "php_ini.h"
  39 #include "SAPI.h"
  40 #include "rfc1867.h"
  41 #include "php_variables.h"
  42 #include "php_session.h"
  43 #include "ext/standard/md5.h"
  44 #include "ext/standard/sha1.h"
  45 #include "ext/standard/php_var.h"
  46 #include "ext/date/php_date.h"
  47 #include "ext/standard/php_lcg.h"
  48 #include "ext/standard/url_scanner_ex.h"
  49 #include "ext/standard/php_rand.h" /* for RAND_MAX */
  50 #include "ext/standard/info.h"
  51 #include "zend_smart_str.h"
  52 #include "ext/standard/url.h"
  53 #include "ext/standard/basic_functions.h"
  54 #include "ext/standard/head.h"
  55 
  56 #include "mod_files.h"
  57 #include "mod_user.h"
  58 
  59 #ifdef HAVE_LIBMM
  60 #include "mod_mm.h"
  61 #endif
  62 
  63 PHPAPI ZEND_DECLARE_MODULE_GLOBALS(ps)
  64 
  65 static int php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra);
  66 static int (*php_session_rfc1867_orig_callback)(unsigned int event, void *event_data, void **extra);
  67 static void php_session_track_init(void);
  68 
  69 /* SessionHandler class */
  70 zend_class_entry *php_session_class_entry;
  71 
  72 /* SessionHandlerInterface */
  73 zend_class_entry *php_session_iface_entry;
  74 
  75 /* SessionIdInterface */
  76 zend_class_entry *php_session_id_iface_entry;
  77 
  78 /* SessionUpdateTimestampHandler class */
  79 zend_class_entry *php_session_update_timestamp_class_entry;
  80 
  81 /* SessionUpdateTimestampInterface */
  82 zend_class_entry *php_session_update_timestamp_iface_entry;
  83 
  84 /* ***********
  85    * Helpers *
  86    *********** */
  87 
  88 #define IF_SESSION_VARS() \
  89         if (Z_ISREF_P(&PS(http_session_vars)) && Z_TYPE_P(Z_REFVAL(PS(http_session_vars))) == IS_ARRAY)
  90 
  91 #define SESSION_CHECK_ACTIVE_STATE      \
  92         if (PS(session_status) == php_session_active) { \
  93                 php_error_docref(NULL, E_WARNING, "A session is active. You cannot change the session module's ini settings at this time");     \
  94                 return FAILURE; \
  95         }
  96 
  97 #define APPLY_TRANS_SID (PS(use_trans_sid) && !PS(use_only_cookies))
  98 
  99 static void php_session_send_cookie(void);
 100 
 101 /* Dispatched by RINIT and by php_session_destroy */
 102 static inline void php_rinit_session_globals(void) /* {{{ */
 103 {
 104         /* Do NOT init PS(mod_user_names) here! */
 105         PS(id) = NULL;
 106         PS(session_status) = php_session_none;
 107         PS(mod_data) = NULL;
 108         PS(mod_user_is_open) = 0;
 109         PS(define_sid) = 1;
 110         PS(session_vars) = NULL;
 111         ZVAL_UNDEF(&PS(http_session_vars));
 112 }
 113 /* }}} */
 114 
 115 /* Dispatched by RSHUTDOWN and by php_session_destroy */
 116 static inline void php_rshutdown_session_globals(void) /* {{{ */
 117 {
 118         /* Do NOT destroy PS(mod_user_names) here! */
 119         if (!Z_ISUNDEF(PS(http_session_vars))) {
 120                 zval_ptr_dtor(&PS(http_session_vars));
 121                 ZVAL_UNDEF(&PS(http_session_vars));
 122         }
 123         if (PS(mod_data) || PS(mod_user_implemented)) {
 124                 zend_try {
 125                         PS(mod)->s_close(&PS(mod_data));
 126                 } zend_end_try();
 127         }
 128         if (PS(id)) {
 129                 zend_string_release(PS(id));
 130                 PS(id) = NULL;
 131         }
 132         if (PS(session_vars)) {
 133                 zend_string_release(PS(session_vars));
 134                 PS(session_vars) = NULL;
 135         }
 136 }
 137 /* }}} */
 138 
 139 static int php_session_destroy(void) /* {{{ */
 140 {
 141         int retval = SUCCESS;
 142 
 143         if (PS(session_status) != php_session_active) {
 144                 php_error_docref(NULL, E_WARNING, "Trying to destroy uninitialized session");
 145                 return FAILURE;
 146         }
 147 
 148         if (PS(id) && PS(mod)->s_destroy(&PS(mod_data), PS(id)) == FAILURE) {
 149                 retval = FAILURE;
 150                 php_error_docref(NULL, E_WARNING, "Session object destruction failed");
 151         }
 152 
 153         php_rshutdown_session_globals();
 154         php_rinit_session_globals();
 155 
 156         return retval;
 157 }
 158 /* }}} */
 159 
 160 PHPAPI void php_add_session_var(zend_string *name) /* {{{ */
 161 {
 162         zval *sym_track = NULL;
 163 
 164         IF_SESSION_VARS() {
 165                 sym_track = zend_hash_find(Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars))), name);
 166         } else {
 167                 return;
 168         }
 169 
 170         if (sym_track == NULL) {
 171                 zval empty_var;
 172 
 173                 ZVAL_NULL(&empty_var);
 174                 zend_hash_update(Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars))), name, &empty_var);
 175         }
 176 }
 177 /* }}} */
 178 
 179 PHPAPI zval* php_set_session_var(zend_string *name, zval *state_val, php_unserialize_data_t *var_hash) /* {{{ */
 180 {
 181         IF_SESSION_VARS() {
 182                 return zend_hash_update(Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars))), name, state_val);
 183         }
 184         return NULL;
 185 }
 186 /* }}} */
 187 
 188 PHPAPI zval* php_get_session_var(zend_string *name) /* {{{ */
 189 {
 190         IF_SESSION_VARS() {
 191                 return zend_hash_find(Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars))), name);
 192         }
 193         return NULL;
 194 }
 195 /* }}} */
 196 
 197 static void php_session_track_init(void) /* {{{ */
 198 {
 199         zval session_vars;
 200         zend_string *var_name = zend_string_init("_SESSION", sizeof("_SESSION") - 1, 0);
 201         /* Unconditionally destroy existing array -- possible dirty data */
 202         zend_delete_global_variable(var_name);
 203 
 204         if (!Z_ISUNDEF(PS(http_session_vars))) {
 205                 zval_ptr_dtor(&PS(http_session_vars));
 206         }
 207 
 208         array_init(&session_vars);
 209         ZVAL_NEW_REF(&PS(http_session_vars), &session_vars);
 210         Z_ADDREF_P(&PS(http_session_vars));
 211         zend_hash_update_ind(&EG(symbol_table), var_name, &PS(http_session_vars));
 212         zend_string_release(var_name);
 213 }
 214 /* }}} */
 215 
 216 static zend_string *php_session_encode(void) /* {{{ */
 217 {
 218         IF_SESSION_VARS() {
 219                 if (!PS(serializer)) {
 220                         php_error_docref(NULL, E_WARNING, "Unknown session.serialize_handler. Failed to encode session object");
 221                         return NULL;
 222                 }
 223                 return PS(serializer)->encode();
 224         } else {
 225                 php_error_docref(NULL, E_WARNING, "Cannot encode non-existent session");
 226         }
 227         return NULL;
 228 }
 229 /* }}} */
 230 
 231 static int php_session_decode(zend_string *data) /* {{{ */
 232 {
 233         if (!PS(serializer)) {
 234                 php_error_docref(NULL, E_WARNING, "Unknown session.serialize_handler. Failed to decode session object");
 235                 return FAILURE;
 236         }
 237         if (PS(serializer)->decode(ZSTR_VAL(data), ZSTR_LEN(data)) == FAILURE) {
 238                 php_session_destroy();
 239                 php_session_track_init();
 240                 php_error_docref(NULL, E_WARNING, "Failed to decode session object. Session has been destroyed");
 241                 return FAILURE;
 242         }
 243         return SUCCESS;
 244 }
 245 /* }}} */
 246 
 247 /*
 248  * Note that we cannot use the BASE64 alphabet here, because
 249  * it contains "/" and "+": both are unacceptable for simple inclusion
 250  * into URLs.
 251  */
 252 
 253 static char hexconvtab[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,-";
 254 
 255 enum {
 256         PS_HASH_FUNC_MD5,
 257         PS_HASH_FUNC_SHA1,
 258         PS_HASH_FUNC_OTHER
 259 };
 260 
 261 /* returns a pointer to the byte after the last valid character in out */
 262 static char *bin_to_readable(char *in, size_t inlen, char *out, char nbits) /* {{{ */
 263 {
 264         unsigned char *p, *q;
 265         unsigned short w;
 266         int mask;
 267         int have;
 268 
 269         p = (unsigned char *)in;
 270         q = (unsigned char *)in + inlen;
 271 
 272         w = 0;
 273         have = 0;
 274         mask = (1 << nbits) - 1;
 275 
 276         while (1) {
 277                 if (have < nbits) {
 278                         if (p < q) {
 279                                 w |= *p++ << have;
 280                                 have += 8;
 281                         } else {
 282                                 /* consumed everything? */
 283                                 if (have == 0) break;
 284                                 /* No? We need a final round */
 285                                 have = nbits;
 286                         }
 287                 }
 288 
 289                 /* consume nbits */
 290                 *out++ = hexconvtab[w & mask];
 291                 w >>= nbits;
 292                 have -= nbits;
 293         }
 294 
 295         *out = '\0';
 296         return out;
 297 }
 298 /* }}} */
 299 
 300 PHPAPI zend_string *php_session_create_id(PS_CREATE_SID_ARGS) /* {{{ */
 301 {
 302         PHP_MD5_CTX md5_context;
 303         PHP_SHA1_CTX sha1_context;
 304 #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
 305         void *hash_context = NULL;
 306 #endif
 307         unsigned char *digest;
 308         size_t digest_len;
 309         char *buf;
 310         struct timeval tv;
 311         zval *array;
 312         zval *token;
 313         zend_string *outid;
 314         char *remote_addr = NULL;
 315 
 316         gettimeofday(&tv, NULL);
 317 
 318         if ((array = zend_hash_str_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER") - 1)) &&
 319                 Z_TYPE_P(array) == IS_ARRAY &&
 320                 (token = zend_hash_str_find(Z_ARRVAL_P(array), "REMOTE_ADDR", sizeof("REMOTE_ADDR") - 1)) &&
 321                 Z_TYPE_P(token) == IS_STRING
 322         ) {
 323                 remote_addr = Z_STRVAL_P(token);
 324         }
 325 
 326         /* maximum 15+19+19+10 bytes */
 327         spprintf(&buf, 0, "%.15s%ld" ZEND_LONG_FMT "%0.8F", remote_addr ? remote_addr : "", tv.tv_sec, (zend_long)tv.tv_usec, php_combined_lcg() * 10);
 328 
 329         switch (PS(hash_func)) {
 330                 case PS_HASH_FUNC_MD5:
 331                         PHP_MD5Init(&md5_context);
 332                         PHP_MD5Update(&md5_context, (unsigned char *) buf, strlen(buf));
 333                         digest_len = 16;
 334                         break;
 335                 case PS_HASH_FUNC_SHA1:
 336                         PHP_SHA1Init(&sha1_context);
 337                         PHP_SHA1Update(&sha1_context, (unsigned char *) buf, strlen(buf));
 338                         digest_len = 20;
 339                         break;
 340 #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
 341                 case PS_HASH_FUNC_OTHER:
 342                         if (!PS(hash_ops)) {
 343                                 efree(buf);
 344                                 php_error_docref(NULL, E_ERROR, "Invalid session hash function");
 345                                 return NULL;
 346                         }
 347 
 348                         hash_context = emalloc(PS(hash_ops)->context_size);
 349                         PS(hash_ops)->hash_init(hash_context);
 350                         PS(hash_ops)->hash_update(hash_context, (unsigned char *) buf, strlen(buf));
 351                         digest_len = PS(hash_ops)->digest_size;
 352                         break;
 353 #endif /* HAVE_HASH_EXT */
 354                 default:
 355                         efree(buf);
 356                         php_error_docref(NULL, E_ERROR, "Invalid session hash function");
 357                         return NULL;
 358         }
 359         efree(buf);
 360 
 361         if (PS(entropy_length) > 0) {
 362 #ifdef PHP_WIN32
 363                 unsigned char rbuf[2048];
 364                 size_t toread = PS(entropy_length);
 365 
 366                 if (php_win32_get_random_bytes(rbuf, MIN(toread, sizeof(rbuf))) == SUCCESS){
 367 
 368                         switch (PS(hash_func)) {
 369                                 case PS_HASH_FUNC_MD5:
 370                                         PHP_MD5Update(&md5_context, rbuf, toread);
 371                                         break;
 372                                 case PS_HASH_FUNC_SHA1:
 373                                         PHP_SHA1Update(&sha1_context, rbuf, toread);
 374                                         break;
 375 # if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
 376                                 case PS_HASH_FUNC_OTHER:
 377                                         PS(hash_ops)->hash_update(hash_context, rbuf, toread);
 378                                         break;
 379 # endif /* HAVE_HASH_EXT */
 380                         }
 381                 }
 382 #else
 383                 int fd;
 384 
 385                 fd = VCWD_OPEN(PS(entropy_file), O_RDONLY);
 386                 if (fd >= 0) {
 387                         unsigned char rbuf[2048];
 388                         int n;
 389                         int to_read = PS(entropy_length);
 390 
 391                         while (to_read > 0) {
 392                                 n = read(fd, rbuf, MIN(to_read, sizeof(rbuf)));
 393                                 if (n <= 0) break;
 394 
 395                                 switch (PS(hash_func)) {
 396                                         case PS_HASH_FUNC_MD5:
 397                                                 PHP_MD5Update(&md5_context, rbuf, n);
 398                                                 break;
 399                                         case PS_HASH_FUNC_SHA1:
 400                                                 PHP_SHA1Update(&sha1_context, rbuf, n);
 401                                                 break;
 402 #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
 403                                         case PS_HASH_FUNC_OTHER:
 404                                                 PS(hash_ops)->hash_update(hash_context, rbuf, n);
 405                                                 break;
 406 #endif /* HAVE_HASH_EXT */
 407                                 }
 408                                 to_read -= n;
 409                         }
 410                         close(fd);
 411                 }
 412 #endif
 413         }
 414 
 415         digest = emalloc(digest_len + 1);
 416         switch (PS(hash_func)) {
 417                 case PS_HASH_FUNC_MD5:
 418                         PHP_MD5Final(digest, &md5_context);
 419                         break;
 420                 case PS_HASH_FUNC_SHA1:
 421                         PHP_SHA1Final(digest, &sha1_context);
 422                         break;
 423 #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
 424                 case PS_HASH_FUNC_OTHER:
 425                         PS(hash_ops)->hash_final(digest, hash_context);
 426                         efree(hash_context);
 427                         break;
 428 #endif /* HAVE_HASH_EXT */
 429         }
 430 
 431         if (PS(hash_bits_per_character) < 4
 432                         || PS(hash_bits_per_character) > 6) {
 433                 PS(hash_bits_per_character) = 4;
 434 
 435                 php_error_docref(NULL, E_WARNING, "The ini setting hash_bits_per_character is out of range (should be 4, 5, or 6) - using 4 for now");
 436         }
 437 
 438         outid = zend_string_alloc((digest_len + 2) * ((8.0f / PS(hash_bits_per_character) + 0.5)), 0);
 439         ZSTR_LEN(outid) = (size_t)(bin_to_readable((char *)digest, digest_len, ZSTR_VAL(outid), (char)PS(hash_bits_per_character)) - (char *)&ZSTR_VAL(outid));
 440         efree(digest);
 441 
 442         return outid;
 443 }
 444 /* }}} */
 445 
 446 /* Default session id char validation function allowed by ps_modules.
 447  * If you change the logic here, please also update the error message in
 448  * ps_modules appropriately */
 449 PHPAPI int php_session_valid_key(const char *key) /* {{{ */
 450 {
 451         size_t len;
 452         const char *p;
 453         char c;
 454         int ret = SUCCESS;
 455 
 456         for (p = key; (c = *p); p++) {
 457                 /* valid characters are a..z,A..Z,0..9 */
 458                 if (!((c >= 'a' && c <= 'z')
 459                                 || (c >= 'A' && c <= 'Z')
 460                                 || (c >= '0' && c <= '9')
 461                                 || c == ','
 462                                 || c == '-')) {
 463                         ret = FAILURE;
 464                         break;
 465                 }
 466         }
 467 
 468         len = p - key;
 469 
 470         /* Somewhat arbitrary length limit here, but should be way more than
 471            anyone needs and avoids file-level warnings later on if we exceed MAX_PATH */
 472         if (len == 0 || len > 128) {
 473                 ret = FAILURE;
 474         }
 475 
 476         return ret;
 477 }
 478 /* }}} */
 479 
 480 
 481 static void php_session_gc(void) /* {{{ */
 482 {
 483         int nrand;
 484 
 485         /* GC must be done before reading session data. */
 486         if ((PS(mod_data) || PS(mod_user_implemented)) && PS(gc_probability) > 0) {
 487                 int nrdels = -1;
 488 
 489                 nrand = (int) ((float) PS(gc_divisor) * php_combined_lcg());
 490                 if (nrand < PS(gc_probability)) {
 491                         PS(mod)->s_gc(&PS(mod_data), PS(gc_maxlifetime), &nrdels);
 492 #ifdef SESSION_DEBUG
 493                         if (nrdels != -1) {
 494                                 php_error_docref(NULL, E_NOTICE, "purged %d expired session objects", nrdels);
 495                         }
 496 #endif
 497                 }
 498         }
 499 } /* }}} */
 500 
 501 
 502 static void php_session_initialize(void) /* {{{ */
 503 {
 504         zend_string *val = NULL;
 505 
 506         if (!PS(mod)) {
 507                 php_error_docref(NULL, E_ERROR, "No storage module chosen - failed to initialize session");
 508                 return;
 509         }
 510 
 511         /* Open session handler first */
 512         if (PS(mod)->s_open(&PS(mod_data), PS(save_path), PS(session_name)) == FAILURE
 513                 /* || PS(mod_data) == NULL */ /* FIXME: open must set valid PS(mod_data) with success */
 514         ) {
 515                 php_error_docref(NULL, E_ERROR, "Failed to initialize storage module: %s (path: %s)", PS(mod)->s_name, PS(save_path));
 516                 return;
 517         }
 518 
 519         /* If there is no ID, use session module to create one */
 520         if (!PS(id)) {
 521                 PS(id) = PS(mod)->s_create_sid(&PS(mod_data));
 522                 if (!PS(id)) {
 523                         php_error_docref(NULL, E_ERROR, "Failed to create session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path));
 524                         return;
 525                 }
 526                 if (PS(use_cookies)) {
 527                         PS(send_cookie) = 1;
 528                 }
 529         } else if (PS(use_strict_mode) && PS(mod)->s_validate_sid &&
 530                 PS(mod)->s_validate_sid(&PS(mod_data), PS(id)) == FAILURE) {
 531                 if (PS(id)) {
 532                         zend_string_release(PS(id));
 533                 }
 534                 PS(id) = PS(mod)->s_create_sid(&PS(mod_data));
 535                 if (!PS(id)) {
 536                         PS(id) = php_session_create_id(NULL);
 537                 }
 538                 if (PS(use_cookies)) {
 539                         PS(send_cookie) = 1;
 540                 }
 541         }
 542 
 543         php_session_reset_id();
 544         PS(session_status) = php_session_active;
 545 
 546         /* Read data */
 547         php_session_track_init();
 548         if (PS(mod)->s_read(&PS(mod_data), PS(id), &val, PS(gc_maxlifetime)) == FAILURE) {
 549                 /* Some broken save handler implementation returns FAILURE for non-existent session ID */
 550                 /* It's better to raise error for this, but disabled error for better compatibility */
 551                 /*
 552                 php_error_docref(NULL, E_NOTICE, "Failed to read session data: %s (path: %s)", PS(mod)->s_name, PS(save_path));
 553                 */
 554         }
 555 
 556         /* GC must be done after read */
 557         php_session_gc();
 558 
 559         if (PS(session_vars)) {
 560                 zend_string_release(PS(session_vars));
 561                 PS(session_vars) = NULL;
 562         }
 563         if (val) {
 564                 if (PS(lazy_write)) {
 565                         PS(session_vars) = zend_string_copy(val);
 566                 }
 567                 php_session_decode(val);
 568                 zend_string_release(val);
 569         }
 570 }
 571 /* }}} */
 572 
 573 static void php_session_save_current_state(int write) /* {{{ */
 574 {
 575         int ret = FAILURE;
 576 
 577         if (write) {
 578                 IF_SESSION_VARS() {
 579                         if (PS(mod_data) || PS(mod_user_implemented)) {
 580                                 zend_string *val;
 581 
 582                                 val = php_session_encode();
 583                                 if (val) {
 584                                         if (PS(lazy_write) && PS(session_vars)
 585                                                 && PS(mod)->s_update_timestamp
 586                                                 && PS(mod)->s_update_timestamp != php_session_update_timestamp
 587                                                 && ZSTR_LEN(val) == ZSTR_LEN(PS(session_vars))
 588                                                 && !memcmp(ZSTR_VAL(val), ZSTR_VAL(PS(session_vars)), ZSTR_LEN(val))
 589                                         ) {
 590                                                 ret = PS(mod)->s_update_timestamp(&PS(mod_data), PS(id), val, PS(gc_maxlifetime));
 591                                         } else {
 592                                                 ret = PS(mod)->s_write(&PS(mod_data), PS(id), val, PS(gc_maxlifetime));
 593                                         }
 594                                         zend_string_release(val);
 595                                 } else {
 596                                         ret = PS(mod)->s_write(&PS(mod_data), PS(id), ZSTR_EMPTY_ALLOC(), PS(gc_maxlifetime));
 597                                 }
 598                         }
 599 
 600                         if ((ret == FAILURE) && !EG(exception)) {
 601                                 php_error_docref(NULL, E_WARNING, "Failed to write session data (%s). Please "
 602                                                                  "verify that the current setting of session.save_path "
 603                                                                  "is correct (%s)",
 604                                                                  PS(mod)->s_name,
 605                                                                  PS(save_path));
 606                         }
 607                 }
 608         }
 609 
 610         if (PS(mod_data) || PS(mod_user_implemented)) {
 611                 PS(mod)->s_close(&PS(mod_data));
 612         }
 613 }
 614 /* }}} */
 615 
 616 /* *************************
 617    * INI Settings/Handlers *
 618    ************************* */
 619 
 620 static PHP_INI_MH(OnUpdateSaveHandler) /* {{{ */
 621 {
 622         ps_module *tmp;
 623         SESSION_CHECK_ACTIVE_STATE;
 624 
 625         tmp = _php_find_ps_module(ZSTR_VAL(new_value));
 626 
 627         if (PG(modules_activated) && !tmp) {
 628                 int err_type;
 629 
 630                 if (stage == ZEND_INI_STAGE_RUNTIME) {
 631                         err_type = E_WARNING;
 632                 } else {
 633                         err_type = E_ERROR;
 634                 }
 635 
 636                 /* Do not output error when restoring ini options. */
 637                 if (stage != ZEND_INI_STAGE_DEACTIVATE) {
 638                         php_error_docref(NULL, err_type, "Cannot find save handler '%s'", ZSTR_VAL(new_value));
 639                 }
 640                 return FAILURE;
 641         }
 642 
 643         PS(default_mod) = PS(mod);
 644         PS(mod) = tmp;
 645 
 646         return SUCCESS;
 647 }
 648 /* }}} */
 649 
 650 static PHP_INI_MH(OnUpdateSerializer) /* {{{ */
 651 {
 652         const ps_serializer *tmp;
 653         SESSION_CHECK_ACTIVE_STATE;
 654 
 655         tmp = _php_find_ps_serializer(ZSTR_VAL(new_value));
 656 
 657         if (PG(modules_activated) && !tmp) {
 658                 int err_type;
 659 
 660                 if (stage == ZEND_INI_STAGE_RUNTIME) {
 661                         err_type = E_WARNING;
 662                 } else {
 663                         err_type = E_ERROR;
 664                 }
 665 
 666                 /* Do not output error when restoring ini options. */
 667                 if (stage != ZEND_INI_STAGE_DEACTIVATE) {
 668                         php_error_docref(NULL, err_type, "Cannot find serialization handler '%s'", ZSTR_VAL(new_value));
 669                 }
 670                 return FAILURE;
 671         }
 672         PS(serializer) = tmp;
 673 
 674         return SUCCESS;
 675 }
 676 /* }}} */
 677 
 678 static PHP_INI_MH(OnUpdateTransSid) /* {{{ */
 679 {
 680         SESSION_CHECK_ACTIVE_STATE;
 681 
 682         if (!strncasecmp(ZSTR_VAL(new_value), "on", sizeof("on"))) {
 683                 PS(use_trans_sid) = (zend_bool) 1;
 684         } else {
 685                 PS(use_trans_sid) = (zend_bool) atoi(ZSTR_VAL(new_value));
 686         }
 687 
 688         return SUCCESS;
 689 }
 690 /* }}} */
 691 
 692 static PHP_INI_MH(OnUpdateSaveDir) /* {{{ */
 693 {
 694         /* Only do the safemode/open_basedir check at runtime */
 695         if (stage == PHP_INI_STAGE_RUNTIME || stage == PHP_INI_STAGE_HTACCESS) {
 696                 char *p;
 697 
 698                 if (memchr(ZSTR_VAL(new_value), '\0', ZSTR_LEN(new_value)) != NULL) {
 699                         return FAILURE;
 700                 }
 701 
 702                 /* we do not use zend_memrchr() since path can contain ; itself */
 703                 if ((p = strchr(ZSTR_VAL(new_value), ';'))) {
 704                         char *p2;
 705                         p++;
 706                         if ((p2 = strchr(p, ';'))) {
 707                                 p = p2 + 1;
 708                         }
 709                 } else {
 710                         p = ZSTR_VAL(new_value);
 711                 }
 712 
 713                 if (PG(open_basedir) && *p && php_check_open_basedir(p)) {
 714                         return FAILURE;
 715                 }
 716         }
 717 
 718         OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
 719         return SUCCESS;
 720 }
 721 /* }}} */
 722 
 723 static PHP_INI_MH(OnUpdateName) /* {{{ */
 724 {
 725         /* Numeric session.name won't work at all */
 726         if ((!ZSTR_LEN(new_value) || is_numeric_string(ZSTR_VAL(new_value), ZSTR_LEN(new_value), NULL, NULL, 0))) {
 727                 int err_type;
 728 
 729                 if (stage == ZEND_INI_STAGE_RUNTIME || stage == ZEND_INI_STAGE_ACTIVATE || stage == ZEND_INI_STAGE_STARTUP) {
 730                         err_type = E_WARNING;
 731                 } else {
 732                         err_type = E_ERROR;
 733                 }
 734 
 735                 /* Do not output error when restoring ini options. */
 736                 if (stage != ZEND_INI_STAGE_DEACTIVATE) {
 737                         php_error_docref(NULL, err_type, "session.name cannot be a numeric or empty '%s'", ZSTR_VAL(new_value));
 738                 }
 739                 return FAILURE;
 740         }
 741 
 742         OnUpdateStringUnempty(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
 743         return SUCCESS;
 744 }
 745 /* }}} */
 746 
 747 static PHP_INI_MH(OnUpdateHashFunc) /* {{{ */
 748 {
 749         zend_long val;
 750         char *endptr = NULL;
 751 
 752 #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH)
 753         PS(hash_ops) = NULL;
 754 #endif
 755 
 756         val = ZEND_STRTOL(ZSTR_VAL(new_value), &endptr, 10);
 757         if (endptr && (*endptr == '\0')) {
 758                 /* Numeric value */
 759                 PS(hash_func) = val ? 1 : 0;
 760 
 761                 return SUCCESS;
 762         }
 763 
 764         if (ZSTR_LEN(new_value) == (sizeof("md5") - 1) &&
 765                 strncasecmp(ZSTR_VAL(new_value), "md5", sizeof("md5") - 1) == 0) {
 766                 PS(hash_func) = PS_HASH_FUNC_MD5;
 767 
 768                 return SUCCESS;
 769         }
 770 
 771         if (ZSTR_LEN(new_value) == (sizeof("sha1") - 1) &&
 772                 strncasecmp(ZSTR_VAL(new_value), "sha1", sizeof("sha1") - 1) == 0) {
 773                 PS(hash_func) = PS_HASH_FUNC_SHA1;
 774 
 775                 return SUCCESS;
 776         }
 777 
 778 #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH) /* {{{ */
 779 {
 780         php_hash_ops *ops = (php_hash_ops*)php_hash_fetch_ops(ZSTR_VAL(new_value), ZSTR_LEN(new_value));
 781 
 782         if (ops) {
 783                 PS(hash_func) = PS_HASH_FUNC_OTHER;
 784                 PS(hash_ops) = ops;
 785 
 786                 return SUCCESS;
 787         }
 788 }
 789 #endif /* HAVE_HASH_EXT }}} */
 790 
 791         php_error_docref(NULL, E_WARNING, "session.configuration 'session.hash_function' must be existing hash function. %s does not exist.", ZSTR_VAL(new_value));
 792         return FAILURE;
 793 }
 794 /* }}} */
 795 
 796 static PHP_INI_MH(OnUpdateRfc1867Freq) /* {{{ */
 797 {
 798         int tmp;
 799         tmp = zend_atoi(ZSTR_VAL(new_value), (int)ZSTR_LEN(new_value));
 800         if(tmp < 0) {
 801                 php_error_docref(NULL, E_WARNING, "session.upload_progress.freq must be greater than or equal to zero");
 802                 return FAILURE;
 803         }
 804         if(ZSTR_LEN(new_value) > 0 && ZSTR_VAL(new_value)[ZSTR_LEN(new_value)-1] == '%') {
 805                 if(tmp > 100) {
 806                         php_error_docref(NULL, E_WARNING, "session.upload_progress.freq cannot be over 100%%");
 807                         return FAILURE;
 808                 }
 809                 PS(rfc1867_freq) = -tmp;
 810         } else {
 811                 PS(rfc1867_freq) = tmp;
 812         }
 813         return SUCCESS;
 814 } /* }}} */
 815 
 816 /* {{{ PHP_INI
 817  */
 818 PHP_INI_BEGIN()
 819         STD_PHP_INI_ENTRY("session.save_path",          "",          PHP_INI_ALL, OnUpdateSaveDir,save_path,          php_ps_globals,    ps_globals)
 820         STD_PHP_INI_ENTRY("session.name",               "PHPSESSID", PHP_INI_ALL, OnUpdateName, session_name,       php_ps_globals,    ps_globals)
 821         PHP_INI_ENTRY("session.save_handler",           "files",     PHP_INI_ALL, OnUpdateSaveHandler)
 822         STD_PHP_INI_BOOLEAN("session.auto_start",       "0",         PHP_INI_PERDIR, OnUpdateBool,   auto_start,         php_ps_globals,    ps_globals)
 823         STD_PHP_INI_ENTRY("session.gc_probability",     "1",         PHP_INI_ALL, OnUpdateLong,   gc_probability,     php_ps_globals,    ps_globals)
 824         STD_PHP_INI_ENTRY("session.gc_divisor",         "100",       PHP_INI_ALL, OnUpdateLong,   gc_divisor,         php_ps_globals,    ps_globals)
 825         STD_PHP_INI_ENTRY("session.gc_maxlifetime",     "1440",      PHP_INI_ALL, OnUpdateLong,   gc_maxlifetime,     php_ps_globals,    ps_globals)
 826         PHP_INI_ENTRY("session.serialize_handler",      "php",       PHP_INI_ALL, OnUpdateSerializer)
 827         STD_PHP_INI_ENTRY("session.cookie_lifetime",    "0",         PHP_INI_ALL, OnUpdateLong,   cookie_lifetime,    php_ps_globals,    ps_globals)
 828         STD_PHP_INI_ENTRY("session.cookie_path",        "/",         PHP_INI_ALL, OnUpdateString, cookie_path,        php_ps_globals,    ps_globals)
 829         STD_PHP_INI_ENTRY("session.cookie_domain",      "",          PHP_INI_ALL, OnUpdateString, cookie_domain,      php_ps_globals,    ps_globals)
 830         STD_PHP_INI_BOOLEAN("session.cookie_secure",    "",          PHP_INI_ALL, OnUpdateBool,   cookie_secure,      php_ps_globals,    ps_globals)
 831         STD_PHP_INI_BOOLEAN("session.cookie_httponly",  "",          PHP_INI_ALL, OnUpdateBool,   cookie_httponly,    php_ps_globals,    ps_globals)
 832         STD_PHP_INI_BOOLEAN("session.use_cookies",      "1",         PHP_INI_ALL, OnUpdateBool,   use_cookies,        php_ps_globals,    ps_globals)
 833         STD_PHP_INI_BOOLEAN("session.use_only_cookies", "1",         PHP_INI_ALL, OnUpdateBool,   use_only_cookies,   php_ps_globals,    ps_globals)
 834         STD_PHP_INI_BOOLEAN("session.use_strict_mode",  "0",         PHP_INI_ALL, OnUpdateBool,   use_strict_mode,    php_ps_globals,    ps_globals)
 835         STD_PHP_INI_ENTRY("session.referer_check",      "",          PHP_INI_ALL, OnUpdateString, extern_referer_chk, php_ps_globals,    ps_globals)
 836 #if HAVE_DEV_URANDOM
 837         STD_PHP_INI_ENTRY("session.entropy_file",       "/dev/urandom",          PHP_INI_ALL, OnUpdateString, entropy_file,       php_ps_globals,    ps_globals)
 838         STD_PHP_INI_ENTRY("session.entropy_length",     "32",         PHP_INI_ALL, OnUpdateLong,   entropy_length,     php_ps_globals,    ps_globals)
 839 #elif HAVE_DEV_ARANDOM
 840         STD_PHP_INI_ENTRY("session.entropy_file",       "/dev/arandom",          PHP_INI_ALL, OnUpdateString, entropy_file,       php_ps_globals,    ps_globals)
 841         STD_PHP_INI_ENTRY("session.entropy_length",     "32",         PHP_INI_ALL, OnUpdateLong,   entropy_length,     php_ps_globals,    ps_globals)
 842 #else
 843         STD_PHP_INI_ENTRY("session.entropy_file",       "",          PHP_INI_ALL, OnUpdateString, entropy_file,       php_ps_globals,    ps_globals)
 844         STD_PHP_INI_ENTRY("session.entropy_length",     "0",         PHP_INI_ALL, OnUpdateLong,   entropy_length,     php_ps_globals,    ps_globals)
 845 #endif
 846         STD_PHP_INI_ENTRY("session.cache_limiter",      "nocache",   PHP_INI_ALL, OnUpdateString, cache_limiter,      php_ps_globals,    ps_globals)
 847         STD_PHP_INI_ENTRY("session.cache_expire",       "180",       PHP_INI_ALL, OnUpdateLong,   cache_expire,       php_ps_globals,    ps_globals)
 848         PHP_INI_ENTRY("session.use_trans_sid",          "0",         PHP_INI_ALL, OnUpdateTransSid)
 849         PHP_INI_ENTRY("session.hash_function",          "0",         PHP_INI_ALL, OnUpdateHashFunc)
 850         STD_PHP_INI_ENTRY("session.hash_bits_per_character", "4",    PHP_INI_ALL, OnUpdateLong,   hash_bits_per_character, php_ps_globals, ps_globals)
 851         STD_PHP_INI_BOOLEAN("session.lazy_write",       "1",         PHP_INI_ALL, OnUpdateBool,   lazy_write,         php_ps_globals,    ps_globals)
 852 
 853         /* Upload progress */
 854         STD_PHP_INI_BOOLEAN("session.upload_progress.enabled",
 855                                                         "1",     ZEND_INI_PERDIR, OnUpdateBool,        rfc1867_enabled, php_ps_globals, ps_globals)
 856         STD_PHP_INI_BOOLEAN("session.upload_progress.cleanup",
 857                                                         "1",     ZEND_INI_PERDIR, OnUpdateBool,        rfc1867_cleanup, php_ps_globals, ps_globals)
 858         STD_PHP_INI_ENTRY("session.upload_progress.prefix",
 859                                              "upload_progress_", ZEND_INI_PERDIR, OnUpdateString,      rfc1867_prefix,  php_ps_globals, ps_globals)
 860         STD_PHP_INI_ENTRY("session.upload_progress.name",
 861                                   "PHP_SESSION_UPLOAD_PROGRESS", ZEND_INI_PERDIR, OnUpdateString,      rfc1867_name,    php_ps_globals, ps_globals)
 862         STD_PHP_INI_ENTRY("session.upload_progress.freq",  "1%", ZEND_INI_PERDIR, OnUpdateRfc1867Freq, rfc1867_freq,    php_ps_globals, ps_globals)
 863         STD_PHP_INI_ENTRY("session.upload_progress.min_freq",
 864                                                            "1",  ZEND_INI_PERDIR, OnUpdateReal,        rfc1867_min_freq,php_ps_globals, ps_globals)
 865 
 866         /* Commented out until future discussion */
 867         /* PHP_INI_ENTRY("session.encode_sources", "globals,track", PHP_INI_ALL, NULL) */
 868 PHP_INI_END()
 869 /* }}} */
 870 
 871 /* ***************
 872    * Serializers *
 873    *************** */
 874 PS_SERIALIZER_ENCODE_FUNC(php_serialize) /* {{{ */
 875 {
 876         smart_str buf = {0};
 877         php_serialize_data_t var_hash;
 878 
 879         IF_SESSION_VARS() {
 880                 PHP_VAR_SERIALIZE_INIT(var_hash);
 881                 php_var_serialize(&buf, Z_REFVAL(PS(http_session_vars)), &var_hash);
 882                 PHP_VAR_SERIALIZE_DESTROY(var_hash);
 883         }
 884         return buf.s;
 885 }
 886 /* }}} */
 887 
 888 PS_SERIALIZER_DECODE_FUNC(php_serialize) /* {{{ */
 889 {
 890         const char *endptr = val + vallen;
 891         zval session_vars;
 892         php_unserialize_data_t var_hash;
 893         zend_string *var_name = zend_string_init("_SESSION", sizeof("_SESSION") - 1, 0);
 894 
 895         ZVAL_NULL(&session_vars);
 896         PHP_VAR_UNSERIALIZE_INIT(var_hash);
 897         php_var_unserialize(&session_vars, (const unsigned char **)&val, (const unsigned char *)endptr, &var_hash);
 898         PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
 899         if (!Z_ISUNDEF(PS(http_session_vars))) {
 900                 zval_ptr_dtor(&PS(http_session_vars));
 901         }
 902         if (Z_TYPE(session_vars) == IS_NULL) {
 903                 array_init(&session_vars);
 904         }
 905         ZVAL_NEW_REF(&PS(http_session_vars), &session_vars);
 906         Z_ADDREF_P(&PS(http_session_vars));
 907         zend_hash_update_ind(&EG(symbol_table), var_name, &PS(http_session_vars));
 908         zend_string_release(var_name);
 909         return SUCCESS;
 910 }
 911 /* }}} */
 912 
 913 #define PS_BIN_NR_OF_BITS 8
 914 #define PS_BIN_UNDEF (1<<(PS_BIN_NR_OF_BITS-1))
 915 #define PS_BIN_MAX (PS_BIN_UNDEF-1)
 916 
 917 PS_SERIALIZER_ENCODE_FUNC(php_binary) /* {{{ */
 918 {
 919         smart_str buf = {0};
 920         php_serialize_data_t var_hash;
 921         PS_ENCODE_VARS;
 922 
 923         PHP_VAR_SERIALIZE_INIT(var_hash);
 924 
 925         PS_ENCODE_LOOP(
 926                         if (ZSTR_LEN(key) > PS_BIN_MAX) continue;
 927                         smart_str_appendc(&buf, (unsigned char)ZSTR_LEN(key));
 928                         smart_str_appendl(&buf, ZSTR_VAL(key), ZSTR_LEN(key));
 929                         php_var_serialize(&buf, struc, &var_hash);
 930                 } else {
 931                         if (ZSTR_LEN(key) > PS_BIN_MAX) continue;
 932                         smart_str_appendc(&buf, (unsigned char) (ZSTR_LEN(key) & PS_BIN_UNDEF));
 933                         smart_str_appendl(&buf, ZSTR_VAL(key), ZSTR_LEN(key));
 934         );
 935 
 936         smart_str_0(&buf);
 937         PHP_VAR_SERIALIZE_DESTROY(var_hash);
 938 
 939         return buf.s;
 940 }
 941 /* }}} */
 942 
 943 PS_SERIALIZER_DECODE_FUNC(php_binary) /* {{{ */
 944 {
 945         const char *p;
 946         const char *endptr = val + vallen;
 947         zval current;
 948         int has_value;
 949         int namelen;
 950         zend_string *name;
 951         php_unserialize_data_t var_hash;
 952 
 953         PHP_VAR_UNSERIALIZE_INIT(var_hash);
 954 
 955         for (p = val; p < endptr; ) {
 956                 zval *tmp;
 957                 namelen = ((unsigned char)(*p)) & (~PS_BIN_UNDEF);
 958 
 959                 if (namelen < 0 || namelen > PS_BIN_MAX || (p + namelen) >= endptr) {
 960                         return FAILURE;
 961                 }
 962 
 963                 has_value = *p & PS_BIN_UNDEF ? 0 : 1;
 964 
 965                 name = zend_string_init(p + 1, namelen, 0);
 966 
 967                 p += namelen + 1;
 968 
 969                 if ((tmp = zend_hash_find(&EG(symbol_table), name))) {
 970                         if ((Z_TYPE_P(tmp) == IS_ARRAY && Z_ARRVAL_P(tmp) == &EG(symbol_table)) || tmp == &PS(http_session_vars)) {
 971                                 zend_string_release(name);
 972                                 continue;
 973                         }
 974                 }
 975 
 976                 if (has_value) {
 977                         ZVAL_UNDEF(&current);
 978                         if (php_var_unserialize(&current, (const unsigned char **) &p, (const unsigned char *) endptr, &var_hash)) {
 979                                 zval *zv = php_set_session_var(name, &current, &var_hash );
 980                                 var_replace(&var_hash, &current, zv);
 981                         } else {
 982                                 zval_ptr_dtor(&current);
 983                                 zend_string_release(name);
 984                                 PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
 985                                 return FAILURE;
 986                         }
 987                 }
 988                 PS_ADD_VARL(name);
 989                 zend_string_release(name);
 990         }
 991 
 992         PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
 993 
 994         return SUCCESS;
 995 }
 996 /* }}} */
 997 
 998 #define PS_DELIMITER '|'
 999 #define PS_UNDEF_MARKER '!'
1000 
1001 PS_SERIALIZER_ENCODE_FUNC(php) /* {{{ */
1002 {
1003         smart_str buf = {0};
1004         php_serialize_data_t var_hash;
1005         PS_ENCODE_VARS;
1006 
1007         PHP_VAR_SERIALIZE_INIT(var_hash);
1008 
1009         PS_ENCODE_LOOP(
1010                         smart_str_appendl(&buf, ZSTR_VAL(key), ZSTR_LEN(key));
1011                         if (memchr(ZSTR_VAL(key), PS_DELIMITER, ZSTR_LEN(key)) || memchr(ZSTR_VAL(key), PS_UNDEF_MARKER, ZSTR_LEN(key))) {
1012                                 PHP_VAR_SERIALIZE_DESTROY(var_hash);
1013                                 smart_str_free(&buf);
1014                                 return NULL;
1015                         }
1016                         smart_str_appendc(&buf, PS_DELIMITER);
1017 
1018                         php_var_serialize(&buf, struc, &var_hash);
1019                 } else {
1020                         smart_str_appendc(&buf, PS_UNDEF_MARKER);
1021                         smart_str_appendl(&buf, ZSTR_VAL(key), ZSTR_LEN(key));
1022                         smart_str_appendc(&buf, PS_DELIMITER);
1023         );
1024 
1025         smart_str_0(&buf);
1026 
1027         PHP_VAR_SERIALIZE_DESTROY(var_hash);
1028         return buf.s;
1029 }
1030 /* }}} */
1031 
1032 PS_SERIALIZER_DECODE_FUNC(php) /* {{{ */
1033 {
1034         const char *p, *q;
1035         const char *endptr = val + vallen;
1036         zval current;
1037         int has_value;
1038         ptrdiff_t namelen;
1039         zend_string *name;
1040         php_unserialize_data_t var_hash;
1041 
1042         PHP_VAR_UNSERIALIZE_INIT(var_hash);
1043 
1044         p = val;
1045 
1046         while (p < endptr) {
1047                 zval *tmp;
1048                 q = p;
1049                 while (*q != PS_DELIMITER) {
1050                         if (++q >= endptr) goto break_outer_loop;
1051                 }
1052                 if (p[0] == PS_UNDEF_MARKER) {
1053                         p++;
1054                         has_value = 0;
1055                 } else {
1056                         has_value = 1;
1057                 }
1058 
1059                 namelen = q - p;
1060                 name = zend_string_init(p, namelen, 0);
1061                 q++;
1062 
1063                 if ((tmp = zend_hash_find(&EG(symbol_table), name))) {
1064                         if ((Z_TYPE_P(tmp) == IS_ARRAY && Z_ARRVAL_P(tmp) == &EG(symbol_table)) || tmp == &PS(http_session_vars)) {
1065                                 goto skip;
1066                         }
1067                 }
1068 
1069                 if (has_value) {
1070                         ZVAL_UNDEF(&current);
1071                         if (php_var_unserialize(&current, (const unsigned char **) &q, (const unsigned char *) endptr, &var_hash)) {
1072                                 zval *zv = php_set_session_var(name, &current, &var_hash);
1073                                 var_replace(&var_hash, &current, zv);
1074                         } else {
1075                                 zval_ptr_dtor(&current);
1076                                 PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
1077                                 zend_string_release(name);
1078                                 return FAILURE;
1079                         }
1080                 }
1081                 PS_ADD_VARL(name);
1082 skip:
1083                 zend_string_release(name);
1084 
1085                 p = q;
1086         }
1087 break_outer_loop:
1088 
1089         PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
1090 
1091         return SUCCESS;
1092 }
1093 /* }}} */
1094 
1095 #define MAX_SERIALIZERS 32
1096 #define PREDEFINED_SERIALIZERS 3
1097 
1098 static ps_serializer ps_serializers[MAX_SERIALIZERS + 1] = {
1099         PS_SERIALIZER_ENTRY(php_serialize),
1100         PS_SERIALIZER_ENTRY(php),
1101         PS_SERIALIZER_ENTRY(php_binary)
1102 };
1103 
1104 PHPAPI int php_session_register_serializer(const char *name, zend_string *(*encode)(PS_SERIALIZER_ENCODE_ARGS), int (*decode)(PS_SERIALIZER_DECODE_ARGS)) /* {{{ */
1105 {
1106         int ret = -1;
1107         int i;
1108 
1109         for (i = 0; i < MAX_SERIALIZERS; i++) {
1110                 if (ps_serializers[i].name == NULL) {
1111                         ps_serializers[i].name = name;
1112                         ps_serializers[i].encode = encode;
1113                         ps_serializers[i].decode = decode;
1114                         ps_serializers[i + 1].name = NULL;
1115                         ret = 0;
1116                         break;
1117                 }
1118         }
1119         return ret;
1120 }
1121 /* }}} */
1122 
1123 /* *******************
1124    * Storage Modules *
1125    ******************* */
1126 
1127 #define MAX_MODULES 32
1128 #define PREDEFINED_MODULES 2
1129 
1130 static ps_module *ps_modules[MAX_MODULES + 1] = {
1131         ps_files_ptr,
1132         ps_user_ptr
1133 };
1134 
1135 PHPAPI int php_session_register_module(ps_module *ptr) /* {{{ */
1136 {
1137         int ret = -1;
1138         int i;
1139 
1140         for (i = 0; i < MAX_MODULES; i++) {
1141                 if (!ps_modules[i]) {
1142                         ps_modules[i] = ptr;
1143                         ret = 0;
1144                         break;
1145                 }
1146         }
1147         return ret;
1148 }
1149 /* }}} */
1150 
1151 /* Dummy PS module function */
1152 PHPAPI int php_session_validate_sid(PS_VALIDATE_SID_ARGS) {
1153         return SUCCESS;
1154 }
1155 
1156 /* Dummy PS module function */
1157 PHPAPI int php_session_update_timestamp(PS_UPDATE_TIMESTAMP_ARGS) {
1158         return SUCCESS;
1159 }
1160 
1161 
1162 /* ******************
1163    * Cache Limiters *
1164    ****************** */
1165 
1166 typedef struct {
1167         char *name;
1168         void (*func)(void);
1169 } php_session_cache_limiter_t;
1170 
1171 #define CACHE_LIMITER(name) _php_cache_limiter_##name
1172 #define CACHE_LIMITER_FUNC(name) static void CACHE_LIMITER(name)(void)
1173 #define CACHE_LIMITER_ENTRY(name) { #name, CACHE_LIMITER(name) },
1174 #define ADD_HEADER(a) sapi_add_header(a, strlen(a), 1);
1175 #define MAX_STR 512
1176 
1177 static char *month_names[] = {
1178         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1179         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1180 };
1181 
1182 static char *week_days[] = {
1183         "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
1184 };
1185 
1186 static inline void strcpy_gmt(char *ubuf, time_t *when) /* {{{ */
1187 {
1188         char buf[MAX_STR];
1189         struct tm tm, *res;
1190         int n;
1191 
1192         res = php_gmtime_r(when, &tm);
1193 
1194         if (!res) {
1195                 ubuf[0] = '\0';
1196                 return;
1197         }
1198 
1199         n = slprintf(buf, sizeof(buf), "%s, %02d %s %d %02d:%02d:%02d GMT", /* SAFE */
1200                                 week_days[tm.tm_wday], tm.tm_mday,
1201                                 month_names[tm.tm_mon], tm.tm_year + 1900,
1202                                 tm.tm_hour, tm.tm_min,
1203                                 tm.tm_sec);
1204         memcpy(ubuf, buf, n);
1205         ubuf[n] = '\0';
1206 }
1207 /* }}} */
1208 
1209 static inline void last_modified(void) /* {{{ */
1210 {
1211         const char *path;
1212         zend_stat_t sb;
1213         char buf[MAX_STR + 1];
1214 
1215         path = SG(request_info).path_translated;
1216         if (path) {
1217                 if (VCWD_STAT(path, &sb) == -1) {
1218                         return;
1219                 }
1220 
1221 #define LAST_MODIFIED "Last-Modified: "
1222                 memcpy(buf, LAST_MODIFIED, sizeof(LAST_MODIFIED) - 1);
1223                 strcpy_gmt(buf + sizeof(LAST_MODIFIED) - 1, &sb.st_mtime);
1224                 ADD_HEADER(buf);
1225         }
1226 }
1227 /* }}} */
1228 
1229 #define EXPIRES "Expires: "
1230 CACHE_LIMITER_FUNC(public) /* {{{ */
1231 {
1232         char buf[MAX_STR + 1];
1233         struct timeval tv;
1234         time_t now;
1235 
1236         gettimeofday(&tv, NULL);
1237         now = tv.tv_sec + PS(cache_expire) * 60;
1238         memcpy(buf, EXPIRES, sizeof(EXPIRES) - 1);
1239         strcpy_gmt(buf + sizeof(EXPIRES) - 1, &now);
1240         ADD_HEADER(buf);
1241 
1242         snprintf(buf, sizeof(buf) , "Cache-Control: public, max-age=" ZEND_LONG_FMT, PS(cache_expire) * 60); /* SAFE */
1243         ADD_HEADER(buf);
1244 
1245         last_modified();
1246 }
1247 /* }}} */
1248 
1249 CACHE_LIMITER_FUNC(private_no_expire) /* {{{ */
1250 {
1251         char buf[MAX_STR + 1];
1252 
1253         snprintf(buf, sizeof(buf), "Cache-Control: private, max-age=" ZEND_LONG_FMT, PS(cache_expire) * 60); /* SAFE */
1254         ADD_HEADER(buf);
1255 
1256         last_modified();
1257 }
1258 /* }}} */
1259 
1260 CACHE_LIMITER_FUNC(private) /* {{{ */
1261 {
1262         ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
1263         CACHE_LIMITER(private_no_expire)();
1264 }
1265 /* }}} */
1266 
1267 CACHE_LIMITER_FUNC(nocache) /* {{{ */
1268 {
1269         ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
1270 
1271         /* For HTTP/1.1 conforming clients */
1272         ADD_HEADER("Cache-Control: no-store, no-cache, must-revalidate");
1273 
1274         /* For HTTP/1.0 conforming clients */
1275         ADD_HEADER("Pragma: no-cache");
1276 }
1277 /* }}} */
1278 
1279 static php_session_cache_limiter_t php_session_cache_limiters[] = {
1280         CACHE_LIMITER_ENTRY(public)
1281         CACHE_LIMITER_ENTRY(private)
1282         CACHE_LIMITER_ENTRY(private_no_expire)
1283         CACHE_LIMITER_ENTRY(nocache)
1284         {0}
1285 };
1286 
1287 static int php_session_cache_limiter(void) /* {{{ */
1288 {
1289         php_session_cache_limiter_t *lim;
1290 
1291         if (PS(cache_limiter)[0] == '\0') return 0;
1292 
1293         if (SG(headers_sent)) {
1294                 const char *output_start_filename = php_output_get_start_filename();
1295                 int output_start_lineno = php_output_get_start_lineno();
1296 
1297                 if (output_start_filename) {
1298                         php_error_docref(NULL, E_WARNING, "Cannot send session cache limiter - headers already sent (output started at %s:%d)", output_start_filename, output_start_lineno);
1299                 } else {
1300                         php_error_docref(NULL, E_WARNING, "Cannot send session cache limiter - headers already sent");
1301                 }
1302                 return -2;
1303         }
1304 
1305         for (lim = php_session_cache_limiters; lim->name; lim++) {
1306                 if (!strcasecmp(lim->name, PS(cache_limiter))) {
1307                         lim->func();
1308                         return 0;
1309                 }
1310         }
1311 
1312         return -1;
1313 }
1314 /* }}} */
1315 
1316 /* *********************
1317    * Cookie Management *
1318    ********************* */
1319 
1320 /*
1321  * Remove already sent session ID cookie.
1322  * It must be directly removed from SG(sapi_header) because sapi_add_header_ex()
1323  * removes all of matching cookie. i.e. It deletes all of Set-Cookie headers.
1324  */
1325 static void php_session_remove_cookie(void) {
1326         sapi_header_struct *header;
1327         zend_llist *l = &SG(sapi_headers).headers;
1328         zend_llist_element *next;
1329         zend_llist_element *current;
1330         char *session_cookie;
1331         zend_string *e_session_name;
1332         size_t session_cookie_len;
1333         size_t len = sizeof("Set-Cookie")-1;
1334 
1335         e_session_name = php_url_encode(PS(session_name), strlen(PS(session_name)));
1336         spprintf(&session_cookie, 0, "Set-Cookie: %s=", ZSTR_VAL(e_session_name));
1337         zend_string_free(e_session_name);
1338 
1339         session_cookie_len = strlen(session_cookie);
1340         current = l->head;
1341         while (current) {
1342                 header = (sapi_header_struct *)(current->data);
1343                 next = current->next;
1344                 if (header->header_len > len && header->header[len] == ':'
1345                         && !strncmp(header->header, session_cookie, session_cookie_len)) {
1346                         if (current->prev) {
1347                                 current->prev->next = next;
1348                         } else {
1349                                 l->head = next;
1350                         }
1351                         if (next) {
1352                                 next->prev = current->prev;
1353                         } else {
1354                                 l->tail = current->prev;
1355                         }
1356                         sapi_free_header(header);
1357                         efree(current);
1358                         --l->count;
1359                 }
1360                 current = next;
1361         }
1362         efree(session_cookie);
1363 }
1364 
1365 static void php_session_send_cookie(void) /* {{{ */
1366 {
1367         smart_str ncookie = {0};
1368         zend_string *date_fmt = NULL;
1369         zend_string *e_session_name, *e_id;
1370 
1371         if (SG(headers_sent)) {
1372                 const char *output_start_filename = php_output_get_start_filename();
1373                 int output_start_lineno = php_output_get_start_lineno();
1374 
1375                 if (output_start_filename) {
1376                         php_error_docref(NULL, E_WARNING, "Cannot send session cookie - headers already sent by (output started at %s:%d)", output_start_filename, output_start_lineno);
1377                 } else {
1378                         php_error_docref(NULL, E_WARNING, "Cannot send session cookie - headers already sent");
1379                 }
1380                 return;
1381         }
1382 
1383         /* URL encode session_name and id because they might be user supplied */
1384         e_session_name = php_url_encode(PS(session_name), strlen(PS(session_name)));
1385         e_id = php_url_encode(ZSTR_VAL(PS(id)), ZSTR_LEN(PS(id)));
1386 
1387         smart_str_appendl(&ncookie, "Set-Cookie: ", sizeof("Set-Cookie: ")-1);
1388         smart_str_appendl(&ncookie, ZSTR_VAL(e_session_name), ZSTR_LEN(e_session_name));
1389         smart_str_appendc(&ncookie, '=');
1390         smart_str_appendl(&ncookie, ZSTR_VAL(e_id), ZSTR_LEN(e_id));
1391 
1392         zend_string_release(e_session_name);
1393         zend_string_release(e_id);
1394 
1395         if (PS(cookie_lifetime) > 0) {
1396                 struct timeval tv;
1397                 time_t t;
1398 
1399                 gettimeofday(&tv, NULL);
1400                 t = tv.tv_sec + PS(cookie_lifetime);
1401 
1402                 if (t > 0) {
1403                         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);
1404                         smart_str_appends(&ncookie, COOKIE_EXPIRES);
1405                         smart_str_appendl(&ncookie, ZSTR_VAL(date_fmt), ZSTR_LEN(date_fmt));
1406                         zend_string_release(date_fmt);
1407 
1408                         smart_str_appends(&ncookie, COOKIE_MAX_AGE);
1409                         smart_str_append_long(&ncookie, PS(cookie_lifetime));
1410                 }
1411         }
1412 
1413         if (PS(cookie_path)[0]) {
1414                 smart_str_appends(&ncookie, COOKIE_PATH);
1415                 smart_str_appends(&ncookie, PS(cookie_path));
1416         }
1417 
1418         if (PS(cookie_domain)[0]) {
1419                 smart_str_appends(&ncookie, COOKIE_DOMAIN);
1420                 smart_str_appends(&ncookie, PS(cookie_domain));
1421         }
1422 
1423         if (PS(cookie_secure)) {
1424                 smart_str_appends(&ncookie, COOKIE_SECURE);
1425         }
1426 
1427         if (PS(cookie_httponly)) {
1428                 smart_str_appends(&ncookie, COOKIE_HTTPONLY);
1429         }
1430 
1431         smart_str_0(&ncookie);
1432 
1433         php_session_remove_cookie(); /* remove already sent session ID cookie */
1434         /*      'replace' must be 0 here, else a previous Set-Cookie
1435                 header, probably sent with setcookie() will be replaced! */
1436         sapi_add_header_ex(estrndup(ZSTR_VAL(ncookie.s), ZSTR_LEN(ncookie.s)), ZSTR_LEN(ncookie.s), 0, 0);
1437         smart_str_free(&ncookie);
1438 }
1439 /* }}} */
1440 
1441 PHPAPI ps_module *_php_find_ps_module(char *name) /* {{{ */
1442 {
1443         ps_module *ret = NULL;
1444         ps_module **mod;
1445         int i;
1446 
1447         for (i = 0, mod = ps_modules; i < MAX_MODULES; i++, mod++) {
1448                 if (*mod && !strcasecmp(name, (*mod)->s_name)) {
1449                         ret = *mod;
1450                         break;
1451                 }
1452         }
1453         return ret;
1454 }
1455 /* }}} */
1456 
1457 PHPAPI const ps_serializer *_php_find_ps_serializer(char *name) /* {{{ */
1458 {
1459         const ps_serializer *ret = NULL;
1460         const ps_serializer *mod;
1461 
1462         for (mod = ps_serializers; mod->name; mod++) {
1463                 if (!strcasecmp(name, mod->name)) {
1464                         ret = mod;
1465                         break;
1466                 }
1467         }
1468         return ret;
1469 }
1470 /* }}} */
1471 
1472 static void ppid2sid(zval *ppid) {
1473         ZVAL_DEREF(ppid);
1474         if (Z_TYPE_P(ppid) == IS_STRING) {
1475                 PS(id) = zend_string_init(Z_STRVAL_P(ppid), Z_STRLEN_P(ppid), 0);
1476                 PS(send_cookie) = 0;
1477         } else {
1478                 PS(id) = NULL;
1479                 PS(send_cookie) = 1;
1480         }
1481 }
1482 
1483 PHPAPI void php_session_reset_id(void) /* {{{ */
1484 {
1485         int module_number = PS(module_number);
1486         zval *sid;
1487 
1488         if (!PS(id)) {
1489                 php_error_docref(NULL, E_WARNING, "Cannot set session ID - session ID is not initialized");
1490                 return;
1491         }
1492 
1493         if (PS(use_cookies) && PS(send_cookie)) {
1494                 php_session_send_cookie();
1495                 PS(send_cookie) = 0;
1496         }
1497 
1498         /* If the SID constant exists, destroy it. */
1499         /* We must not delete any items in EG(zend_contants) */
1500         /* zend_hash_str_del(EG(zend_constants), "sid", sizeof("sid") - 1); */
1501         sid = zend_get_constant_str("SID", sizeof("SID") - 1);
1502 
1503         if (PS(define_sid)) {
1504                 smart_str var = {0};
1505 
1506                 smart_str_appends(&var, PS(session_name));
1507                 smart_str_appendc(&var, '=');
1508                 smart_str_appends(&var, ZSTR_VAL(PS(id)));
1509                 smart_str_0(&var);
1510                 if (sid) {
1511                         zend_string_release(Z_STR_P(sid));
1512                         ZVAL_NEW_STR(sid, var.s);
1513                 } else {
1514                         REGISTER_STRINGL_CONSTANT("SID", ZSTR_VAL(var.s), ZSTR_LEN(var.s), 0);
1515                         smart_str_free(&var);
1516                 }
1517         } else {
1518                 if (sid) {
1519                         zend_string_release(Z_STR_P(sid));
1520                         ZVAL_EMPTY_STRING(sid);
1521                 } else {
1522                         REGISTER_STRINGL_CONSTANT("SID", "", 0, 0);
1523                 }
1524         }
1525 
1526         if (APPLY_TRANS_SID) {
1527                 /* FIXME: Resetting vars are required when
1528                    session is stop/start/regenerated. However,
1529                    php_url_scanner_reset_vars() resets all vars
1530                    including other URL rewrites set by elsewhere. */
1531                 /* php_url_scanner_reset_vars(); */
1532                 php_url_scanner_add_var(PS(session_name), strlen(PS(session_name)), ZSTR_VAL(PS(id)), ZSTR_LEN(PS(id)), 1);
1533         }
1534 }
1535 /* }}} */
1536 
1537 PHPAPI void php_session_start(void) /* {{{ */
1538 {
1539         zval *ppid;
1540         zval *data;
1541         char *p, *value;
1542         size_t lensess;
1543 
1544         switch (PS(session_status)) {
1545                 case php_session_active:
1546                         php_error(E_NOTICE, "A session had already been started - ignoring session_start()");
1547                         return;
1548                         break;
1549 
1550                 case php_session_disabled:
1551                         value = zend_ini_string("session.save_handler", sizeof("session.save_handler") - 1, 0);
1552                         if (!PS(mod) && value) {
1553                                 PS(mod) = _php_find_ps_module(value);
1554                                 if (!PS(mod)) {
1555                                         php_error_docref(NULL, E_WARNING, "Cannot find save handler '%s' - session startup failed", value);
1556                                         return;
1557                                 }
1558                         }
1559                         value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler") - 1, 0);
1560                         if (!PS(serializer) && value) {
1561                                 PS(serializer) = _php_find_ps_serializer(value);
1562                                 if (!PS(serializer)) {
1563                                         php_error_docref(NULL, E_WARNING, "Cannot find serialization handler '%s' - session startup failed", value);
1564                                         return;
1565                                 }
1566                         }
1567                         PS(session_status) = php_session_none;
1568                         /* fallthrough */
1569 
1570                 default:
1571                 case php_session_none:
1572                         /* Setup internal flags */
1573                         PS(define_sid) = !PS(use_only_cookies); /* SID constant is defined when non-cookie ID is used */
1574                         PS(send_cookie) = PS(use_cookies) || PS(use_only_cookies);
1575         }
1576 
1577         lensess = strlen(PS(session_name));
1578 
1579         /*
1580          * Cookies are preferred, because initially cookie and get
1581          * variables will be available.
1582          * URL/POST session ID may be used when use_only_cookies=Off.
1583          * session.use_strice_mode=On prevents session adoption.
1584          * Session based file upload progress uses non-cookie ID.
1585          */
1586 
1587         if (!PS(id)) {
1588                 if (PS(use_cookies) && (data = zend_hash_str_find(&EG(symbol_table), "_COOKIE", sizeof("_COOKIE") - 1))) {
1589                         ZVAL_DEREF(data);
1590                         if (Z_TYPE_P(data) == IS_ARRAY && (ppid = zend_hash_str_find(Z_ARRVAL_P(data), PS(session_name), lensess))) {
1591                                 ppid2sid(ppid);
1592                                 PS(send_cookie) = 0;
1593                         }
1594                 }
1595 
1596                 if (PS(define_sid) && !PS(id) && (data = zend_hash_str_find(&EG(symbol_table), "_GET", sizeof("_GET") - 1))) {
1597                         ZVAL_DEREF(data);
1598                         if (Z_TYPE_P(data) == IS_ARRAY && (ppid = zend_hash_str_find(Z_ARRVAL_P(data), PS(session_name), lensess))) {
1599                                 ppid2sid(ppid);
1600                         }
1601                 }
1602 
1603                 if (PS(define_sid) && !PS(id) && (data = zend_hash_str_find(&EG(symbol_table), "_POST", sizeof("_POST") - 1))) {
1604                         ZVAL_DEREF(data);
1605                         if (Z_TYPE_P(data) == IS_ARRAY && (ppid = zend_hash_str_find(Z_ARRVAL_P(data), PS(session_name), lensess))) {
1606                                 ppid2sid(ppid);
1607                         }
1608                 }
1609 
1610                 /* Check the REQUEST_URI symbol for a string of the form
1611                  * '<session-name>=<session-id>' to allow URLs of the form
1612                  * http://yoursite/<session-name>=<session-id>/script.php */
1613                 if (PS(define_sid) && !PS(id) &&
1614                         zend_is_auto_global_str("_SERVER", sizeof("_SERVER") - 1) == SUCCESS &&
1615                         (data = zend_hash_str_find(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]), "REQUEST_URI", sizeof("REQUEST_URI") - 1)) &&
1616                         Z_TYPE_P(data) == IS_STRING &&
1617                         (p = strstr(Z_STRVAL_P(data), PS(session_name))) &&
1618                         p[lensess] == '='
1619                 ) {
1620                         char *q;
1621                         p += lensess + 1;
1622                         if ((q = strpbrk(p, "/?\\"))) {
1623                                 PS(id) = zend_string_init(p, q - p, 0);
1624                         }
1625                 }
1626 
1627                 /* Check whether the current request was referred to by
1628                  * an external site which invalidates the previously found id. */
1629                 if (PS(define_sid) && PS(id) &&
1630                         PS(extern_referer_chk)[0] != '\0' &&
1631                         !Z_ISUNDEF(PG(http_globals)[TRACK_VARS_SERVER]) &&
1632                         (data = zend_hash_str_find(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]), "HTTP_REFERER", sizeof("HTTP_REFERER") - 1)) &&
1633                         Z_TYPE_P(data) == IS_STRING &&
1634                         Z_STRLEN_P(data) != 0 &&
1635                         strstr(Z_STRVAL_P(data), PS(extern_referer_chk)) == NULL
1636                 ) {
1637                         zend_string_release(PS(id));
1638                         PS(id) = NULL;
1639                 }
1640         }
1641 
1642         /* Finally check session id for dangerous characters
1643          * Security note: session id may be embedded in HTML pages.*/
1644         if (PS(id) && strpbrk(ZSTR_VAL(PS(id)), "\r\n\t <>'\"\\")) {
1645                 zend_string_release(PS(id));
1646                 PS(id) = NULL;
1647         }
1648 
1649         php_session_initialize();
1650         php_session_cache_limiter();
1651 }
1652 /* }}} */
1653 
1654 static void php_session_flush(int write) /* {{{ */
1655 {
1656         if (PS(session_status) == php_session_active) {
1657                 PS(session_status) = php_session_none;
1658                 php_session_save_current_state(write);
1659         }
1660 }
1661 /* }}} */
1662 
1663 static void php_session_abort(void) /* {{{ */
1664 {
1665         if (PS(session_status) == php_session_active) {
1666                 PS(session_status) = php_session_none;
1667                 if (PS(mod_data) || PS(mod_user_implemented)) {
1668                         PS(mod)->s_close(&PS(mod_data));
1669                 }
1670         }
1671 }
1672 /* }}} */
1673 
1674 static void php_session_reset(void) /* {{{ */
1675 {
1676         if (PS(session_status) == php_session_active) {
1677                 php_session_initialize();
1678         }
1679 }
1680 /* }}} */
1681 
1682 
1683 /* This API is not used by any PHP modules including session currently.
1684    session_adapt_url() may be used to set Session ID to target url without
1685    starting "URL-Rewriter" output handler. */
1686 PHPAPI void session_adapt_url(const char *url, size_t urllen, char **new, size_t *newlen) /* {{{ */
1687 {
1688         if (APPLY_TRANS_SID && (PS(session_status) == php_session_active)) {
1689                 *new = php_url_scanner_adapt_single_url(url, urllen, PS(session_name), ZSTR_VAL(PS(id)), newlen, 1);
1690         }
1691 }
1692 /* }}} */
1693 
1694 /* ********************************
1695    * Userspace exported functions *
1696    ******************************** */
1697 
1698 /* {{{ proto void session_set_cookie_params(int lifetime [, string path [, string domain [, bool secure[, bool httponly]]]])
1699    Set session cookie parameters */
1700 static PHP_FUNCTION(session_set_cookie_params)
1701 {
1702         zval *lifetime;
1703         zend_string *path = NULL, *domain = NULL;
1704         int argc = ZEND_NUM_ARGS();
1705         zend_bool secure = 0, httponly = 0;
1706         zend_string *ini_name;
1707 
1708         if (!PS(use_cookies) ||
1709                 zend_parse_parameters(argc, "z|SSbb", &lifetime, &path, &domain, &secure, &httponly) == FAILURE) {
1710                 return;
1711         }
1712 
1713         convert_to_string_ex(lifetime);
1714 
1715         ini_name = zend_string_init("session.cookie_lifetime", sizeof("session.cookie_lifetime") - 1, 0);
1716         zend_alter_ini_entry(ini_name,  Z_STR_P(lifetime), PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
1717         zend_string_release(ini_name);
1718 
1719         if (path) {
1720                 ini_name = zend_string_init("session.cookie_path", sizeof("session.cookie_path") - 1, 0);
1721                 zend_alter_ini_entry(ini_name, path, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
1722                 zend_string_release(ini_name);
1723         }
1724         if (domain) {
1725                 ini_name = zend_string_init("session.cookie_domain", sizeof("session.cookie_domain") - 1, 0);
1726                 zend_alter_ini_entry(ini_name, domain, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
1727                 zend_string_release(ini_name);
1728         }
1729 
1730         if (argc > 3) {
1731                 ini_name = zend_string_init("session.cookie_secure", sizeof("session.cookie_secure") - 1, 0);
1732                 zend_alter_ini_entry_chars(ini_name, secure ? "1" : "0", 1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
1733                 zend_string_release(ini_name);
1734         }
1735         if (argc > 4) {
1736                 ini_name = zend_string_init("session.cookie_httponly", sizeof("session.cookie_httponly") - 1, 0);
1737                 zend_alter_ini_entry_chars(ini_name, httponly ? "1" : "0", 1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
1738                 zend_string_release(ini_name);
1739         }
1740 }
1741 /* }}} */
1742 
1743 /* {{{ proto array session_get_cookie_params(void)
1744    Return the session cookie parameters */
1745 static PHP_FUNCTION(session_get_cookie_params)
1746 {
1747         if (zend_parse_parameters_none() == FAILURE) {
1748                 return;
1749         }
1750 
1751         array_init(return_value);
1752 
1753         add_assoc_long(return_value, "lifetime", PS(cookie_lifetime));
1754         add_assoc_string(return_value, "path", PS(cookie_path));
1755         add_assoc_string(return_value, "domain", PS(cookie_domain));
1756         add_assoc_bool(return_value, "secure", PS(cookie_secure));
1757         add_assoc_bool(return_value, "httponly", PS(cookie_httponly));
1758 }
1759 /* }}} */
1760 
1761 /* {{{ proto string session_name([string newname])
1762    Return the current session name. If newname is given, the session name is replaced with newname */
1763 static PHP_FUNCTION(session_name)
1764 {
1765         zend_string *name = NULL;
1766         zend_string *ini_name;
1767 
1768         if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &name) == FAILURE) {
1769                 return;
1770         }
1771 
1772         RETVAL_STRING(PS(session_name));
1773 
1774         if (name) {
1775                 ini_name = zend_string_init("session.name", sizeof("session.name") - 1, 0);
1776                 zend_alter_ini_entry(ini_name, name, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
1777                 zend_string_release(ini_name);
1778         }
1779 }
1780 /* }}} */
1781 
1782 /* {{{ proto string session_module_name([string newname])
1783    Return the current module name used for accessing session data. If newname is given, the module name is replaced with newname */
1784 static PHP_FUNCTION(session_module_name)
1785 {
1786         zend_string *name = NULL;
1787         zend_string *ini_name;
1788 
1789         if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &name) == FAILURE) {
1790                 return;
1791         }
1792 
1793         /* Set return_value to current module name */
1794         if (PS(mod) && PS(mod)->s_name) {
1795                 RETVAL_STRING(PS(mod)->s_name);
1796         } else {
1797                 RETVAL_EMPTY_STRING();
1798         }
1799 
1800         if (name) {
1801                 if (!_php_find_ps_module(ZSTR_VAL(name))) {
1802                         php_error_docref(NULL, E_WARNING, "Cannot find named PHP session module (%s)", ZSTR_VAL(name));
1803 
1804                         zval_dtor(return_value);
1805                         RETURN_FALSE;
1806                 }
1807                 if (PS(mod_data) || PS(mod_user_implemented)) {
1808                         PS(mod)->s_close(&PS(mod_data));
1809                 }
1810                 PS(mod_data) = NULL;
1811 
1812                 ini_name = zend_string_init("session.save_handler", sizeof("session.save_handler") - 1, 0);
1813                 zend_alter_ini_entry(ini_name, name, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
1814                 zend_string_release(ini_name);
1815         }
1816 }
1817 /* }}} */
1818 
1819 /* {{{ proto void session_set_save_handler(string open, string close, string read, string write, string destroy, string gc, string create_sid)
1820    Sets user-level functions */
1821 static PHP_FUNCTION(session_set_save_handler)
1822 {
1823         zval *args = NULL;
1824         int i, num_args, argc = ZEND_NUM_ARGS();
1825         zend_string *name;
1826         zend_string *ini_name, *ini_val;
1827 
1828         if (PS(session_status) != php_session_none) {
1829                 RETURN_FALSE;
1830         }
1831 
1832         if (argc > 0 && argc <= 2) {
1833                 zval *obj = NULL;
1834                 zend_string *func_name;
1835                 zend_function *current_mptr;
1836                 zend_bool register_shutdown = 1;
1837 
1838                 if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|b", &obj, php_session_iface_entry, &register_shutdown) == FAILURE) {
1839                         RETURN_FALSE;
1840                 }
1841 
1842                 /* For compatibility reason, implemeted interface is not checked */
1843                 /* Find implemented methods - SessionHandlerInterface */
1844                 i = 0;
1845                 ZEND_HASH_FOREACH_STR_KEY(&php_session_iface_entry->function_table, func_name) {
1846                         if ((current_mptr = zend_hash_find_ptr(&Z_OBJCE_P(obj)->function_table, func_name))) {
1847                                 if (!Z_ISUNDEF(PS(mod_user_names).names[i])) {
1848                                         zval_ptr_dtor(&PS(mod_user_names).names[i]);
1849                                 }
1850 
1851                                 array_init_size(&PS(mod_user_names).names[i], 2);
1852                                 Z_ADDREF_P(obj);
1853                                 add_next_index_zval(&PS(mod_user_names).names[i], obj);
1854                                 add_next_index_str(&PS(mod_user_names).names[i], zend_string_copy(func_name));
1855                         } else {
1856                                 php_error_docref(NULL, E_ERROR, "Session handler's function table is corrupt");
1857                                 RETURN_FALSE;
1858                         }
1859 
1860                         ++i;
1861                 } ZEND_HASH_FOREACH_END();
1862 
1863                 /* Find implemented methods - SessionIdInterface (optional) */
1864                 ZEND_HASH_FOREACH_STR_KEY(&php_session_id_iface_entry->function_table, func_name) {
1865                         if ((current_mptr = zend_hash_find_ptr(&Z_OBJCE_P(obj)->function_table, func_name))) {
1866                                 if (!Z_ISUNDEF(PS(mod_user_names).names[i])) {
1867                                         zval_ptr_dtor(&PS(mod_user_names).names[i]);
1868                                 }
1869                                 array_init_size(&PS(mod_user_names).names[i], 2);
1870                                 Z_ADDREF_P(obj);
1871                                 add_next_index_zval(&PS(mod_user_names).names[i], obj);
1872                                 add_next_index_str(&PS(mod_user_names).names[i], zend_string_copy(func_name));
1873                         } else {
1874                                 if (!Z_ISUNDEF(PS(mod_user_names).names[i])) {
1875                                         zval_ptr_dtor(&PS(mod_user_names).names[i]);
1876                                         ZVAL_UNDEF(&PS(mod_user_names).names[i]);
1877                                 }
1878                         }
1879 
1880                         ++i;
1881                 } ZEND_HASH_FOREACH_END();
1882 
1883                 /* Find implemented methods - SessionUpdateTimestampInterface (optional) */
1884                 ZEND_HASH_FOREACH_STR_KEY(&php_session_update_timestamp_iface_entry->function_table, func_name) {
1885                         if ((current_mptr = zend_hash_find_ptr(&Z_OBJCE_P(obj)->function_table, func_name))) {
1886                                 if (!Z_ISUNDEF(PS(mod_user_names).names[i])) {
1887                                         zval_ptr_dtor(&PS(mod_user_names).names[i]);
1888                                 }
1889                                 array_init_size(&PS(mod_user_names).names[i], 2);
1890                                 Z_ADDREF_P(obj);
1891                                 add_next_index_zval(&PS(mod_user_names).names[i], obj);
1892                                 add_next_index_str(&PS(mod_user_names).names[i], zend_string_copy(func_name));
1893                         } else {
1894                                 if (!Z_ISUNDEF(PS(mod_user_names).names[i])) {
1895                                         zval_ptr_dtor(&PS(mod_user_names).names[i]);
1896                                         ZVAL_UNDEF(&PS(mod_user_names).names[i]);
1897                                 }
1898                         }
1899                         ++i;
1900                 } ZEND_HASH_FOREACH_END();
1901 
1902                 if (register_shutdown) {
1903                         /* create shutdown function */
1904                         php_shutdown_function_entry shutdown_function_entry;
1905                         shutdown_function_entry.arg_count = 1;
1906                         shutdown_function_entry.arguments = (zval *) safe_emalloc(sizeof(zval), 1, 0);
1907 
1908                         ZVAL_STRING(&shutdown_function_entry.arguments[0], "session_register_shutdown");
1909 
1910                         /* add shutdown function, removing the old one if it exists */
1911                         if (!register_user_shutdown_function("session_shutdown", sizeof("session_shutdown") - 1, &shutdown_function_entry)) {
1912                                 zval_ptr_dtor(&shutdown_function_entry.arguments[0]);
1913                                 efree(shutdown_function_entry.arguments);
1914                                 php_error_docref(NULL, E_WARNING, "Unable to register session shutdown function");
1915                                 RETURN_FALSE;
1916                         }
1917                 } else {
1918                         /* remove shutdown function */
1919                         remove_user_shutdown_function("session_shutdown", sizeof("session_shutdown") - 1);
1920                 }
1921 
1922                 if (PS(mod) && PS(session_status) != php_session_active && PS(mod) != &ps_mod_user) {
1923                         ini_name = zend_string_init("session.save_handler", sizeof("session.save_handler") - 1, 0);
1924                         ini_val = zend_string_init("user", sizeof("user") - 1, 0);
1925                         zend_alter_ini_entry(ini_name, ini_val, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
1926                         zend_string_release(ini_val);
1927                         zend_string_release(ini_name);
1928                 }
1929 
1930                 RETURN_TRUE;
1931         }
1932 
1933         /* Set procedural save handler functions */
1934         if (argc < 6 || PS_NUM_APIS < argc) {
1935                 WRONG_PARAM_COUNT;
1936         }
1937 
1938         if (zend_parse_parameters(argc, "+", &args, &num_args) == FAILURE) {
1939                 return;
1940         }
1941 
1942         /* remove shutdown function */
1943         remove_user_shutdown_function("session_shutdown", sizeof("session_shutdown") - 1);
1944 
1945         /* At this point argc can only be between 6 and PS_NUM_APIS */
1946         for (i = 0; i < argc; i++) {
1947                 if (!zend_is_callable(&args[i], 0, &name)) {
1948                         php_error_docref(NULL, E_WARNING, "Argument %d is not a valid callback", i+1);
1949                         zend_string_release(name);
1950                         RETURN_FALSE;
1951                 }
1952                 zend_string_release(name);
1953         }
1954 
1955         if (PS(mod) && PS(mod) != &ps_mod_user) {
1956                 ini_name = zend_string_init("session.save_handler", sizeof("session.save_handler") - 1, 0);
1957                 ini_val = zend_string_init("user", sizeof("user") - 1, 0);
1958                 zend_alter_ini_entry(ini_name, ini_val, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
1959                 zend_string_release(ini_val);
1960                 zend_string_release(ini_name);
1961         }
1962 
1963         for (i = 0; i < argc; i++) {
1964                 if (!Z_ISUNDEF(PS(mod_user_names).names[i])) {
1965                         zval_ptr_dtor(&PS(mod_user_names).names[i]);
1966                 }
1967                 ZVAL_COPY(&PS(mod_user_names).names[i], &args[i]);
1968         }
1969 
1970         RETURN_TRUE;
1971 }
1972 /* }}} */
1973 
1974 /* {{{ proto string session_save_path([string newname])
1975    Return the current save path passed to module_name. If newname is given, the save path is replaced with newname */
1976 static PHP_FUNCTION(session_save_path)
1977 {
1978         zend_string *name = NULL;
1979         zend_string *ini_name;
1980 
1981         if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &name) == FAILURE) {
1982                 return;
1983         }
1984 
1985         RETVAL_STRING(PS(save_path));
1986 
1987         if (name) {
1988                 if (memchr(ZSTR_VAL(name), '\0', ZSTR_LEN(name)) != NULL) {
1989                         php_error_docref(NULL, E_WARNING, "The save_path cannot contain NULL characters");
1990                         zval_dtor(return_value);
1991                         RETURN_FALSE;
1992                 }
1993                 ini_name = zend_string_init("session.save_path", sizeof("session.save_path") - 1, 0);
1994                 zend_alter_ini_entry(ini_name, name, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
1995                 zend_string_release(ini_name);
1996         }
1997 }
1998 /* }}} */
1999 
2000 /* {{{ proto string session_id([string newid])
2001    Return the current session id. If newid is given, the session id is replaced with newid */
2002 static PHP_FUNCTION(session_id)
2003 {
2004         zend_string *name = NULL;
2005         int argc = ZEND_NUM_ARGS();
2006 
2007         if (zend_parse_parameters(argc, "|S", &name) == FAILURE) {
2008                 return;
2009         }
2010 
2011         if (PS(id)) {
2012                 /* keep compatibility for "\0" characters ???
2013                  * see: ext/session/tests/session_id_error3.phpt */
2014                 size_t len = strlen(ZSTR_VAL(PS(id)));
2015                 if (UNEXPECTED(len != ZSTR_LEN(PS(id)))) {
2016                         RETVAL_NEW_STR(zend_string_init(ZSTR_VAL(PS(id)), len, 0));
2017                 } else {
2018                         RETVAL_STR_COPY(PS(id));
2019                 }
2020         } else {
2021                 RETVAL_EMPTY_STRING();
2022         }
2023 
2024         if (name) {
2025                 if (PS(id)) {
2026                         zend_string_release(PS(id));
2027                 }
2028                 PS(id) = zend_string_copy(name);
2029         }
2030 }
2031 /* }}} */
2032 
2033 /* {{{ proto bool session_regenerate_id([bool delete_old_session])
2034    Update the current session id with a newly generated one. If delete_old_session is set to true, remove the old session. */
2035 static PHP_FUNCTION(session_regenerate_id)
2036 {
2037         zend_bool del_ses = 0;
2038         zend_string *data;
2039 
2040         if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &del_ses) == FAILURE) {
2041                 return;
2042         }
2043 
2044         if (SG(headers_sent) && PS(use_cookies)) {
2045                 php_error_docref(NULL, E_WARNING, "Cannot regenerate session id - headers already sent");
2046                 RETURN_FALSE;
2047         }
2048 
2049         if (PS(session_status) != php_session_active) {
2050                 php_error_docref(NULL, E_WARNING, "Cannot regenerate session id - session is not active");
2051                 RETURN_FALSE;
2052         }
2053 
2054         /* Process old session data */
2055         if (del_ses) {
2056                 if (PS(mod)->s_destroy(&PS(mod_data), PS(id)) == FAILURE) {
2057                         PS(mod)->s_close(&PS(mod_data));
2058                         PS(session_status) = php_session_none;
2059                         php_error_docref(NULL, E_WARNING, "Session object destruction failed.  ID: %s (path: %s)", PS(mod)->s_name, PS(save_path));
2060                         RETURN_FALSE;
2061                 }
2062         } else {
2063                 int ret;
2064                 data = php_session_encode();
2065                 if (data) {
2066                         ret = PS(mod)->s_write(&PS(mod_data), PS(id), data, PS(gc_maxlifetime));
2067                         zend_string_release(data);
2068                 } else {
2069                         ret = PS(mod)->s_write(&PS(mod_data), PS(id), ZSTR_EMPTY_ALLOC(), PS(gc_maxlifetime));
2070                 }
2071                 if (ret == FAILURE) {
2072                         PS(mod)->s_close(&PS(mod_data));
2073                         PS(session_status) = php_session_none;
2074                         php_error_docref(NULL, E_WARNING, "Session write failed. ID: %s (path: %s)", PS(mod)->s_name, PS(save_path));
2075                         RETURN_FALSE;
2076                 }
2077         }
2078         PS(mod)->s_close(&PS(mod_data));
2079 
2080         /* New session data */
2081         if (PS(session_vars)) {
2082                 zend_string_release(PS(session_vars));
2083                 PS(session_vars) = NULL;
2084         }
2085         zend_string_release(PS(id));
2086         PS(id) = PS(mod)->s_create_sid(&PS(mod_data));
2087         if (!PS(id)) {
2088                 PS(session_status) = php_session_none;
2089                 php_error_docref(NULL, E_RECOVERABLE_ERROR, "Failed to create new session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path));
2090                 RETURN_FALSE;
2091         }
2092         if (PS(mod)->s_open(&PS(mod_data), PS(save_path), PS(session_name)) == FAILURE) {
2093                 PS(session_status) = php_session_none;
2094                 php_error_docref(NULL, E_RECOVERABLE_ERROR, "Failed to create(open) session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path));
2095                 RETURN_FALSE;
2096         }
2097         if (PS(use_strict_mode) && PS(mod)->s_validate_sid &&
2098                 PS(mod)->s_validate_sid(&PS(mod_data), PS(id)) == FAILURE) {
2099                 zend_string_release(PS(id));
2100                 PS(id) = PS(mod)->s_create_sid(&PS(mod_data));
2101                 if (!PS(id)) {
2102                         PS(session_status) = php_session_none;
2103                         php_error_docref(NULL, E_RECOVERABLE_ERROR, "Failed to create session ID by collision: %s (path: %s)", PS(mod)->s_name, PS(save_path));
2104                         RETURN_FALSE;
2105                 }
2106         }
2107         /* Read is required to make new session data at this point. */
2108         if (PS(mod)->s_read(&PS(mod_data), PS(id), &data, PS(gc_maxlifetime)) == FAILURE) {
2109                 PS(session_status) = php_session_none;
2110                 php_error_docref(NULL, E_RECOVERABLE_ERROR, "Failed to create(read) session ID: %s (path: %s)", PS(mod)->s_name, PS(save_path));
2111                 RETURN_FALSE;
2112         }
2113         if (data) {
2114                 zend_string_release(data);
2115         }
2116 
2117         if (PS(use_cookies)) {
2118                 PS(send_cookie) = 1;
2119         }
2120         php_session_reset_id();
2121 
2122         RETURN_TRUE;
2123 }
2124 /* }}} */
2125 
2126 /* {{{ proto void session_create_id([string prefix])
2127    Generate new session ID. Intended for user save handlers. */
2128 #if 0
2129 /* This is not used yet */
2130 static PHP_FUNCTION(session_create_id)
2131 {
2132         zend_string *prefix = NULL, *new_id;
2133         smart_str id = {0};
2134 
2135         if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &prefix) == FAILURE) {
2136                 return;
2137         }
2138 
2139         if (prefix && ZSTR_LEN(prefix)) {
2140                 if (php_session_valid_key(ZSTR_VAL(prefix)) == FAILURE) {
2141                         /* E_ERROR raised for security reason. */
2142                         php_error_docref(NULL, E_WARNING, "Prefix cannot contain special characters. Only aphanumeric, ',', '-' are allowed");
2143                         RETURN_FALSE;
2144                 } else {
2145                         smart_str_append(&id, prefix);
2146                 }
2147         }
2148 
2149         if (PS(session_status) == php_session_active) {
2150                 new_id = PS(mod)->s_create_sid(&PS(mod_data));
2151         } else {
2152                 new_id = php_session_create_id(NULL);
2153         }
2154 
2155         if (new_id) {
2156                 smart_str_append(&id, new_id);
2157                 zend_string_release(new_id);
2158         } else {
2159                 smart_str_free(&id);
2160                 php_error_docref(NULL, E_WARNING, "Failed to create new ID");
2161                 RETURN_FALSE;
2162         }
2163         smart_str_0(&id);
2164         RETVAL_NEW_STR(id.s);
2165         smart_str_free(&id);
2166 }
2167 #endif
2168 /* }}} */
2169 
2170 /* {{{ proto string session_cache_limiter([string new_cache_limiter])
2171    Return the current cache limiter. If new_cache_limited is given, the current cache_limiter is replaced with new_cache_limiter */
2172 static PHP_FUNCTION(session_cache_limiter)
2173 {
2174         zend_string *limiter = NULL;
2175         zend_string *ini_name;
2176 
2177         if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &limiter) == FAILURE) {
2178                 return;
2179         }
2180 
2181         RETVAL_STRING(PS(cache_limiter));
2182 
2183         if (limiter) {
2184                 ini_name = zend_string_init("session.cache_limiter", sizeof("session.cache_limiter") - 1, 0);
2185                 zend_alter_ini_entry(ini_name, limiter, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
2186                 zend_string_release(ini_name);
2187         }
2188 }
2189 /* }}} */
2190 
2191 /* {{{ proto int session_cache_expire([int new_cache_expire])
2192    Return the current cache expire. If new_cache_expire is given, the current cache_expire is replaced with new_cache_expire */
2193 static PHP_FUNCTION(session_cache_expire)
2194 {
2195         zval *expires = NULL;
2196         zend_string *ini_name;
2197 
2198         if (zend_parse_parameters(ZEND_NUM_ARGS(), "|z", &expires) == FAILURE) {
2199                 return;
2200         }
2201 
2202         RETVAL_LONG(PS(cache_expire));
2203 
2204         if (expires) {
2205                 convert_to_string_ex(expires);
2206                 ini_name = zend_string_init("session.cache_expire", sizeof("session.cache_expire") - 1, 0);
2207                 zend_alter_ini_entry(ini_name, Z_STR_P(expires), ZEND_INI_USER, ZEND_INI_STAGE_RUNTIME);
2208                 zend_string_release(ini_name);
2209         }
2210 }
2211 /* }}} */
2212 
2213 /* {{{ proto string session_encode(void)
2214    Serializes the current setup and returns the serialized representation */
2215 static PHP_FUNCTION(session_encode)
2216 {
2217         zend_string *enc;
2218 
2219         if (zend_parse_parameters_none() == FAILURE) {
2220                 return;
2221         }
2222 
2223         enc = php_session_encode();
2224         if (enc == NULL) {
2225                 RETURN_FALSE;
2226         }
2227 
2228         RETURN_STR(enc);
2229 }
2230 /* }}} */
2231 
2232 /* {{{ proto bool session_decode(string data)
2233    Deserializes data and reinitializes the variables */
2234 static PHP_FUNCTION(session_decode)
2235 {
2236         zend_string *str = NULL;
2237 
2238         if (PS(session_status) != php_session_active) {
2239                 php_error_docref(NULL, E_WARNING, "Session is not active. You cannot decode session data");
2240                 RETURN_FALSE;
2241         }
2242 
2243         if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) {
2244                 return;
2245         }
2246 
2247         if (php_session_decode(str) == FAILURE) {
2248                 RETURN_FALSE;
2249         }
2250         RETURN_TRUE;
2251 }
2252 /* }}} */
2253 
2254 static int php_session_start_set_ini(zend_string *varname, zend_string *new_value) {
2255         int ret;
2256         smart_str buf ={0};
2257         smart_str_appends(&buf, "session");
2258         smart_str_appendc(&buf, '.');
2259         smart_str_append(&buf, varname);
2260         smart_str_0(&buf);
2261         ret = zend_alter_ini_entry_ex(buf.s, new_value, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0);
2262         smart_str_free(&buf);
2263         return ret;
2264 }
2265 
2266 /* {{{ proto bool session_start([array options])
2267 +   Begin session */
2268 static PHP_FUNCTION(session_start)
2269 {
2270         zval *options = NULL;
2271         zval *value;
2272         zend_ulong num_idx;
2273         zend_string *str_idx;
2274         zend_long read_and_close = 0;
2275 
2276         if (zend_parse_parameters(ZEND_NUM_ARGS(), "|a", &options) == FAILURE) {
2277                 RETURN_FALSE;
2278         }
2279 
2280         if (PS(id) && !(ZSTR_LEN(PS(id)))) {
2281                 php_error_docref(NULL, E_WARNING, "Cannot start session with empty session ID");
2282                 RETURN_FALSE;
2283         }
2284 
2285         /* set options */
2286         if (options) {
2287                 ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(options), num_idx, str_idx, value) {
2288                         if (str_idx) {
2289                                 switch(Z_TYPE_P(value)) {
2290                                         case IS_STRING:
2291                                         case IS_TRUE:
2292                                         case IS_FALSE:
2293                                         case IS_LONG:
2294                                                 if (zend_string_equals_literal(str_idx, "read_and_close")) {
2295                                                         read_and_close = zval_get_long(value);
2296                                                 } else {
2297                                                         zend_string *val = zval_get_string(value);
2298                                                         if (php_session_start_set_ini(str_idx, val) == FAILURE) {
2299                                                                 php_error_docref(NULL, E_WARNING, "Setting option '%s' failed", ZSTR_VAL(str_idx));
2300                                                         }
2301                                                         zend_string_release(val);
2302                                                 }
2303                                                 break;
2304                                         default:
2305                                                 php_error_docref(NULL, E_WARNING, "Option(%s) value must be string, boolean or long", ZSTR_VAL(str_idx));
2306                                                 break;
2307                                 }
2308                         }
2309                         (void) num_idx;
2310                 } ZEND_HASH_FOREACH_END();
2311         }
2312 
2313         php_session_start();
2314 
2315         if (PS(session_status) != php_session_active) {
2316                 RETURN_FALSE;
2317         }
2318 
2319         if (read_and_close) {
2320                 php_session_flush(0);
2321         }
2322 
2323         RETURN_TRUE;
2324 }
2325 /* }}} */
2326 
2327 /* {{{ proto bool session_destroy(void)
2328    Destroy the current session and all data associated with it */
2329 static PHP_FUNCTION(session_destroy)
2330 {
2331         if (zend_parse_parameters_none() == FAILURE) {
2332                 return;
2333         }
2334 
2335         RETURN_BOOL(php_session_destroy() == SUCCESS);
2336 }
2337 /* }}} */
2338 
2339 /* {{{ proto void session_unset(void)
2340    Unset all registered variables */
2341 static PHP_FUNCTION(session_unset)
2342 {
2343         if (PS(session_status) != php_session_active) {
2344                 RETURN_FALSE;
2345         }
2346 
2347         IF_SESSION_VARS() {
2348                 HashTable *ht_sess_var = Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars)));
2349 
2350                 /* Clean $_SESSION. */
2351                 zend_hash_clean(ht_sess_var);
2352         }
2353 }
2354 /* }}} */
2355 
2356 /* {{{ proto void session_write_close(void)
2357    Write session data and end session */
2358 static PHP_FUNCTION(session_write_close)
2359 {
2360         php_session_flush(1);
2361 }
2362 /* }}} */
2363 
2364 /* {{{ proto void session_abort(void)
2365    Abort session and end session. Session data will not be written */
2366 static PHP_FUNCTION(session_abort)
2367 {
2368         php_session_abort();
2369 }
2370 /* }}} */
2371 
2372 /* {{{ proto void session_reset(void)
2373    Reset session data from saved session data */
2374 static PHP_FUNCTION(session_reset)
2375 {
2376         php_session_reset();
2377 }
2378 /* }}} */
2379 
2380 /* {{{ proto int session_status(void)
2381    Returns the current session status */
2382 static PHP_FUNCTION(session_status)
2383 {
2384         if (zend_parse_parameters_none() == FAILURE) {
2385                 return;
2386         }
2387 
2388         RETURN_LONG(PS(session_status));
2389 }
2390 /* }}} */
2391 
2392 /* {{{ proto void session_register_shutdown(void)
2393    Registers session_write_close() as a shutdown function */
2394 static PHP_FUNCTION(session_register_shutdown)
2395 {
2396         php_shutdown_function_entry shutdown_function_entry;
2397 
2398         /* This function is registered itself as a shutdown function by
2399          * session_set_save_handler($obj). The reason we now register another
2400          * shutdown function is in case the user registered their own shutdown
2401          * function after calling session_set_save_handler(), which expects
2402          * the session still to be available.
2403          */
2404 
2405         shutdown_function_entry.arg_count = 1;
2406         shutdown_function_entry.arguments = (zval *) safe_emalloc(sizeof(zval), 1, 0);
2407 
2408         ZVAL_STRING(&shutdown_function_entry.arguments[0], "session_write_close");
2409 
2410         if (!append_user_shutdown_function(shutdown_function_entry)) {
2411                 zval_ptr_dtor(&shutdown_function_entry.arguments[0]);
2412                 efree(shutdown_function_entry.arguments);
2413 
2414                 /* Unable to register shutdown function, presumably because of lack
2415                  * of memory, so flush the session now. It would be done in rshutdown
2416                  * anyway but the handler will have had it's dtor called by then.
2417                  * If the user does have a later shutdown function which needs the
2418                  * session then tough luck.
2419                  */
2420                 php_session_flush(1);
2421                 php_error_docref(NULL, E_WARNING, "Unable to register session flush function");
2422         }
2423 }
2424 /* }}} */
2425 
2426 /* {{{ arginfo */
2427 ZEND_BEGIN_ARG_INFO_EX(arginfo_session_name, 0, 0, 0)
2428         ZEND_ARG_INFO(0, name)
2429 ZEND_END_ARG_INFO()
2430 
2431 ZEND_BEGIN_ARG_INFO_EX(arginfo_session_module_name, 0, 0, 0)
2432         ZEND_ARG_INFO(0, module)
2433 ZEND_END_ARG_INFO()
2434 
2435 ZEND_BEGIN_ARG_INFO_EX(arginfo_session_save_path, 0, 0, 0)
2436         ZEND_ARG_INFO(0, path)
2437 ZEND_END_ARG_INFO()
2438 
2439 ZEND_BEGIN_ARG_INFO_EX(arginfo_session_id, 0, 0, 0)
2440         ZEND_ARG_INFO(0, id)
2441 ZEND_END_ARG_INFO()
2442 
2443 ZEND_BEGIN_ARG_INFO_EX(arginfo_session_regenerate_id, 0, 0, 0)
2444         ZEND_ARG_INFO(0, delete_old_session)
2445 ZEND_END_ARG_INFO()
2446 
2447 ZEND_BEGIN_ARG_INFO_EX(arginfo_session_decode, 0, 0, 1)
2448         ZEND_ARG_INFO(0, data)
2449 ZEND_END_ARG_INFO()
2450 
2451 ZEND_BEGIN_ARG_INFO(arginfo_session_void, 0)
2452 ZEND_END_ARG_INFO()
2453 
2454 ZEND_BEGIN_ARG_INFO_EX(arginfo_session_set_save_handler, 0, 0, 1)
2455         ZEND_ARG_INFO(0, open)
2456         ZEND_ARG_INFO(0, close)
2457         ZEND_ARG_INFO(0, read)
2458         ZEND_ARG_INFO(0, write)
2459         ZEND_ARG_INFO(0, destroy)
2460         ZEND_ARG_INFO(0, gc)
2461         ZEND_ARG_INFO(0, create_sid)
2462         ZEND_ARG_INFO(0, validate_sid)
2463         ZEND_ARG_INFO(0, update_timestamp)
2464 ZEND_END_ARG_INFO()
2465 
2466 ZEND_BEGIN_ARG_INFO_EX(arginfo_session_cache_limiter, 0, 0, 0)
2467         ZEND_ARG_INFO(0, cache_limiter)
2468 ZEND_END_ARG_INFO()
2469 
2470 ZEND_BEGIN_ARG_INFO_EX(arginfo_session_cache_expire, 0, 0, 0)
2471         ZEND_ARG_INFO(0, new_cache_expire)
2472 ZEND_END_ARG_INFO()
2473 
2474 ZEND_BEGIN_ARG_INFO_EX(arginfo_session_set_cookie_params, 0, 0, 1)
2475         ZEND_ARG_INFO(0, lifetime)
2476         ZEND_ARG_INFO(0, path)
2477         ZEND_ARG_INFO(0, domain)
2478         ZEND_ARG_INFO(0, secure)
2479         ZEND_ARG_INFO(0, httponly)
2480 ZEND_END_ARG_INFO()
2481 
2482 ZEND_BEGIN_ARG_INFO(arginfo_session_class_open, 0)
2483         ZEND_ARG_INFO(0, save_path)
2484         ZEND_ARG_INFO(0, session_name)
2485 ZEND_END_ARG_INFO()
2486 
2487 ZEND_BEGIN_ARG_INFO(arginfo_session_class_close, 0)
2488 ZEND_END_ARG_INFO()
2489 
2490 ZEND_BEGIN_ARG_INFO(arginfo_session_class_read, 0)
2491         ZEND_ARG_INFO(0, key)
2492 ZEND_END_ARG_INFO()
2493 
2494 ZEND_BEGIN_ARG_INFO(arginfo_session_class_write, 0)
2495         ZEND_ARG_INFO(0, key)
2496         ZEND_ARG_INFO(0, val)
2497 ZEND_END_ARG_INFO()
2498 
2499 ZEND_BEGIN_ARG_INFO(arginfo_session_class_destroy, 0)
2500         ZEND_ARG_INFO(0, key)
2501 ZEND_END_ARG_INFO()
2502 
2503 ZEND_BEGIN_ARG_INFO(arginfo_session_class_gc, 0)
2504         ZEND_ARG_INFO(0, maxlifetime)
2505 ZEND_END_ARG_INFO()
2506 
2507 ZEND_BEGIN_ARG_INFO(arginfo_session_class_create_sid, 0)
2508 ZEND_END_ARG_INFO()
2509 
2510 ZEND_BEGIN_ARG_INFO(arginfo_session_class_validateId, 0)
2511         ZEND_ARG_INFO(0, key)
2512 ZEND_END_ARG_INFO()
2513 
2514 ZEND_BEGIN_ARG_INFO(arginfo_session_class_updateTimestamp, 0)
2515         ZEND_ARG_INFO(0, key)
2516         ZEND_ARG_INFO(0, val)
2517 ZEND_END_ARG_INFO()
2518 /* }}} */
2519 
2520 /* {{{ session_functions[]
2521  */
2522 static const zend_function_entry session_functions[] = {
2523         PHP_FE(session_name,              arginfo_session_name)
2524         PHP_FE(session_module_name,       arginfo_session_module_name)
2525         PHP_FE(session_save_path,         arginfo_session_save_path)
2526         PHP_FE(session_id,                arginfo_session_id)
2527         PHP_FE(session_regenerate_id,     arginfo_session_regenerate_id)
2528         PHP_FE(session_decode,            arginfo_session_decode)
2529         PHP_FE(session_encode,            arginfo_session_void)
2530         PHP_FE(session_start,             arginfo_session_void)
2531         PHP_FE(session_destroy,           arginfo_session_void)
2532         PHP_FE(session_unset,             arginfo_session_void)
2533         PHP_FE(session_set_save_handler,  arginfo_session_set_save_handler)
2534         PHP_FE(session_cache_limiter,     arginfo_session_cache_limiter)
2535         PHP_FE(session_cache_expire,      arginfo_session_cache_expire)
2536         PHP_FE(session_set_cookie_params, arginfo_session_set_cookie_params)
2537         PHP_FE(session_get_cookie_params, arginfo_session_void)
2538         PHP_FE(session_write_close,       arginfo_session_void)
2539         PHP_FE(session_abort,             arginfo_session_void)
2540         PHP_FE(session_reset,             arginfo_session_void)
2541         PHP_FE(session_status,            arginfo_session_void)
2542         PHP_FE(session_register_shutdown, arginfo_session_void)
2543         PHP_FALIAS(session_commit, session_write_close, arginfo_session_void)
2544         PHP_FE_END
2545 };
2546 /* }}} */
2547 
2548 /* {{{ SessionHandlerInterface functions[]
2549 */
2550 static const zend_function_entry php_session_iface_functions[] = {
2551         PHP_ABSTRACT_ME(SessionHandlerInterface, open, arginfo_session_class_open)
2552         PHP_ABSTRACT_ME(SessionHandlerInterface, close, arginfo_session_class_close)
2553         PHP_ABSTRACT_ME(SessionHandlerInterface, read, arginfo_session_class_read)
2554         PHP_ABSTRACT_ME(SessionHandlerInterface, write, arginfo_session_class_write)
2555         PHP_ABSTRACT_ME(SessionHandlerInterface, destroy, arginfo_session_class_destroy)
2556         PHP_ABSTRACT_ME(SessionHandlerInterface, gc, arginfo_session_class_gc)
2557         { NULL, NULL, NULL }
2558 };
2559 /* }}} */
2560 
2561 /* {{{ SessionIdInterface functions[]
2562 */
2563 static const zend_function_entry php_session_id_iface_functions[] = {
2564         PHP_ABSTRACT_ME(SessionIdInterface, create_sid, arginfo_session_class_create_sid)
2565         { NULL, NULL, NULL }
2566 };
2567 /* }}} */
2568 
2569 /* {{{ SessionUpdateTimestampHandler functions[]
2570  */
2571 static const zend_function_entry php_session_update_timestamp_iface_functions[] = {
2572         PHP_ABSTRACT_ME(SessionUpdateTimestampHandlerInterface, validateId, arginfo_session_class_validateId)
2573         PHP_ABSTRACT_ME(SessionUpdateTimestampHandlerInterface, updateTimestamp, arginfo_session_class_updateTimestamp)
2574         { NULL, NULL, NULL }
2575 };
2576 /* }}} */
2577 
2578 /* {{{ SessionHandler functions[]
2579  */
2580 static const zend_function_entry php_session_class_functions[] = {
2581         PHP_ME(SessionHandler, open, arginfo_session_class_open, ZEND_ACC_PUBLIC)
2582         PHP_ME(SessionHandler, close, arginfo_session_class_close, ZEND_ACC_PUBLIC)
2583         PHP_ME(SessionHandler, read, arginfo_session_class_read, ZEND_ACC_PUBLIC)
2584         PHP_ME(SessionHandler, write, arginfo_session_class_write, ZEND_ACC_PUBLIC)
2585         PHP_ME(SessionHandler, destroy, arginfo_session_class_destroy, ZEND_ACC_PUBLIC)
2586         PHP_ME(SessionHandler, gc, arginfo_session_class_gc, ZEND_ACC_PUBLIC)
2587         PHP_ME(SessionHandler, create_sid, arginfo_session_class_create_sid, ZEND_ACC_PUBLIC)
2588         { NULL, NULL, NULL }
2589 };
2590 /* }}} */
2591 
2592 /* ********************************
2593    * Module Setup and Destruction *
2594    ******************************** */
2595 
2596 static int php_rinit_session(zend_bool auto_start) /* {{{ */
2597 {
2598         php_rinit_session_globals();
2599 
2600         if (PS(mod) == NULL) {
2601                 char *value;
2602 
2603                 value = zend_ini_string("session.save_handler", sizeof("session.save_handler") - 1, 0);
2604                 if (value) {
2605                         PS(mod) = _php_find_ps_module(value);
2606                 }
2607         }
2608 
2609         if (PS(serializer) == NULL) {
2610                 char *value;
2611 
2612                 value = zend_ini_string("session.serialize_handler", sizeof("session.serialize_handler") - 1, 0);
2613                 if (value) {
2614                         PS(serializer) = _php_find_ps_serializer(value);
2615                 }
2616         }
2617 
2618         if (PS(mod) == NULL || PS(serializer) == NULL) {
2619                 /* current status is unusable */
2620                 PS(session_status) = php_session_disabled;
2621                 return SUCCESS;
2622         }
2623 
2624         if (auto_start) {
2625                 php_session_start();
2626         }
2627 
2628         return SUCCESS;
2629 } /* }}} */
2630 
2631 static PHP_RINIT_FUNCTION(session) /* {{{ */
2632 {
2633         return php_rinit_session(PS(auto_start));
2634 }
2635 /* }}} */
2636 
2637 static PHP_RSHUTDOWN_FUNCTION(session) /* {{{ */
2638 {
2639         int i;
2640 
2641         zend_try {
2642                 php_session_flush(1);
2643         } zend_end_try();
2644         php_rshutdown_session_globals();
2645 
2646         /* this should NOT be done in php_rshutdown_session_globals() */
2647         for (i = 0; i < PS_NUM_APIS; i++) {
2648                 if (!Z_ISUNDEF(PS(mod_user_names).names[i])) {
2649                         zval_ptr_dtor(&PS(mod_user_names).names[i]);
2650                         ZVAL_UNDEF(&PS(mod_user_names).names[i]);
2651                 }
2652         }
2653 
2654         return SUCCESS;
2655 }
2656 /* }}} */
2657 
2658 static PHP_GINIT_FUNCTION(ps) /* {{{ */
2659 {
2660         int i;
2661 
2662 #if defined(COMPILE_DL_SESSION) && defined(ZTS)
2663         ZEND_TSRMLS_CACHE_UPDATE();
2664 #endif
2665 
2666         ps_globals->save_path = NULL;
2667         ps_globals->session_name = NULL;
2668         ps_globals->id = NULL;
2669         ps_globals->mod = NULL;
2670         ps_globals->serializer = NULL;
2671         ps_globals->mod_data = NULL;
2672         ps_globals->session_status = php_session_none;
2673         ps_globals->default_mod = NULL;
2674         ps_globals->mod_user_implemented = 0;
2675         ps_globals->mod_user_is_open = 0;
2676         ps_globals->session_vars = NULL;
2677         for (i = 0; i < PS_NUM_APIS; i++) {
2678                 ZVAL_UNDEF(&ps_globals->mod_user_names.names[i]);
2679         }
2680         ZVAL_UNDEF(&ps_globals->http_session_vars);
2681 }
2682 /* }}} */
2683 
2684 static PHP_MINIT_FUNCTION(session) /* {{{ */
2685 {
2686         zend_class_entry ce;
2687 
2688         zend_register_auto_global(zend_string_init("_SESSION", sizeof("_SESSION") - 1, 1), 0, NULL);
2689 
2690         PS(module_number) = module_number; /* if we really need this var we need to init it in zts mode as well! */
2691 
2692         PS(session_status) = php_session_none;
2693         REGISTER_INI_ENTRIES();
2694 
2695 #ifdef HAVE_LIBMM
2696         PHP_MINIT(ps_mm) (INIT_FUNC_ARGS_PASSTHRU);
2697 #endif
2698         php_session_rfc1867_orig_callback = php_rfc1867_callback;
2699         php_rfc1867_callback = php_session_rfc1867_callback;
2700 
2701         /* Register interfaces */
2702         INIT_CLASS_ENTRY(ce, PS_IFACE_NAME, php_session_iface_functions);
2703         php_session_iface_entry = zend_register_internal_class(&ce);
2704         php_session_iface_entry->ce_flags |= ZEND_ACC_INTERFACE;
2705 
2706         INIT_CLASS_ENTRY(ce, PS_SID_IFACE_NAME, php_session_id_iface_functions);
2707         php_session_id_iface_entry = zend_register_internal_class(&ce);
2708         php_session_id_iface_entry->ce_flags |= ZEND_ACC_INTERFACE;
2709 
2710         INIT_CLASS_ENTRY(ce, PS_UPDATE_TIMESTAMP_IFACE_NAME, php_session_update_timestamp_iface_functions);
2711         php_session_update_timestamp_iface_entry = zend_register_internal_class(&ce);
2712         php_session_update_timestamp_iface_entry->ce_flags |= ZEND_ACC_INTERFACE;
2713 
2714         /* Register base class */
2715         INIT_CLASS_ENTRY(ce, PS_CLASS_NAME, php_session_class_functions);
2716         php_session_class_entry = zend_register_internal_class(&ce);
2717         zend_class_implements(php_session_class_entry, 1, php_session_iface_entry);
2718         zend_class_implements(php_session_class_entry, 1, php_session_id_iface_entry);
2719 
2720         REGISTER_LONG_CONSTANT("PHP_SESSION_DISABLED", php_session_disabled, CONST_CS | CONST_PERSISTENT);
2721         REGISTER_LONG_CONSTANT("PHP_SESSION_NONE", php_session_none, CONST_CS | CONST_PERSISTENT);
2722         REGISTER_LONG_CONSTANT("PHP_SESSION_ACTIVE", php_session_active, CONST_CS | CONST_PERSISTENT);
2723 
2724         return SUCCESS;
2725 }
2726 /* }}} */
2727 
2728 static PHP_MSHUTDOWN_FUNCTION(session) /* {{{ */
2729 {
2730         UNREGISTER_INI_ENTRIES();
2731 
2732 #ifdef HAVE_LIBMM
2733         PHP_MSHUTDOWN(ps_mm) (SHUTDOWN_FUNC_ARGS_PASSTHRU);
2734 #endif
2735 
2736         /* reset rfc1867 callbacks */
2737         php_session_rfc1867_orig_callback = NULL;
2738         if (php_rfc1867_callback == php_session_rfc1867_callback) {
2739                 php_rfc1867_callback = NULL;
2740         }
2741 
2742         ps_serializers[PREDEFINED_SERIALIZERS].name = NULL;
2743         memset(&ps_modules[PREDEFINED_MODULES], 0, (MAX_MODULES-PREDEFINED_MODULES)*sizeof(ps_module *));
2744 
2745         return SUCCESS;
2746 }
2747 /* }}} */
2748 
2749 static PHP_MINFO_FUNCTION(session) /* {{{ */
2750 {
2751         ps_module **mod;
2752         ps_serializer *ser;
2753         smart_str save_handlers = {0};
2754         smart_str ser_handlers = {0};
2755         int i;
2756 
2757         /* Get save handlers */
2758         for (i = 0, mod = ps_modules; i < MAX_MODULES; i++, mod++) {
2759                 if (*mod && (*mod)->s_name) {
2760                         smart_str_appends(&save_handlers, (*mod)->s_name);
2761                         smart_str_appendc(&save_handlers, ' ');
2762                 }
2763         }
2764 
2765         /* Get serializer handlers */
2766         for (i = 0, ser = ps_serializers; i < MAX_SERIALIZERS; i++, ser++) {
2767                 if (ser && ser->name) {
2768                         smart_str_appends(&ser_handlers, ser->name);
2769                         smart_str_appendc(&ser_handlers, ' ');
2770                 }
2771         }
2772 
2773         php_info_print_table_start();
2774         php_info_print_table_row(2, "Session Support", "enabled" );
2775 
2776         if (save_handlers.s) {
2777                 smart_str_0(&save_handlers);
2778                 php_info_print_table_row(2, "Registered save handlers", ZSTR_VAL(save_handlers.s));
2779                 smart_str_free(&save_handlers);
2780         } else {
2781                 php_info_print_table_row(2, "Registered save handlers", "none");
2782         }
2783 
2784         if (ser_handlers.s) {
2785                 smart_str_0(&ser_handlers);
2786                 php_info_print_table_row(2, "Registered serializer handlers", ZSTR_VAL(ser_handlers.s));
2787                 smart_str_free(&ser_handlers);
2788         } else {
2789                 php_info_print_table_row(2, "Registered serializer handlers", "none");
2790         }
2791 
2792         php_info_print_table_end();
2793 
2794         DISPLAY_INI_ENTRIES();
2795 }
2796 /* }}} */
2797 
2798 static const zend_module_dep session_deps[] = { /* {{{ */
2799         ZEND_MOD_OPTIONAL("hash")
2800         ZEND_MOD_REQUIRED("spl")
2801         ZEND_MOD_END
2802 };
2803 /* }}} */
2804 
2805 /* ************************
2806    * Upload hook handling *
2807    ************************ */
2808 
2809 static zend_bool early_find_sid_in(zval *dest, int where, php_session_rfc1867_progress *progress) /* {{{ */
2810 {
2811         zval *ppid;
2812 
2813         if (Z_ISUNDEF(PG(http_globals)[where])) {
2814                 return 0;
2815         }
2816 
2817         if ((ppid = zend_hash_str_find(Z_ARRVAL(PG(http_globals)[where]), PS(session_name), progress->sname_len))
2818                         && Z_TYPE_P(ppid) == IS_STRING) {
2819                 zval_dtor(dest);
2820                 ZVAL_DEREF(ppid);
2821                 ZVAL_COPY(dest, ppid);
2822                 return 1;
2823         }
2824 
2825         return 0;
2826 } /* }}} */
2827 
2828 static void php_session_rfc1867_early_find_sid(php_session_rfc1867_progress *progress) /* {{{ */
2829 {
2830 
2831         if (PS(use_cookies)) {
2832                 sapi_module.treat_data(PARSE_COOKIE, NULL, NULL);
2833                 if (early_find_sid_in(&progress->sid, TRACK_VARS_COOKIE, progress)) {
2834                         progress->apply_trans_sid = 0;
2835                         return;
2836                 }
2837         }
2838         if (PS(use_only_cookies)) {
2839                 return;
2840         }
2841         sapi_module.treat_data(PARSE_GET, NULL, NULL);
2842         early_find_sid_in(&progress->sid, TRACK_VARS_GET, progress);
2843 } /* }}} */
2844 
2845 static zend_bool php_check_cancel_upload(php_session_rfc1867_progress *progress) /* {{{ */
2846 {
2847         zval *progress_ary, *cancel_upload;
2848 
2849         if ((progress_ary = zend_symtable_find(Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars))), progress->key.s)) == NULL) {
2850                 return 0;
2851         }
2852         if (Z_TYPE_P(progress_ary) != IS_ARRAY) {
2853                 return 0;
2854         }
2855         if ((cancel_upload = zend_hash_str_find(Z_ARRVAL_P(progress_ary), "cancel_upload", sizeof("cancel_upload") - 1)) == NULL) {
2856                 return 0;
2857         }
2858         return Z_TYPE_P(cancel_upload) == IS_TRUE;
2859 } /* }}} */
2860 
2861 static void php_session_rfc1867_update(php_session_rfc1867_progress *progress, int force_update) /* {{{ */
2862 {
2863         if (!force_update) {
2864                 if (Z_LVAL_P(progress->post_bytes_processed) < progress->next_update) {
2865                         return;
2866                 }
2867 #ifdef HAVE_GETTIMEOFDAY
2868                 if (PS(rfc1867_min_freq) > 0.0) {
2869                         struct timeval tv = {0};
2870                         double dtv;
2871                         gettimeofday(&tv, NULL);
2872                         dtv = (double) tv.tv_sec + tv.tv_usec / 1000000.0;
2873                         if (dtv < progress->next_update_time) {
2874                                 return;
2875                         }
2876                         progress->next_update_time = dtv + PS(rfc1867_min_freq);
2877                 }
2878 #endif
2879                 progress->next_update = Z_LVAL_P(progress->post_bytes_processed) + progress->update_step;
2880         }
2881 
2882         php_session_initialize();
2883         PS(session_status) = php_session_active;
2884         IF_SESSION_VARS() {
2885                 progress->cancel_upload |= php_check_cancel_upload(progress);
2886                 if (Z_REFCOUNTED(progress->data)) Z_ADDREF(progress->data);
2887                 zend_hash_update(Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars))), progress->key.s, &progress->data);
2888         }
2889         php_session_flush(1);
2890 } /* }}} */
2891 
2892 static void php_session_rfc1867_cleanup(php_session_rfc1867_progress *progress) /* {{{ */
2893 {
2894         php_session_initialize();
2895         PS(session_status) = php_session_active;
2896         IF_SESSION_VARS() {
2897                 zend_hash_del(Z_ARRVAL_P(Z_REFVAL(PS(http_session_vars))), progress->key.s);
2898         }
2899         php_session_flush(1);
2900 } /* }}} */
2901 
2902 static int php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra) /* {{{ */
2903 {
2904         php_session_rfc1867_progress *progress;
2905         int retval = SUCCESS;
2906 
2907         if (php_session_rfc1867_orig_callback) {
2908                 retval = php_session_rfc1867_orig_callback(event, event_data, extra);
2909         }
2910         if (!PS(rfc1867_enabled)) {
2911                 return retval;
2912         }
2913 
2914         progress = PS(rfc1867_progress);
2915 
2916         switch(event) {
2917                 case MULTIPART_EVENT_START: {
2918                         multipart_event_start *data = (multipart_event_start *) event_data;
2919                         progress = ecalloc(1, sizeof(php_session_rfc1867_progress));
2920                         progress->content_length = data->content_length;
2921                         progress->sname_len  = strlen(PS(session_name));
2922                         PS(rfc1867_progress) = progress;
2923                 }
2924                 break;
2925                 case MULTIPART_EVENT_FORMDATA: {
2926                         multipart_event_formdata *data = (multipart_event_formdata *) event_data;
2927                         size_t value_len;
2928 
2929                         if (Z_TYPE(progress->sid) && progress->key.s) {
2930                                 break;
2931                         }
2932 
2933                         /* orig callback may have modified *data->newlength */
2934                         if (data->newlength) {
2935                                 value_len = *data->newlength;
2936                         } else {
2937                                 value_len = data->length;
2938                         }
2939 
2940                         if (data->name && data->value && value_len) {
2941                                 size_t name_len = strlen(data->name);
2942 
2943                                 if (name_len == progress->sname_len && memcmp(data->name, PS(session_name), name_len) == 0) {
2944                                         zval_dtor(&progress->sid);
2945                                         ZVAL_STRINGL(&progress->sid, (*data->value), value_len);
2946                                 } else if (name_len == strlen(PS(rfc1867_name)) && memcmp(data->name, PS(rfc1867_name), name_len + 1) == 0) {
2947                                         smart_str_free(&progress->key);
2948                                         smart_str_appends(&progress->key, PS(rfc1867_prefix));
2949                                         smart_str_appendl(&progress->key, *data->value, value_len);
2950                                         smart_str_0(&progress->key);
2951 
2952                                         progress->apply_trans_sid = APPLY_TRANS_SID;
2953                                         php_session_rfc1867_early_find_sid(progress);
2954                                 }
2955                         }
2956                 }
2957                 break;
2958                 case MULTIPART_EVENT_FILE_START: {
2959                         multipart_event_file_start *data = (multipart_event_file_start *) event_data;
2960 
2961                         /* Do nothing when $_POST["PHP_SESSION_UPLOAD_PROGRESS"] is not set
2962                          * or when we have no session id */
2963                         if (!Z_TYPE(progress->sid) || !progress->key.s) {
2964                                 break;
2965                         }
2966 
2967                         /* First FILE_START event, initializing data */
2968                         if (Z_ISUNDEF(progress->data)) {
2969 
2970                                 if (PS(rfc1867_freq) >= 0) {
2971                                         progress->update_step = PS(rfc1867_freq);
2972                                 } else if (PS(rfc1867_freq) < 0) { /* % of total size */
2973                                         progress->update_step = progress->content_length * -PS(rfc1867_freq) / 100;
2974                                 }
2975                                 progress->next_update = 0;
2976                                 progress->next_update_time = 0.0;
2977 
2978                                 array_init(&progress->data);
2979                                 array_init(&progress->files);
2980 
2981                                 add_assoc_long_ex(&progress->data, "start_time", sizeof("start_time") - 1, (zend_long)sapi_get_request_time());
2982                                 add_assoc_long_ex(&progress->data, "content_length",  sizeof("content_length") - 1, progress->content_length);
2983                                 add_assoc_long_ex(&progress->data, "bytes_processed", sizeof("bytes_processed") - 1, data->post_bytes_processed);
2984                                 add_assoc_bool_ex(&progress->data, "done", sizeof("done") - 1, 0);
2985                                 add_assoc_zval_ex(&progress->data, "files", sizeof("files") - 1, &progress->files);
2986 
2987                                 progress->post_bytes_processed = zend_hash_str_find(Z_ARRVAL(progress->data), "bytes_processed", sizeof("bytes_processed") - 1);
2988 
2989                                 php_rinit_session(0);
2990                                 PS(id) = zend_string_init(Z_STRVAL(progress->sid), Z_STRLEN(progress->sid), 0);
2991                                 if (progress->apply_trans_sid) {
2992                                         /* Enable trans sid by modifying flags */
2993                                         PS(use_trans_sid) = 1;
2994                                         PS(use_only_cookies) = 0;
2995                                 }
2996                                 PS(send_cookie) = 0;
2997                         }
2998 
2999                         array_init(&progress->current_file);
3000 
3001                         /* Each uploaded file has its own array. Trying to make it close to $_FILES entries. */
3002                         add_assoc_string_ex(&progress->current_file, "field_name", sizeof("field_name") - 1, data->name);
3003                         add_assoc_string_ex(&progress->current_file, "name", sizeof("name") - 1, *data->filename);
3004                         add_assoc_null_ex(&progress->current_file, "tmp_name", sizeof("tmp_name") - 1);
3005                         add_assoc_long_ex(&progress->current_file, "error", sizeof("error") - 1, 0);
3006 
3007                         add_assoc_bool_ex(&progress->current_file, "done", sizeof("done") - 1, 0);
3008                         add_assoc_long_ex(&progress->current_file, "start_time", sizeof("start_time") - 1, (zend_long)time(NULL));
3009                         add_assoc_long_ex(&progress->current_file, "bytes_processed", sizeof("bytes_processed") - 1, 0);
3010 
3011                         add_next_index_zval(&progress->files, &progress->current_file);
3012 
3013                         progress->current_file_bytes_processed = zend_hash_str_find(Z_ARRVAL(progress->current_file), "bytes_processed", sizeof("bytes_processed") - 1);
3014 
3015                         Z_LVAL_P(progress->current_file_bytes_processed) =  data->post_bytes_processed;
3016                         php_session_rfc1867_update(progress, 0);
3017                 }
3018                 break;
3019                 case MULTIPART_EVENT_FILE_DATA: {
3020                         multipart_event_file_data *data = (multipart_event_file_data *) event_data;
3021 
3022                         if (!Z_TYPE(progress->sid) || !progress->key.s) {
3023                                 break;
3024                         }
3025 
3026                         Z_LVAL_P(progress->current_file_bytes_processed) = data->offset + data->length;
3027                         Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed;
3028 
3029                         php_session_rfc1867_update(progress, 0);
3030                 }
3031                 break;
3032                 case MULTIPART_EVENT_FILE_END: {
3033                         multipart_event_file_end *data = (multipart_event_file_end *) event_data;
3034 
3035                         if (!Z_TYPE(progress->sid) || !progress->key.s) {
3036                                 break;
3037                         }
3038 
3039                         if (data->temp_filename) {
3040                                 add_assoc_string_ex(&progress->current_file, "tmp_name",  sizeof("tmp_name") - 1, data->temp_filename);
3041                         }
3042 
3043                         add_assoc_long_ex(&progress->current_file, "error", sizeof("error") - 1, data->cancel_upload);
3044                         add_assoc_bool_ex(&progress->current_file, "done", sizeof("done") - 1,  1);
3045 
3046                         Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed;
3047 
3048                         php_session_rfc1867_update(progress, 0);
3049                 }
3050                 break;
3051                 case MULTIPART_EVENT_END: {
3052                         multipart_event_end *data = (multipart_event_end *) event_data;
3053 
3054                         if (Z_TYPE(progress->sid) && progress->key.s) {
3055                                 if (PS(rfc1867_cleanup)) {
3056                                         php_session_rfc1867_cleanup(progress);
3057                                 } else {
3058                                         add_assoc_bool_ex(&progress->data, "done", sizeof("done") - 1, 1);
3059                                         Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed;
3060                                         php_session_rfc1867_update(progress, 1);
3061                                 }
3062                                 php_rshutdown_session_globals();
3063                         }
3064 
3065                         if (!Z_ISUNDEF(progress->data)) {
3066                                 zval_ptr_dtor(&progress->data);
3067                         }
3068                         zval_ptr_dtor(&progress->sid);
3069                         smart_str_free(&progress->key);
3070                         efree(progress);
3071                         progress = NULL;
3072                         PS(rfc1867_progress) = NULL;
3073                 }
3074                 break;
3075         }
3076 
3077         if (progress && progress->cancel_upload) {
3078                 return FAILURE;
3079         }
3080         return retval;
3081 
3082 } /* }}} */
3083 
3084 zend_module_entry session_module_entry = {
3085         STANDARD_MODULE_HEADER_EX,
3086         NULL,
3087         session_deps,
3088         "session",
3089         session_functions,
3090         PHP_MINIT(session), PHP_MSHUTDOWN(session),
3091         PHP_RINIT(session), PHP_RSHUTDOWN(session),
3092         PHP_MINFO(session),
3093         PHP_SESSION_VERSION,
3094         PHP_MODULE_GLOBALS(ps),
3095         PHP_GINIT(ps),
3096         NULL,
3097         NULL,
3098         STANDARD_MODULE_PROPERTIES_EX
3099 };
3100 
3101 #ifdef COMPILE_DL_SESSION
3102 #ifdef ZTS
3103 ZEND_TSRMLS_CACHE_DEFINE()
3104 #endif
3105 ZEND_GET_MODULE(session)
3106 #endif
3107 
3108 /*
3109  * Local variables:
3110  * tab-width: 4
3111  * c-basic-offset: 4
3112  * End:
3113  * vim600: noet sw=4 ts=4 fdm=marker
3114  * vim<600: sw=4 ts=4
3115  */

/* [<][>][^][v][top][bottom][index][help] */