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