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