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