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