From e6c8640a2a242b4c620923dcbe5f93c8366585e7 Mon Sep 17 00:00:00 2001 From: Yasuo Ohgaki Date: Wed, 21 Jan 2015 19:13:59 +0900 Subject: [PATCH] WIP - test passes --- ext/session/mod_files.c | 262 +++++++++++++++--- ext/session/mod_files.h | 2 +- ext/session/mod_user.c | 38 ++- ext/session/mod_user.h | 2 +- ext/session/mod_user_class.c | 34 +++ ext/session/php_session.h | 82 ++++-- ext/session/session.c | 257 +++++++++++++++-- ext/session/tests/save_handler.inc | 41 ++- ext/session/tests/session_basic1.phpt | 67 +++++ ext/session/tests/session_basic2.phpt | 83 ++++++ .../tests/session_commit_variation3.phpt | 2 +- .../tests/session_commit_variation4.phpt | 10 +- .../tests/session_commit_variation5.phpt | 70 +++++ .../tests/session_decode_variation3.phpt | 4 +- .../tests/session_set_save_handler_basic.phpt | 10 + .../session_set_save_handler_class_001.phpt | 51 +++- .../session_set_save_handler_class_018.phpt | 94 +++++++ .../session_set_save_handler_variation4.phpt | 4 +- .../session_set_save_handler_variation5.phpt | 100 +++++++ ... session_set_save_handler_variation6.phpt} | 12 +- ext/session/tests/session_start_error.phpt | 193 +++++++++---- 21 files changed, 1256 insertions(+), 162 deletions(-) create mode 100644 ext/session/tests/session_basic1.phpt create mode 100644 ext/session/tests/session_basic2.phpt create mode 100644 ext/session/tests/session_commit_variation5.phpt create mode 100644 ext/session/tests/session_set_save_handler_class_018.phpt create mode 100644 ext/session/tests/session_set_save_handler_variation5.phpt rename ext/session/tests/{session_set_save_handler_write_short_circuit.phpt => session_set_save_handler_variation6.phpt} (88%) diff --git a/ext/session/mod_files.c b/ext/session/mod_files.c index 1d6686a5d9..36c9a3354e 100644 --- a/ext/session/mod_files.c +++ b/ext/session/mod_files.c @@ -16,7 +16,41 @@ +----------------------------------------------------------------------+ */ -/* $Id$ */ +/************************************************************************** + * Files save handler should be used as reference implementations of session + * save handlers. PS_* functions are called as follows with standard usage. + * + * PS_OPEN_FUNC() - Create module data that manages save handler. + * PS_CREATE_SID() and/or PS_VALIDATE_SID() + * - PS_CREATE_ID() is called if session ID(key) is not + * provided or invalid. PS_VALIDATE_SID() is called to + * verify session ID already exists or not to mitigate + * session adoption vulunerabilty risk. + * PS_READ_FUNC() - Read data from storage. + * PS_GC_FUNC() - Perform GC. Called by probability + * (session.gc_probability/session.gc_divisor). + * PS_WRITE_FUNC() or PS_UPDATE_TIMESTAMP() + * - Write session data or update session data timestamp. + * It depends on session data change. + * PS_CLOSE_FUNC() - Clean up module data created by PS_OPEN_FUNC(). + * + * Session module gurantees PS_OPEN_FUNC() is called before calling other + * PS_*_FUNC() functions. Other than this, session module may call any + * PS_*_FUNC() at any time. You may assume non null *mod_data created by + * PS_OPEN_FUNC() is passed to PS_*_FUNC(). + * + * NOTE: + * - Save handlers _MUST_NOT_ change/refer PS() values. + * i.e. PS(id), PS(session_status), PS(mod) and any other PS() values. + * Use only function parameters passed from session module. + * - Save handler _MUST_ use PS_GET_MOD_DATA()/PS_SET_MOD_DATA() macro to + * set/get save handler module data(mod_data). mod_data contains + * data required by PS modules. It will not be NULL except PS_OPEN_FUNC(). + * - Refer to PS_* macros in php_session.h for function/parameter defitions. + * - Returning FAILURE state from PS_* function results in raising errors. + * Avoid failure state as much as possible. + * - Use static ps_[module name]_[function name] functions for internal use. + *************************************************************************/ #include "php.h" @@ -67,7 +101,8 @@ typedef struct { } ps_files; ps_module ps_mod_files = { - PS_MOD_SID(files) + /* New save handlers MUST use PS_MOD_UPDATE_TIMESTAMP macro */ + PS_MOD_UPDATE_TIMESTAMP(files) }; @@ -182,6 +217,43 @@ static void ps_files_open(ps_files *data, const char *key) } } +static int ps_files_write(ps_files *data, zend_string *key, zend_string *val) +{ + zend_long n; + zend_stat_t sbuf; + + /* PS(id) may be changed by calling session_regenerate_id(). + Re-initialization should be tried here. ps_files_open() checks + data->lastkey and reopen when it is needed. */ + ps_files_open(data, key->val); + if (data->fd < 0) { + return FAILURE; + } + + /* Truncate file if the amount of new data is smaller than the existing data set. */ + if (val->len < (int)data->st_size) { + php_ignore_value(ftruncate(data->fd, 0)); + } + +#if defined(HAVE_PWRITE) + n = pwrite(data->fd, val->val, val->len, 0); +#else + lseek(data->fd, 0, SEEK_SET); + n = write(data->fd, val->val, val->len); +#endif + + if (n != val->len) { + if (n == -1) { + php_error_docref(NULL, E_WARNING, "write failed: %s (%d)", strerror(errno), errno); + } else { + php_error_docref(NULL, E_WARNING, "write wrote less bytes than requested"); + } + return FAILURE; + } + + return SUCCESS; +} + static int ps_files_cleanup_dir(const char *dirname, int maxlifetime) { DIR *dir; @@ -252,6 +324,18 @@ static int ps_files_key_exists(ps_files *data, const char *key) #define PS_FILES_DATA ps_files *data = PS_GET_MOD_DATA() + +/* + * Open save handler. Setup resources that are needed by the handler. + * PARAMETERS: PS_OPEN_ARGS in php_session.h + * RETURN VALUE: SUCCESS or FAILURE. Must set non-NULL valid module data + * (void **mod_data) with SUCCESS, NULL(default) for FAILUREs. + * + * Files save handler checks/create save_path directory and setup ps_files data. + * Note that files save handler supports splitting session data into multiple + * directories. + * *mod_data, *save_path, *session_name are guranteed to have non-NULL values. + */ PS_OPEN_FUNC(files) { ps_files *data; @@ -316,6 +400,17 @@ PS_OPEN_FUNC(files) return SUCCESS; } + +/* + * Clean up opened resources. + * PARAMETERS: PS_CLOSE_ARGS in php_session.h + * RETURN VALUE: SUCCESS. Must set PS module data(void **mod_data) to NULL. + * + * Files save handler closes open files and it's memory. + * *mod_data is guranteed to have non-NULL value. + * PS_CLOSE_FUNC() must set *mod_data to NULL. PS_CLOSE_FUNC() should not + * fail. + */ PS_CLOSE_FUNC(files) { PS_FILES_DATA; @@ -333,32 +428,24 @@ PS_CLOSE_FUNC(files) return SUCCESS; } + +/* + * Read session data from opened resource. + * PARAMETERS: PS_READ_ARGS in php_session.h + * RETURN VALUE: SUCCESS or FAILURE. Must set non-NULL session data to (zend_string **val) + * for SUCCESS. NULL(default) for FAILUREs. + * + * Files save handler supports splitting session data into multiple + * directories. + * *mod_data, *key are guranteed to have non-NULL values. + */ PS_READ_FUNC(files) { zend_long n; zend_stat_t sbuf; PS_FILES_DATA; - /* If strict mode, check session id existence */ - if (PS(use_strict_mode) && - ps_files_key_exists(data, key? key->val : NULL) == FAILURE) { - /* key points to PS(id), but cannot change here. */ - if (key) { - zend_string_release(PS(id)); - PS(id) = NULL; - } - PS(id) = PS(mod)->s_create_sid((void **)&data); - if (!PS(id)) { - return FAILURE; - } - if (PS(use_cookies)) { - PS(send_cookie) = 1; - } - php_session_reset_id(); - PS(session_status) = php_session_active; - } - - ps_files_open(data, PS(id)->val); + ps_files_open(data, key->val); if (data->fd < 0) { return FAILURE; } @@ -390,47 +477,82 @@ PS_READ_FUNC(files) php_error_docref(NULL, E_WARNING, "read returned less bytes than requested"); } zend_string_release(*val); + *val = STR_EMPTY_ALLOC(); return FAILURE; } return SUCCESS; } + +/* + * Write session data. + * PARAMETERS: PS_WRITE_ARGS in php_session.h + * RETURN VALUE: SUCCESS or FAILURE. + * + * PS_WRITE_FUNC() must write session data(zend_string *val) unconditionally. + * *mod_data, *key, *val are guranteed to have non-NULL values. + */ PS_WRITE_FUNC(files) { - zend_long n; PS_FILES_DATA; - ps_files_open(data, key->val); - if (data->fd < 0) { - return FAILURE; - } + return ps_files_write(data, key, val); +} - /* Truncate file if the amount of new data is smaller than the existing data set. */ - if (val->len < (int)data->st_size) { - php_ignore_value(ftruncate(data->fd, 0)); +/* + * Update session data modification/access time stamp. + * PARAMETERS: PS_UPDATE_TIMESTAMP_ARGS in php_session.h + * RETURN VALUE: SUCCESS or FAILURE. + * + * PS_UPDATE_TIMESTAMP_FUNC() updates time stamp(mtime) so that active session + * data files will not be purged by GC. If session data storage does not need to + * update timestamp, it should return SUCCESS simply. (e.g. Memcache) + * *mod_data, *key, *val are guranteed to have non-NULL values. + * + * NOTE: Updating access timestamp at PS_READ_FUNC() may extend life of obsolete + * session data. Use of PS_UPDATE_TIMESTAMP_FUNC() is prefered whenenver it is + * possible. + */ +PS_UPDATE_TIMESTAMP_FUNC(files) +{ + char buf[MAXPATHLEN]; + struct utimbuf newtimebuf; + struct utimbuf *newtime = &newtimebuf; + int ret; + PS_FILES_DATA; + + if (!ps_files_path_create(buf, sizeof(buf), data, key->val)) { + return FAILURE; } -#if defined(HAVE_PWRITE) - n = pwrite(data->fd, val->val, val->len, 0); + /* Update mtime */ +#ifdef HAVE_UTIME_NULL + newtime = NULL; #else - lseek(data->fd, 0, SEEK_SET); - n = write(data->fd, val->val, val->len); + newtime->modtime = newtime->actime = time(NULL); #endif - - if (n != val->len) { - if (n == -1) { - php_error_docref(NULL, E_WARNING, "write failed: %s (%d)", strerror(errno), errno); - } else { - php_error_docref(NULL, E_WARNING, "write wrote less bytes than requested"); - } - return FAILURE; + ret = VCWD_UTIME(buf, newtime); + if (ret == -1) { + /* New session ID, create data file */ + return ps_files_write(data, key, val); } return SUCCESS; } + +/* + * Delete session data. + * PARAMETERS: PS_DESTROY_ARGS in php_session.h + * RETURN VALUE: SUCCESS or FAILURE. + * + * PS_DESTROY_FUNC() must remove the session data specified by *key from + * session data storage unconditionally. It must not return FAILURE for + * non-existent session data. + * *mod_data, *key are guranteed to have non-NULL values. + */ PS_DESTROY_FUNC(files) { char buf[MAXPATHLEN]; @@ -455,11 +577,23 @@ PS_DESTROY_FUNC(files) return SUCCESS; } + +/* + * Cleanup expired session data. + * PARAMETERS: PS_GC_ARGS in php_session.h + * RETURN VALUE: SUCCESS or FAILURE. Number of deleted records(int *nrdels(default=-1)). + * + * PS_GC_FUNC() must remove session data that are not accessed + * 'session.maxlifetime'(seconds). If storage does not need manual GC, it + * may return SUCCESS simply. (e.g. Memcache) It must set number of records + * deleted(nrdels). + * *mod_data is guranteed to have non-NULL value. + */ PS_GC_FUNC(files) { PS_FILES_DATA; - /* we don't perform any cleanup, if dirdepth is larger than 0. + /* We don't perform any cleanup, if dirdepth is larger than 0. we return SUCCESS, since all cleanup should be handled by an external entity (i.e. find -ctime x | xargs rm) */ @@ -470,6 +604,20 @@ PS_GC_FUNC(files) return SUCCESS; } + +/* + * Create session ID. + * PARAMETERS: PS_CREATE_SID_ARGS in php_session.h + * RETURN VALUE: Valid session ID(zend_string *) or NULL for FAILURE. + * + * PS_CREATE_SID_FUNC() must check collision. i.e. Check session data if + * new sid exists already. + * *mod_data is guranteed to have non-NULL value. + * NOTE: Default php_session_create_id() does not check collision. If + * NULL is returned, session module create new ID by using php_session_create_id(). + * If php_session_create_id() fails due to invalid configuration, it raises E_ERROR. + * NULL return value checks from php_session_create_id() is not required generally. + */ PS_CREATE_SID_FUNC(files) { zend_string *sid; @@ -478,13 +626,21 @@ PS_CREATE_SID_FUNC(files) do { sid = php_session_create_id((void**)&data); + if (!sid) { + if (--maxfail < 0) { + return NULL; + } else { + continue; + } + } /* Check collision */ - if (data && ps_files_key_exists(data, sid? sid->val : NULL) == SUCCESS) { + /* FIXME: mod_data(data) should not be NULL (User handler could be NULL) */ + if (data && ps_files_key_exists(data, sid->val) == SUCCESS) { if (sid) { zend_string_release(sid); sid = NULL; } - if (!(maxfail--)) { + if (--maxfail < 0) { return NULL; } } @@ -494,6 +650,22 @@ PS_CREATE_SID_FUNC(files) } +/* + * Check session ID existence for use_strict_mode support. + * PARAMETERS: PS_VALIDATE_SID_ARGS in php_session.h + * RETURN VALUE: SUCCESS or FAILURE. + * + * Return SUCCESS for valid key(already exsting session). + * Return FAILURE for invalid key(non-existing session). + * *mod_data, *key are guranteed to have non-NULL values. + */ +PS_VALIDATE_SID_FUNC(files) +{ + PS_FILES_DATA; + + return ps_files_key_exists(data, key->val); +} + /* * Local variables: * tab-width: 4 diff --git a/ext/session/mod_files.h b/ext/session/mod_files.h index 9b068e6828..e0a706ff31 100644 --- a/ext/session/mod_files.h +++ b/ext/session/mod_files.h @@ -24,6 +24,6 @@ extern ps_module ps_mod_files; #define ps_files_ptr &ps_mod_files -PS_FUNCS_SID(files); +PS_FUNCS_UPDATE_TIMESTAMP(files); #endif diff --git a/ext/session/mod_user.c b/ext/session/mod_user.c index 47aafc8417..bb32a957dc 100644 --- a/ext/session/mod_user.c +++ b/ext/session/mod_user.c @@ -23,7 +23,7 @@ #include "mod_user.h" ps_module ps_mod_user = { - PS_MOD_SID(user) + PS_MOD_UPDATE_TIMESTAMP(user) }; #define SESS_ZVAL_LONG(val, a) \ @@ -227,6 +227,42 @@ PS_CREATE_SID_FUNC(user) return php_session_create_id(mod_data); } +PS_VALIDATE_SID_FUNC(user) +{ + /* maintain backwards compatibility */ + if (!Z_ISUNDEF(PSF(validate_sid))) { + zval args[1]; + STDVARS; + + SESS_ZVAL_STR(key, &args[0]); + + ps_call_handler(&PSF(validate_sid), 1, args, &retval); + + FINISH; + } + + /* dummy function defined by PS_MOD */ + return php_session_validate_sid(mod_data, key TSRMLS_CC); +} + +PS_UPDATE_TIMESTAMP_FUNC(user) +{ + zval args[2]; + STDVARS; + + SESS_ZVAL_STR(key, &args[0]); + SESS_ZVAL_STR(val, &args[1]); + + /* maintain backwards compatibility */ + if (!Z_ISUNDEF(PSF(update_timestamp))) { + ps_call_handler(&PSF(update_timestamp), 2, args, &retval); + } else { + ps_call_handler(&PSF(write), 2, args, &retval); + } + + FINISH; +} + /* * Local variables: * tab-width: 4 diff --git a/ext/session/mod_user.h b/ext/session/mod_user.h index 1a315b70da..592b223c60 100644 --- a/ext/session/mod_user.h +++ b/ext/session/mod_user.h @@ -24,6 +24,6 @@ extern ps_module ps_mod_user; #define ps_user_ptr &ps_mod_user -PS_FUNCS_SID(user); +PS_FUNCS_UPDATE_TIMESTAMP(user); #endif diff --git a/ext/session/mod_user_class.c b/ext/session/mod_user_class.c index 419fd03f61..328416c02e 100644 --- a/ext/session/mod_user_class.c +++ b/ext/session/mod_user_class.c @@ -155,3 +155,37 @@ PHP_METHOD(SessionHandler, create_sid) RETURN_STR(id); } /* }}} */ + +/* {{{ proto char SessionUpdateTimestampHandler::validateId(string id) + Simply return TRUE */ +PHP_METHOD(SessionHandler, validateId) +{ + zend_string *key; + + PS_SANITY_CHECK_IS_OPEN; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &key) == FAILURE) { + return; + } + + /* Legacy save handler may not support validate_sid API. Return TRUE. */ + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool SessionUpdateTimestampHandler::updateTimestamp(string id, string data) + Simply call update_timestamp */ +PHP_METHOD(SessionHandler, updateTimestamp) +{ + zend_string *key, *val; + + PS_SANITY_CHECK_IS_OPEN; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &key, &val) == FAILURE) { + return; + } + + /* Legacy save handler may not support update_timestamp API. Just write. */ + RETVAL_BOOL(SUCCESS == PS(default_mod)->s_write(&PS(mod_data), key, val)); +} +/* }}} */ diff --git a/ext/session/php_session.h b/ext/session/php_session.h index c384b1f97b..c5750066fd 100644 --- a/ext/session/php_session.h +++ b/ext/session/php_session.h @@ -27,21 +27,19 @@ # include "ext/hash/php_hash.h" #endif -#define PHP_SESSION_API 20020330 - -/* To check php_session_valid_key()/php_session_reset_id() */ -#define PHP_SESSION_STRICT 1 - -#define PS_OPEN_ARGS void **mod_data, const char *save_path, const char *session_name -#define PS_CLOSE_ARGS void **mod_data -#define PS_READ_ARGS void **mod_data, zend_string *key, zend_string **val -#define PS_WRITE_ARGS void **mod_data, zend_string *key, zend_string *val -#define PS_DESTROY_ARGS void **mod_data, zend_string *key -#define PS_GC_ARGS void **mod_data, int maxlifetime, int *nrdels +#define PHP_SESSION_API 20150121 + +/* save handler macros */ +#define PS_NUM_APIS 9 +#define PS_OPEN_ARGS void **mod_data, const char *save_path, const char *session_name +#define PS_CLOSE_ARGS void **mod_data +#define PS_READ_ARGS void **mod_data, zend_string *key, zend_string **val +#define PS_WRITE_ARGS void **mod_data, zend_string *key, zend_string *val +#define PS_DESTROY_ARGS void **mod_data, zend_string *key +#define PS_GC_ARGS void **mod_data, int maxlifetime, int *nrdels #define PS_CREATE_SID_ARGS void **mod_data - -/* default create id function */ -PHPAPI zend_string *php_session_create_id(PS_CREATE_SID_ARGS); +#define PS_VALIDATE_SID_ARGS void **mod_data, zend_string *key +#define PS_UPDATE_TIMESTAMP_ARGS void **mod_data, zend_string *key, zend_string *val typedef struct ps_module_struct { const char *s_name; @@ -52,6 +50,8 @@ typedef struct ps_module_struct { int (*s_destroy)(PS_DESTROY_ARGS); int (*s_gc)(PS_GC_ARGS); zend_string *(*s_create_sid)(PS_CREATE_SID_ARGS); + int (*s_validate_sid)(PS_VALIDATE_SID_ARGS); + int (*s_update_timestamp)(PS_UPDATE_TIMESTAMP_ARGS); } ps_module; #define PS_GET_MOD_DATA() *mod_data @@ -64,7 +64,10 @@ typedef struct ps_module_struct { #define PS_DESTROY_FUNC(x) int ps_delete_##x(PS_DESTROY_ARGS) #define PS_GC_FUNC(x) int ps_gc_##x(PS_GC_ARGS) #define PS_CREATE_SID_FUNC(x) zend_string *ps_create_sid_##x(PS_CREATE_SID_ARGS) +#define PS_VALIDATE_SID_FUNC(x) int ps_validate_sid_##x(PS_VALIDATE_SID_ARGS) +#define PS_UPDATE_TIMESTAMP_FUNC(x) int ps_update_timestamp_##x(PS_UPDATE_TIMESTAMP_ARGS) +/* Legacy save handler module definitions */ #define PS_FUNCS(x) \ PS_OPEN_FUNC(x); \ PS_CLOSE_FUNC(x); \ @@ -76,9 +79,10 @@ typedef struct ps_module_struct { #define PS_MOD(x) \ #x, ps_open_##x, ps_close_##x, ps_read_##x, ps_write_##x, \ - ps_delete_##x, ps_gc_##x, php_session_create_id + ps_delete_##x, ps_gc_##x, php_session_create_id, \ + php_session_validate_sid, php_session_update_timestamp -/* SID creation enabled module handler definitions */ +/* Legacy SID creation enabled save handler module definitions */ #define PS_FUNCS_SID(x) \ PS_OPEN_FUNC(x); \ PS_CLOSE_FUNC(x); \ @@ -86,11 +90,33 @@ typedef struct ps_module_struct { PS_WRITE_FUNC(x); \ PS_DESTROY_FUNC(x); \ PS_GC_FUNC(x); \ - PS_CREATE_SID_FUNC(x) + PS_CREATE_SID_FUNC(x); \ + PS_VALIDATE_SID_FUNC(x); \ + PS_UPDATE_TIMESTAMP_FUNC(x); #define PS_MOD_SID(x) \ #x, ps_open_##x, ps_close_##x, ps_read_##x, ps_write_##x, \ - ps_delete_##x, ps_gc_##x, ps_create_sid_##x + ps_delete_##x, ps_gc_##x, ps_create_sid_##x, \ + php_session_validate_sid, php_session_update_timestamp + +/* Update timestamp enabled save handler module definitions + New save handlers should use this API */ +#define PS_FUNCS_UPDATE_TIMESTAMP(x) \ + PS_OPEN_FUNC(x); \ + PS_CLOSE_FUNC(x); \ + PS_READ_FUNC(x); \ + PS_WRITE_FUNC(x); \ + PS_DESTROY_FUNC(x); \ + PS_GC_FUNC(x); \ + PS_CREATE_SID_FUNC(x); \ + PS_VALIDATE_SID_FUNC(x); \ + PS_UPDATE_TIMESTAMP_FUNC(x); + +#define PS_MOD_UPDATE_TIMESTAMP(x) \ + #x, ps_open_##x, ps_close_##x, ps_read_##x, ps_write_##x, \ + ps_delete_##x, ps_gc_##x, ps_create_sid_##x, \ + ps_validate_sid_##x, ps_update_timestamp_##x + typedef enum { php_session_disabled, @@ -99,7 +125,6 @@ typedef enum { } php_session_status; typedef struct _php_session_rfc1867_progress { - size_t sname_len; zval sid; smart_str key; @@ -141,7 +166,7 @@ typedef struct _php_ps_globals { int module_number; zend_long cache_expire; union { - zval names[7]; + zval names[PS_NUM_APIS]; struct { zval ps_open; zval ps_close; @@ -150,6 +175,8 @@ typedef struct _php_ps_globals { zval ps_destroy; zval ps_gc; zval ps_create_sid; + zval ps_validate_sid; + zval ps_update_timestamp; } name; } mod_user_names; int mod_user_implemented; @@ -169,7 +196,6 @@ typedef struct _php_ps_globals { zend_long hash_bits_per_character; int send_cookie; int define_sid; - zend_bool invalid_session_id; /* allows the driver to report about an invalid session id and request id regeneration */ php_session_rfc1867_progress *rfc1867_progress; zend_bool rfc1867_enabled; /* session.upload_progress.enabled */ @@ -180,7 +206,8 @@ typedef struct _php_ps_globals { double rfc1867_min_freq; /* session.upload_progress.min_freq */ zend_bool use_strict_mode; /* whether or not PHP accepts unknown session ids */ - unsigned char session_data_hash[16]; /* binary MD5 hash length */ + zend_bool lazy_write; /* omit session write when it is possible */ + zend_string *session_vars; /* serialized original session data */ } php_ps_globals; typedef php_ps_globals zend_ps_globals; @@ -221,6 +248,12 @@ typedef struct ps_serializer_struct { #define PS_SERIALIZER_ENTRY(x) \ { #x, PS_SERIALIZER_ENCODE_NAME(x), PS_SERIALIZER_DECODE_NAME(x) } +/* default create id function */ +PHPAPI zend_string *php_session_create_id(PS_CREATE_SID_ARGS); +/* Dummy PS module functions */ +PHPAPI int php_session_validate_sid(PS_VALIDATE_SID_ARGS); +PHPAPI int php_session_update_timestamp(PS_UPDATE_TIMESTAMP_ARGS); + PHPAPI void session_adapt_url(const char *, size_t, char **, size_t *); PHPAPI void php_add_session_var(zend_string *name); @@ -288,6 +321,9 @@ extern zend_class_entry *php_session_iface_entry; #define PS_SID_IFACE_NAME "SessionIdInterface" extern zend_class_entry *php_session_id_iface_entry; +#define PS_UPDATE_TIMESTAMP_IFACE_NAME "SessionUpdateTimestampHandlerInterface" +extern zend_class_entry *php_session_update_timestamp_iface_entry; + extern PHP_METHOD(SessionHandler, open); extern PHP_METHOD(SessionHandler, close); extern PHP_METHOD(SessionHandler, read); @@ -295,5 +331,7 @@ extern PHP_METHOD(SessionHandler, write); extern PHP_METHOD(SessionHandler, destroy); extern PHP_METHOD(SessionHandler, gc); extern PHP_METHOD(SessionHandler, create_sid); +extern PHP_METHOD(SessionHandler, validateId); +extern PHP_METHOD(SessionHandler, updateTimestamp); #endif diff --git a/ext/session/session.c b/ext/session/session.c index ab328573be..c83191f05f 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -74,6 +74,12 @@ zend_class_entry *php_session_iface_entry; /* SessionIdInterface */ zend_class_entry *php_session_id_iface_entry; +/* SessionUpdateTimestampHandler class */ +zend_class_entry *php_session_update_timestamp_class_entry; + +/* SessionUpdateTimestampInterface */ +zend_class_entry *php_session_update_timestamp_iface_entry; + /* *********** * Helpers * *********** */ @@ -92,11 +98,13 @@ static void php_session_send_cookie(void); /* Dispatched by RINIT and by php_session_destroy */ static inline void php_rinit_session_globals(void) /* {{{ */ { + /* Do NOT init PS(mod_user_names) here! */ PS(id) = NULL; PS(session_status) = php_session_none; PS(mod_data) = NULL; PS(mod_user_is_open) = 0; - /* Do NOT init PS(mod_user_names) here! */ + PS(define_sid) = 1; + PS(session_vars) = NULL; ZVAL_UNDEF(&PS(http_session_vars)); } /* }}} */ @@ -104,11 +112,11 @@ static inline void php_rinit_session_globals(void) /* {{{ */ /* Dispatched by RSHUTDOWN and by php_session_destroy */ static inline void php_rshutdown_session_globals(void) /* {{{ */ { + /* Do NOT destroy PS(mod_user_names) here! */ if (!Z_ISUNDEF(PS(http_session_vars))) { zval_ptr_dtor(&PS(http_session_vars)); ZVAL_UNDEF(&PS(http_session_vars)); } - /* Do NOT destroy PS(mod_user_names) here! */ if (PS(mod_data) || PS(mod_user_implemented)) { zend_try { PS(mod)->s_close(&PS(mod_data)); @@ -117,6 +125,9 @@ static inline void php_rshutdown_session_globals(void) /* {{{ */ if (PS(id)) { zend_string_release(PS(id)); } + if (PS(session_vars)) { + zend_string_release(PS(session_vars)); + } } /* }}} */ @@ -212,13 +223,13 @@ static zend_string *php_session_encode(void) /* {{{ */ } /* }}} */ -static void php_session_decode(const char *val, int vallen) /* {{{ */ +static void php_session_decode(zend_string *data) /* {{{ */ { if (!PS(serializer)) { php_error_docref(NULL, E_WARNING, "Unknown session.serialize_handler. Failed to decode session object"); return; } - if (PS(serializer)->decode(val, vallen) == FAILURE) { + if (PS(serializer)->decode(data->val, data->len) == FAILURE) { php_session_destroy(); php_error_docref(NULL, E_WARNING, "Failed to decode session object. Session has been destroyed"); } @@ -321,8 +332,8 @@ PHPAPI zend_string *php_session_create_id(PS_CREATE_SID_ARGS) /* {{{ */ #if defined(HAVE_HASH_EXT) && !defined(COMPILE_DL_HASH) case PS_HASH_FUNC_OTHER: if (!PS(hash_ops)) { - php_error_docref(NULL, E_ERROR, "Invalid session hash function"); efree(buf); + php_error_docref(NULL, E_ERROR, "Invalid session hash function"); return NULL; } @@ -333,8 +344,8 @@ PHPAPI zend_string *php_session_create_id(PS_CREATE_SID_ARGS) /* {{{ */ break; #endif /* HAVE_HASH_EXT */ default: - php_error_docref(NULL, E_ERROR, "Invalid session hash function"); efree(buf); + php_error_docref(NULL, E_ERROR, "Invalid session hash function"); return NULL; } efree(buf); @@ -468,7 +479,9 @@ static void php_session_initialize(void) /* {{{ */ } /* Open session handler first */ - if (PS(mod)->s_open(&PS(mod_data), PS(save_path), PS(session_name)) == FAILURE) { + if (PS(mod)->s_open(&PS(mod_data), PS(save_path), PS(session_name)) == FAILURE + /* || PS(mod_data) == NULL */ /* FIXME: open must set valid PS(mod_data) with success */ + ) { php_error_docref(NULL, E_ERROR, "Failed to initialize storage module: %s (path: %s)", PS(mod)->s_name, PS(save_path)); return; } @@ -483,8 +496,20 @@ static void php_session_initialize(void) /* {{{ */ if (PS(use_cookies)) { PS(send_cookie) = 1; } + } else if (PS(use_strict_mode) && PS(mod)->s_validate_sid && + PS(mod)->s_validate_sid(&PS(mod_data), PS(id)) == FAILURE) { + if (PS(id)) { + zend_string_release(PS(id)); + } + PS(id) = PS(mod)->s_create_sid(&PS(mod_data)); + if (!PS(id)) { + PS(id) = php_session_create_id(NULL); + } + if (PS(use_cookies)) { + PS(send_cookie) = 1; + } } - + /* Set session ID for compatibility for older/3rd party save handlers */ if (!PS(use_strict_mode)) { php_session_reset_id(); @@ -505,8 +530,15 @@ static void php_session_initialize(void) /* {{{ */ php_session_reset_id(); PS(session_status) = php_session_active; } + if (PS(session_vars)) { + zend_string_release(PS(session_vars)); + PS(session_vars) = NULL; + } if (val) { - php_session_decode(val->val, val->len); + if (PS(lazy_write)) { + PS(session_vars) = zend_string_copy(val); + } + php_session_decode(val); zend_string_release(val); } @@ -529,7 +561,16 @@ static void php_session_save_current_state(void) /* {{{ */ val = php_session_encode(); if (val) { - ret = PS(mod)->s_write(&PS(mod_data), PS(id), val); + if (PS(lazy_write) && PS(session_vars) + && PS(mod)->s_update_timestamp + && PS(mod)->s_update_timestamp != php_session_update_timestamp + && val->len == PS(session_vars)->len + && !memcmp(val->val, PS(session_vars)->val, val->len) + ) { + ret = PS(mod)->s_update_timestamp(&PS(mod_data), PS(id), val); + } else { + ret = PS(mod)->s_write(&PS(mod_data), PS(id), val); + } zend_string_release(val); } else { ret = PS(mod)->s_write(&PS(mod_data), PS(id), STR_EMPTY_ALLOC()); @@ -786,6 +827,7 @@ PHP_INI_BEGIN() PHP_INI_ENTRY("session.use_trans_sid", "0", PHP_INI_ALL, OnUpdateTransSid) PHP_INI_ENTRY("session.hash_function", "0", PHP_INI_ALL, OnUpdateHashFunc) STD_PHP_INI_ENTRY("session.hash_bits_per_character", "4", PHP_INI_ALL, OnUpdateLong, hash_bits_per_character, php_ps_globals, ps_globals) + STD_PHP_INI_BOOLEAN("session.lazy_write", "1", PHP_INI_ALL, OnUpdateBool, lazy_write, php_ps_globals, ps_globals) /* Upload progress */ STD_PHP_INI_BOOLEAN("session.upload_progress.enabled", @@ -813,9 +855,11 @@ PS_SERIALIZER_ENCODE_FUNC(php_serialize) /* {{{ */ smart_str buf = {0}; php_serialize_data_t var_hash; - PHP_VAR_SERIALIZE_INIT(var_hash); - php_var_serialize(&buf, Z_REFVAL(PS(http_session_vars)), &var_hash); - PHP_VAR_SERIALIZE_DESTROY(var_hash); + IF_SESSION_VARS() { + PHP_VAR_SERIALIZE_INIT(var_hash); + php_var_serialize(&buf, Z_REFVAL(PS(http_session_vars)), &var_hash); + PHP_VAR_SERIALIZE_DESTROY(var_hash); + } return buf.s; } /* }}} */ @@ -829,7 +873,7 @@ PS_SERIALIZER_DECODE_FUNC(php_serialize) /* {{{ */ ZVAL_NULL(&session_vars); PHP_VAR_UNSERIALIZE_INIT(var_hash); - php_var_unserialize(&session_vars, (const unsigned char **)&val, endptr, &var_hash); + php_var_unserialize(&session_vars, (const unsigned char **)&val, (const unsigned char *)endptr, &var_hash); PHP_VAR_UNSERIALIZE_DESTROY(var_hash); if (!Z_ISUNDEF(PS(http_session_vars))) { zval_ptr_dtor(&PS(http_session_vars)); @@ -1053,7 +1097,7 @@ PHPAPI int php_session_register_serializer(const char *name, zend_string *(*enco * Storage Modules * ******************* */ -#define MAX_MODULES 10 +#define MAX_MODULES 32 #define PREDEFINED_MODULES 2 static ps_module *ps_modules[MAX_MODULES + 1] = { @@ -1077,6 +1121,17 @@ PHPAPI int php_session_register_module(ps_module *ptr) /* {{{ */ } /* }}} */ +/* Dummy PS module function */ +PHPAPI int php_session_validate_sid(PS_VALIDATE_SID_ARGS) { + return SUCCESS; +} + +/* Dummy PS module function */ +PHPAPI int php_session_update_timestamp(PS_UPDATE_TIMESTAMP_ARGS) { + return SUCCESS; +} + + /* ****************** * Cache Limiters * ****************** */ @@ -1762,6 +1817,7 @@ static PHP_FUNCTION(session_set_save_handler) RETURN_FALSE; } + /* For compatibility reason, implemeted interface is not checked */ /* Find implemented methods - SessionHandlerInterface */ i = 0; ZEND_HASH_FOREACH_STR_KEY(&php_session_iface_entry->function_table, func_name) { @@ -1788,16 +1844,39 @@ static PHP_FUNCTION(session_set_save_handler) if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { zval_ptr_dtor(&PS(mod_user_names).names[i]); } - array_init_size(&PS(mod_user_names).names[i], 2); Z_ADDREF_P(obj); add_next_index_zval(&PS(mod_user_names).names[i], obj); add_next_index_str(&PS(mod_user_names).names[i], zend_string_copy(func_name)); + } else { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + ZVAL_UNDEF(&PS(mod_user_names).names[i]); + } } ++i; } ZEND_HASH_FOREACH_END(); + /* Find implemented methods - SessionUpdateTimestampInterface (optional) */ + ZEND_HASH_FOREACH_STR_KEY(&php_session_update_timestamp_iface_entry->function_table, func_name) { + if ((current_mptr = zend_hash_find_ptr(&Z_OBJCE_P(obj)->function_table, func_name))) { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + } + array_init_size(&PS(mod_user_names).names[i], 2); + Z_ADDREF_P(obj); + add_next_index_zval(&PS(mod_user_names).names[i], obj); + add_next_index_str(&PS(mod_user_names).names[i], zend_string_copy(func_name)); + } else { + if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + ZVAL_UNDEF(&PS(mod_user_names).names[i]); + } + } + ++i; + } ZEND_HASH_FOREACH_END(); + if (register_shutdown) { /* create shutdown function */ php_shutdown_function_entry shutdown_function_entry; @@ -1818,7 +1897,7 @@ static PHP_FUNCTION(session_set_save_handler) remove_user_shutdown_function("session_shutdown", sizeof("session_shutdown") - 1); } - if (PS(mod) && PS(session_status) == php_session_none && PS(mod) != &ps_mod_user) { + if (PS(mod) && PS(session_status) != php_session_active && PS(mod) != &ps_mod_user) { ini_name = zend_string_init("session.save_handler", sizeof("session.save_handler") - 1, 0); ini_val = zend_string_init("user", sizeof("user") - 1, 0); zend_alter_ini_entry(ini_name, ini_val, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); @@ -1829,7 +1908,8 @@ static PHP_FUNCTION(session_set_save_handler) RETURN_TRUE; } - if (argc != 6 && argc != 7) { + /* Set procedural save handler functions */ + if (argc < 6 || PS_NUM_APIS < argc) { WRONG_PARAM_COUNT; } @@ -1840,7 +1920,7 @@ static PHP_FUNCTION(session_set_save_handler) /* remove shutdown function */ remove_user_shutdown_function("session_shutdown", sizeof("session_shutdown") - 1); - /* at this point argc can only be 6 or 7 */ + /* At this point argc can only be between 6 and PS_NUM_APIS */ for (i = 0; i < argc; i++) { if (!zend_is_callable(&args[i], 0, &name)) { php_error_docref(NULL, E_WARNING, "Argument %d is not a valid callback", i+1); @@ -1950,6 +2030,7 @@ static PHP_FUNCTION(session_regenerate_id) RETURN_FALSE; } zend_string_release(PS(id)); + PS(id) = NULL; } PS(id) = PS(mod)->s_create_sid(&PS(mod_data)); @@ -1965,6 +2046,47 @@ static PHP_FUNCTION(session_regenerate_id) } /* }}} */ +/* {{{ proto void session_create_id([string prefix]) + Generate new session ID. Intended for user save handlers. */ +static PHP_FUNCTION(session_create_id) +{ + zend_string *prefix = NULL, *new_id; + smart_str id = {0}; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|S", &prefix) == FAILURE) { + return; + } + + if (prefix && prefix->len) { + if (php_session_valid_key(prefix->val) == FAILURE) { + /* E_ERROR raised for security reason. */ + php_error_docref(NULL, E_WARNING, "Prefix cannot contain special characters. Only aphanumeric, ',', '-' are allowed"); + RETURN_FALSE; + } else { + smart_str_append(&id, prefix); + } + } + + if (PS(session_status) == php_session_active) { + new_id = PS(mod)->s_create_sid(&PS(mod_data)); + } else { + new_id = php_session_create_id(NULL); + } + + if (new_id) { + smart_str_append(&id, new_id); + zend_string_release(new_id); + } else { + smart_str_free(&id); + php_error_docref(NULL, E_WARNING, "Failed to create new ID"); + RETURN_FALSE; + } + smart_str_0(&id); + RETVAL_STR(id.s); + smart_str_free(&id); +} +/* }}} */ + /* {{{ proto string session_cache_limiter([string new_cache_limiter]) Return the current cache limiter. If new_cache_limited is given, the current cache_limiter is replaced with new_cache_limiter */ static PHP_FUNCTION(session_cache_limiter) @@ -2031,33 +2153,83 @@ static PHP_FUNCTION(session_encode) Deserializes data and reinitializes the variables */ static PHP_FUNCTION(session_decode) { - char *str; - size_t str_len; + zend_string *str = NULL; - if (PS(session_status) == php_session_none) { + if (PS(session_status) != php_session_active) { RETURN_FALSE; } - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &str, &str_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { return; } - php_session_decode(str, str_len); + php_session_decode(str); RETURN_TRUE; } /* }}} */ -/* {{{ proto bool session_start(void) - Begin session - reinitializes freezed variables, registers browsers etc */ +static int php_session_start_set_ini(zend_string *varname, zend_string *new_value) { + int ret; + smart_str buf ={0}; + smart_str_appends(&buf, "session"); + smart_str_appendc(&buf, '.'); + smart_str_append(&buf, varname); + smart_str_0(&buf); + ret = zend_alter_ini_entry_ex(buf.s, new_value, PHP_INI_USER, PHP_INI_STAGE_RUNTIME, 0); + smart_str_free(&buf); + return ret; +} + +/* {{{ proto bool session_start([array options]) ++ Begin session */ static PHP_FUNCTION(session_start) { - /* skipping check for non-zero args for performance reasons here ?*/ + zval *options = NULL; + zval *value; + zend_ulong num_idx; + zend_string *str_idx; + int read_and_close = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "|a", &options) == FAILURE) { + RETURN_FALSE; + } + + /* set options */ + if (options) { + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(options), num_idx, str_idx, value) { + switch(Z_TYPE_P(value)) { + case IS_STRING: + case IS_TRUE: + case IS_FALSE: + case IS_LONG: + if (!zend_string_equals_literal(str_idx, "read_and_close")) { + convert_to_boolean(value); + read_and_close = (Z_TYPE_P(value) == IS_TRUE) ? 1 : 0; + } else { + convert_to_string(value); + if (php_session_start_set_ini(str_idx, Z_STR_P(value)) == FAILURE) { + php_error_docref(NULL, E_WARNING, "Setting option '%s' failed", str_idx->val); + } + } + break; + default: + php_error_docref(NULL, E_WARNING, "Option(%s) value must be string, boolean or long", str_idx->val); + break; + } + } ZEND_HASH_FOREACH_END(); + } + php_session_start(); if (PS(session_status) != php_session_active) { RETURN_FALSE; } + + if (read_and_close) { + php_session_flush(); + } + RETURN_TRUE; } /* }}} */ @@ -2078,7 +2250,7 @@ static PHP_FUNCTION(session_destroy) Unset all registered variables */ static PHP_FUNCTION(session_unset) { - if (PS(session_status) == php_session_none) { + if (PS(session_status) != php_session_active) { RETURN_FALSE; } @@ -2197,6 +2369,8 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_session_set_save_handler, 0, 0, 1) ZEND_ARG_INFO(0, destroy) ZEND_ARG_INFO(0, gc) ZEND_ARG_INFO(0, create_sid) + ZEND_ARG_INFO(0, validate_sid) + ZEND_ARG_INFO(0, update_timestamp) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_session_cache_limiter, 0, 0, 0) @@ -2242,6 +2416,15 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_session_class_create_sid, 0) ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_validateId, 0) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_updateTimestamp, 0) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, val) +ZEND_END_ARG_INFO() /* }}} */ /* {{{ session_functions[] @@ -2293,6 +2476,15 @@ static const zend_function_entry php_session_id_iface_functions[] = { }; /* }}} */ +/* {{{ SessionUpdateTimestampHandler functions[] + */ +static const zend_function_entry php_session_update_timestamp_iface_functions[] = { + PHP_ABSTRACT_ME(SessionUpdateTimestampHandlerInterface, validateId, arginfo_session_class_validateId) + PHP_ABSTRACT_ME(SessionUpdateTimestampHandlerInterface, updateTimestamp, arginfo_session_class_updateTimestamp) + { NULL, NULL, NULL } +}; +/* }}} */ + /* {{{ SessionHandler functions[] */ static const zend_function_entry php_session_class_functions[] = { @@ -2362,7 +2554,7 @@ static PHP_RSHUTDOWN_FUNCTION(session) /* {{{ */ php_rshutdown_session_globals(); /* this should NOT be done in php_rshutdown_session_globals() */ - for (i = 0; i < 7; i++) { + for (i = 0; i < PS_NUM_APIS; i++) { if (!Z_ISUNDEF(PS(mod_user_names).names[i])) { zval_ptr_dtor(&PS(mod_user_names).names[i]); ZVAL_UNDEF(&PS(mod_user_names).names[i]); @@ -2391,7 +2583,8 @@ static PHP_GINIT_FUNCTION(ps) /* {{{ */ ps_globals->default_mod = NULL; ps_globals->mod_user_implemented = 0; ps_globals->mod_user_is_open = 0; - for (i = 0; i < 7; i++) { + ps_globals->session_vars = NULL; + for (i = 0; i < PS_NUM_APIS; i++) { ZVAL_UNDEF(&ps_globals->mod_user_names.names[i]); } ZVAL_UNDEF(&ps_globals->http_session_vars); @@ -2424,6 +2617,10 @@ static PHP_MINIT_FUNCTION(session) /* {{{ */ php_session_id_iface_entry = zend_register_internal_class(&ce); php_session_id_iface_entry->ce_flags |= ZEND_ACC_INTERFACE; + INIT_CLASS_ENTRY(ce, PS_UPDATE_TIMESTAMP_IFACE_NAME, php_session_update_timestamp_iface_functions); + php_session_update_timestamp_iface_entry = zend_register_internal_class(&ce TSRMLS_CC); + php_session_update_timestamp_iface_entry->ce_flags |= ZEND_ACC_INTERFACE; + /* Register base class */ INIT_CLASS_ENTRY(ce, PS_CLASS_NAME, php_session_class_functions); php_session_class_entry = zend_register_internal_class(&ce); diff --git a/ext/session/tests/save_handler.inc b/ext/session/tests/save_handler.inc index d271748259..79ebaaabb0 100644 --- a/ext/session/tests/save_handler.inc +++ b/ext/session/tests/save_handler.inc @@ -20,6 +20,8 @@ function read($id) { $session_id = $id; echo "Read [${session_save_path},${id}]\n"; $session_file = "$session_save_path/".SESSION_FILE_PREFIX.$id; + // read MUST create file. Otherwise, strict mode will not work + touch($session_file); return (string) @file_get_contents($session_file); } @@ -31,7 +33,7 @@ function write($id, $session_data) { if ($fp = fopen($session_file, "w")) { $return = fwrite($fp, $session_data); fclose($fp); - return (bool)$return; + return $return === FALSE ? FALSE : TRUE; } return false; } @@ -60,5 +62,42 @@ function gc($maxlifetime) { return true; } +function create_sid() { + $id = ('PHPT-'.time()); + echo "CreateID [${id}]\n"; + return $id; +} + +function validate_sid($id) { + global $session_save_path, $name; + echo "ValidateID [${session_save_path},${id}]\n"; + $session_file = "$session_save_path/".SESSION_FILE_PREFIX.$id; + $ret = file_exists($session_file); + return $ret; +} + +function update($id, $session_data) { + global $session_save_path, $name; + echo "Update [${session_save_path},${id}]\n"; + $session_file = "$session_save_path/".SESSION_FILE_PREFIX.$id; + $ret = touch($session_file); + return $ret; +} + + +function feature() { + /* NOT IMPLEMENTED YET */ + /* TYPES: gc, create_sid, use_strict_mode, minizie_lock, lazy_write + /* VALUES: 0=unknown, 1=supported, 2=partially supported, 3=unsupported */ + return array('gc'=>0, + 'create_sid'=>1, + 'use_strict_mode'=>2, + 'minimize_lock'=>3, + 'lazy_write'=>4, + 'invalid'=>5, + 'another invalid'=>6 + ); +} + ?> diff --git a/ext/session/tests/session_basic1.phpt b/ext/session/tests/session_basic1.phpt new file mode 100644 index 0000000000..616fdfc57a --- /dev/null +++ b/ext/session/tests/session_basic1.phpt @@ -0,0 +1,67 @@ +--TEST-- +Test basic function : variation1 +--INI-- +session.use_strict_mode=0 +session.save_handler=files +session.gc_probability=1 +session.gc_divisor=1000 +session.gc_maxlifetime=300 +session.save_path= +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- +FALSE])); +var_dump(session_write_close()); +var_dump(session_id()); + +echo "*** With lazy_write ***\n"; +var_dump(session_id($session_id)); +var_dump(session_start(['lazy_write'=>TRUE])); +var_dump(session_commit()); +var_dump(session_id()); + +echo "*** Cleanup ***\n"; +var_dump(session_id($session_id)); +var_dump(session_start()); +var_dump(session_destroy()); + +ob_end_flush(); +?> +--EXPECT-- +*** Testing basic session functionality : variation1 *** +string(0) "" +*** Without lazy_write *** +string(6) "testid" +bool(true) +NULL +string(6) "testid" +*** With lazy_write *** +string(6) "testid" +bool(true) +NULL +string(6) "testid" +*** Cleanup *** +string(6) "testid" +bool(true) +bool(true) + diff --git a/ext/session/tests/session_basic2.phpt b/ext/session/tests/session_basic2.phpt new file mode 100644 index 0000000000..179b82971e --- /dev/null +++ b/ext/session/tests/session_basic2.phpt @@ -0,0 +1,83 @@ +--TEST-- +Test basic function : variation2 +--INI-- +session.use_strict_mode=1 +session.save_handler=files +session.hash_bits_per_character=4 +session.hash_function=0 +session.gc_probability=1 +session.gc_divisor=1000 +session.gc_maxlifetime=300 +session.save_path= +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- +FALSE])); +$session_id_new1 = session_id(); +var_dump($session_id_new1 !== $session_id); +var_dump(session_write_close()); +var_dump(session_id()); + +echo "*** With lazy_write ***\n"; +var_dump(session_id($session_id)); +var_dump(session_start(['lazy_write'=>TRUE])); +$session_id_new2 = session_id(); +var_dump($session_id_new1 !== $session_id_new2); +var_dump(session_commit()); +var_dump(session_id()); + +echo "*** Cleanup ***\n"; +ini_set('session.use_strict_mode',0); +var_dump(session_id($session_id_new1)); +var_dump(session_start()); +var_dump(session_destroy()); +var_dump(session_id($session_id_new2)); +var_dump(session_start()); +var_dump(session_destroy()); + +ob_end_flush(); +?> +--EXPECTF-- +*** Testing basic session functionality : variation2 *** +string(0) "" +*** Without lazy_write *** +string(6) "testid" +bool(true) +bool(true) +NULL +string(32) "%s" +*** With lazy_write *** +string(32) "%s" +bool(true) +bool(true) +NULL +string(32) "%s" +*** Cleanup *** +string(32) "%s" +bool(true) +bool(true) +string(0) "" +bool(true) +bool(true) + + diff --git a/ext/session/tests/session_commit_variation3.phpt b/ext/session/tests/session_commit_variation3.phpt index 4cee2f4d58..998e60340f 100644 --- a/ext/session/tests/session_commit_variation3.phpt +++ b/ext/session/tests/session_commit_variation3.phpt @@ -1,5 +1,5 @@ --TEST-- -Test session_start() function : variation +Test session_commit() function : variation --SKIPIF-- --INI-- diff --git a/ext/session/tests/session_commit_variation4.phpt b/ext/session/tests/session_commit_variation4.phpt index 69854a6cf9..fdc4ca5186 100644 --- a/ext/session/tests/session_commit_variation4.phpt +++ b/ext/session/tests/session_commit_variation4.phpt @@ -9,20 +9,22 @@ session.use_strict_mode=0 ob_start(); -/* +/* * Prototype : bool session_commit(void) * Description : Write session data and end session - * Source code : ext/session/session.c + * Source code : ext/session/session.c */ echo "*** Testing session_commit() : variation ***\n"; +var_dump(ini_get('session.use_strict_mode')); var_dump(session_id("test")); var_dump(session_start()); var_dump(session_id()); var_dump(session_commit()); var_dump(session_id()); var_dump(session_start()); +var_dump(ini_get('session.use_strict_mode')); var_dump(session_id()); var_dump(session_commit()); var_dump(session_id()); @@ -32,18 +34,21 @@ var_dump(session_commit()); var_dump(session_id()); var_dump(session_start()); var_dump(session_destroy()); +var_dump(ini_get('session.use_strict_mode')); echo "Done"; ob_end_flush(); ?> --EXPECTF-- *** Testing session_commit() : variation *** +string(1) "0" string(0) "" bool(true) string(4) "test" NULL string(4) "test" bool(true) +string(1) "0" string(4) "test" NULL string(4) "test" @@ -53,5 +58,6 @@ NULL string(4) "test" bool(true) bool(true) +string(1) "0" Done diff --git a/ext/session/tests/session_commit_variation5.phpt b/ext/session/tests/session_commit_variation5.phpt new file mode 100644 index 0000000000..62bd1c1511 --- /dev/null +++ b/ext/session/tests/session_commit_variation5.phpt @@ -0,0 +1,70 @@ +--TEST-- +Test session_commit() function : variation +--SKIPIF-- + +--INI-- +session.use_strict_mode=0 +--FILE-- + +--EXPECTF-- +*** Testing session_commit() : variation *** +string(0) "" +bool(true) +string(32) "%s" +bool(true) +NULL +bool(true) +string(32) "%s" +bool(true) +bool(true) +string(32) "%s" +NULL +bool(true) +string(32) "%s" +bool(true) +bool(true) +string(32) "%s" +NULL +bool(true) +string(32) "%s" +bool(true) +bool(true) +Done + diff --git a/ext/session/tests/session_decode_variation3.phpt b/ext/session/tests/session_decode_variation3.phpt index 4a6f768713..3557efa46d 100644 --- a/ext/session/tests/session_decode_variation3.phpt +++ b/ext/session/tests/session_decode_variation3.phpt @@ -48,8 +48,8 @@ array(3) { float(123.456) } -Warning: session_decode(): Unknown session.serialize_handler. Failed to decode session object in %s on line %d -bool(true) +Warning: session_decode(): Session is not active. You cannot decode session data in %s on line %d +bool(false) array(3) { ["foo"]=> int(1234567890) diff --git a/ext/session/tests/session_set_save_handler_basic.phpt b/ext/session/tests/session_set_save_handler_basic.phpt index e8496e8afb..c8b406e5ab 100644 --- a/ext/session/tests/session_set_save_handler_basic.phpt +++ b/ext/session/tests/session_set_save_handler_basic.phpt @@ -46,6 +46,11 @@ var_dump($_SESSION); $_SESSION['Bar'] = 'Foo'; session_write_close(); +echo "Cleanup..\n"; +session_id($session_id); +session_start(); +session_destroy(); + ob_end_flush(); ?> --EXPECTF-- @@ -94,3 +99,8 @@ array(3) { } Write [%s,%s,Blah|s:12:"Hello World!";Foo|b:0;Guff|i:1234567890;Bar|s:3:"Foo";] Close [%s,PHPSESSID] +Cleanup.. +Open [%s,PHPSESSID] +Read [%s,%s] +Destroy [%s,%s] +Close [%s,PHPSESSID] diff --git a/ext/session/tests/session_set_save_handler_class_001.phpt b/ext/session/tests/session_set_save_handler_class_001.phpt index 83e899a2bc..f1656c3b5b 100644 --- a/ext/session/tests/session_set_save_handler_class_001.phpt +++ b/ext/session/tests/session_set_save_handler_class_001.phpt @@ -1,6 +1,7 @@ --TEST-- Test session_set_save_handler() : basic class wrapping existing handler --INI-- +session.use_strict_mode=1 session.save_handler=files session.name=PHPSESSID --SKIPIF-- @@ -25,11 +26,51 @@ class MySession extends SessionHandler { echo 'Open ', session_id(), "\n"; return parent::open($path, $name); } + public function create_sid() { + // This method should be removed when 5.5 become unsupported. + ++$this->i; + echo 'Old Create SID ', session_id(), "\n"; + return parent::create_sid(); + } public function read($key) { ++$this->i; echo 'Read ', session_id(), "\n"; return parent::read($key); } + public function write($key, $data) { + ++$this->i; + echo 'Write ', session_id(), "\n"; + return parent::write($key, $data); + } + public function close() { + ++$this->i; + echo 'Close ', session_id(), "\n"; + return parent::close(); + } + public function createSid() { + // User should use this rather than create_sid() + // If both create_sid() and createSid() exists, + // createSid() is used. + ++$this->i; + echo 'New Create ID ', session_id(), "\n"; + return parent::create_sid(); + } + public function validateId($key) { + ++$this->i; + echo 'Validate ID ', session_id(), "\n"; + return TRUE; + // User must implement their own method and + // cannot call parent as follows. + // return parent::validateSid($key); + } + public function updateTimestamp($key, $data) { + ++$this->i; + echo 'Update Timestamp ', session_id(), "\n"; + return parent::write($key, $data); + // User must implement their own method and + // cannot call parent as follows + // return parent::updateTimestamp($key, $data); + } } $oldHandler = ini_get('session.save_handler'); @@ -49,20 +90,28 @@ var_dump($_SESSION); session_write_close(); session_unset(); +var_dump($handler->i); --EXPECTF-- *** Testing session_set_save_handler() : basic class wrapping existing handler *** Open +Old Create SID Read %s string(%d) "%s" string(5) "files" string(4) "user" -int(2) +int(3) array(0) { } +Write %s +Close %s Open %s +Validate ID %s Read %s array(1) { ["foo"]=> string(5) "hello" } +Update Timestamp %s +Close %s +int(10) diff --git a/ext/session/tests/session_set_save_handler_class_018.phpt b/ext/session/tests/session_set_save_handler_class_018.phpt new file mode 100644 index 0000000000..7ff06e38cb --- /dev/null +++ b/ext/session/tests/session_set_save_handler_class_018.phpt @@ -0,0 +1,94 @@ +--TEST-- +Test session_set_save_handler() function: class with validate_sid +--INI-- +session.save_handler=files +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- +path = $path . '/u_sess_' . $name; + return true; + } + + public function close() { + return true; + } + + public function read($id) { + return @file_get_contents($this->path . $id); + } + + public function write($id, $data) { + return file_put_contents($this->path . $id, $data)===FALSE ? FALSE : TRUE ; + } + + public function destroy($id) { + @unlink($this->path . $id); + } + + public function gc($maxlifetime) { + foreach (glob($this->path . '*') as $filename) { + if (filemtime($filename) + $maxlifetime < time()) { + @unlink($filename); + } + } + return true; + } + + public function create_sid() { + return 'my_sid'; + } + + public function validate_sid($id) { + return 'my_sid'===$id; + } +} + +$handler = new MySession2; +session_set_save_handler($handler); +session_start(); + +$_SESSION['foo'] = "hello"; + +var_dump(session_id(), ini_get('session.save_handler'), $_SESSION); + +session_write_close(); +session_unset(); + +session_start(); +var_dump($_SESSION); + +session_write_close(); +session_unset(); + +--EXPECTF-- +*** Testing session_set_save_handler() function: class with validate_sid *** +string(%d) "my_sid" +string(4) "user" +array(1) { + ["foo"]=> + string(5) "hello" +} +array(1) { + ["foo"]=> + string(5) "hello" +} diff --git a/ext/session/tests/session_set_save_handler_variation4.phpt b/ext/session/tests/session_set_save_handler_variation4.phpt index c34eb9cd9f..56b8a67f2a 100644 --- a/ext/session/tests/session_set_save_handler_variation4.phpt +++ b/ext/session/tests/session_set_save_handler_variation4.phpt @@ -23,7 +23,7 @@ echo "*** Testing session_set_save_handler() : variation ***\n"; function noisy_gc($maxlifetime) { echo("GC [".$maxlifetime."]\n"); - gc($maxlifetime); + echo gc($maxlifetime)." deleted\n"; return true; } @@ -54,6 +54,7 @@ ob_end_flush(); Open [%s,PHPSESSID] Read [%s,%s] GC [0] +1 deleted array(3) { ["Blah"]=> string(12) "Hello World!" @@ -68,6 +69,7 @@ NULL Open [%s,PHPSESSID] Read [%s,%s] GC [0] +1 deleted array(3) { ["Blah"]=> string(12) "Hello World!" diff --git a/ext/session/tests/session_set_save_handler_variation5.phpt b/ext/session/tests/session_set_save_handler_variation5.phpt new file mode 100644 index 0000000000..e1c0de4d57 --- /dev/null +++ b/ext/session/tests/session_set_save_handler_variation5.phpt @@ -0,0 +1,100 @@ +--TEST-- +Test session_set_save_handler() function : variation +--INI-- +session.use_strict_mode=1 +session.gc_probability=1 +session.gc_divisor=1 +session.gc_maxlifetime=0 +session.save_path= +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- +FALSE])); +var_dump(session_write_close()); +var_dump(session_id()); + +echo "*** With lazy_write ***\n"; +var_dump(session_id($session_id)); +var_dump(session_set_save_handler("open", "close", "read", "write", "destroy", "noisy_gc", "create_sid", "validate_sid", "update")); +var_dump(session_start(['lazy_write'=>TRUE])); +var_dump(session_commit()); +var_dump(session_id()); + +echo "*** Cleanup ***\n"; +var_dump(session_id($session_id)); +var_dump(session_start()); +var_dump(session_destroy()); + +ob_end_flush(); +?> +--EXPECTF-- +*** Testing session_set_save_handler() : variation *** + +string(0) "" +*** Without lazy_write *** +string(0) "" +bool(true) +Open [%s,PHPSESSID] +ValidateID [%s,testid] +CreateID [PHPT-%d] +Read [%s,PHPT-%d] +GC [0] +1 deleted +bool(true) +Write [%s,PHPT-%d,] +Close [%s,PHPSESSID] +NULL +string(15) "PHPT-%d" +*** With lazy_write *** +string(15) "PHPT-%d" +bool(true) +Open [%s,PHPSESSID] +ValidateID [%s,PHPT-%d] +Read [%s,PHPT-%d] +GC [0] +1 deleted +Write [%s,PHPT-%d,] +Close [%s,PHPSESSID] +bool(true) +NULL +string(15) "PHPT-%d" +*** Cleanup *** +string(15) "PHPT-%d" +Open [%s,PHPSESSID] +ValidateID [%s,PHPT-%d] +Read [%s,PHPT-%d] +GC [0] +1 deleted +bool(true) +Destroy [%s,PHPT-%d] + +Warning: unlink(%s/session_test_PHPT-%s): No such file or directory in %s/save_handler.inc on line %d +Close [%s,PHPSESSID] +bool(true) diff --git a/ext/session/tests/session_set_save_handler_write_short_circuit.phpt b/ext/session/tests/session_set_save_handler_variation6.phpt similarity index 88% rename from ext/session/tests/session_set_save_handler_write_short_circuit.phpt rename to ext/session/tests/session_set_save_handler_variation6.phpt index 08da29a8fd..159c85d631 100644 --- a/ext/session/tests/session_set_save_handler_write_short_circuit.phpt +++ b/ext/session/tests/session_set_save_handler_variation6.phpt @@ -1,11 +1,11 @@ --TEST-- -Test session_set_save_handler() function : test write short circuit +Test session_set_save_handler() function : test lazy_write --INI-- +session.lazy_write=1 session.save_path= session.name=PHPSESSID --SKIPIF-- -skip - Waiting RFC patch merge --FILE-- @@ -102,4 +103,5 @@ array(4) { ["Bar"]=> string(3) "Foo" } +Update [%s,PHPT-%d] Close [%s,PHPSESSID] \ No newline at end of file diff --git a/ext/session/tests/session_start_error.phpt b/ext/session/tests/session_start_error.phpt index 8cbf42c860..5a0bcefc2a 100644 --- a/ext/session/tests/session_start_error.phpt +++ b/ext/session/tests/session_start_error.phpt @@ -97,99 +97,194 @@ ob_end_flush(); *** Testing session_start() : error functionality *** -- Iteration 1 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, integer given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 2 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, integer given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 3 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, integer given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 4 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, integer given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 5 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, float given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 6 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, float given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 7 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, float given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 8 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, float given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 9 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, float given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 10 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, null given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 11 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, null given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 12 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, boolean given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 13 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, boolean given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 14 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, boolean given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 15 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, boolean given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 16 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, string given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 17 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, string given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 18 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, string given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 19 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, string given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 20 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, string given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 21 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, object given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 22 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, null given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 23 -- -bool(true) -bool(true) + +Warning: session_start() expects parameter 1 to be array, null given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) -- Iteration 24 -- -bool(true) -bool(true) -Done +Warning: session_start() expects parameter 1 to be array, resource given in %s on line %d +bool(false) + +Warning: session_destroy(): Trying to destroy uninitialized session in %s on line %d +bool(false) +Done -- 2.49.0