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