From 47cfae87eb5a51a2d90c6363ca36b109e8253605 Mon Sep 17 00:00:00 2001 From: Arpad Ray Date: Tue, 13 Sep 2011 22:28:15 +0000 Subject: Implement object-oriented session handlers (https://wiki.php.net/rfc/session-oo) --- NEWS | 1 + ext/session/config.m4 | 2 +- ext/session/config.w32 | 2 +- ext/session/mod_user.c | 29 ++-- ext/session/mod_user_class.c | 144 ++++++++++++++++ ext/session/package.xml | 1 + ext/session/php_session.h | 13 ++ ext/session/session.c | 182 ++++++++++++++++++++- .../tests/session_set_save_handler_class_001.phpt | 68 ++++++++ .../tests/session_set_save_handler_class_002.phpt | 113 +++++++++++++ .../tests/session_set_save_handler_class_003.phpt | 78 +++++++++ .../tests/session_set_save_handler_class_004.phpt | 48 ++++++ .../tests/session_set_save_handler_class_005.phpt | 54 ++++++ .../tests/session_set_save_handler_class_006.phpt | 54 ++++++ .../tests/session_set_save_handler_class_007.phpt | 74 +++++++++ .../tests/session_set_save_handler_class_008.phpt | 65 ++++++++ .../tests/session_set_save_handler_class_009.phpt | 62 +++++++ .../tests/session_set_save_handler_class_010.phpt | 63 +++++++ .../tests/session_set_save_handler_class_011.phpt | 66 ++++++++ .../tests/session_set_save_handler_class_012.phpt | 59 +++++++ .../tests/session_set_save_handler_class_013.phpt | 56 +++++++ .../tests/session_set_save_handler_class_014.phpt | 32 ++++ ext/standard/basic_functions.c | 37 ++++- ext/standard/basic_functions.h | 9 + 24 files changed, 1279 insertions(+), 33 deletions(-) create mode 100644 ext/session/mod_user_class.c create mode 100644 ext/session/tests/session_set_save_handler_class_001.phpt create mode 100644 ext/session/tests/session_set_save_handler_class_002.phpt create mode 100644 ext/session/tests/session_set_save_handler_class_003.phpt create mode 100644 ext/session/tests/session_set_save_handler_class_004.phpt create mode 100644 ext/session/tests/session_set_save_handler_class_005.phpt create mode 100644 ext/session/tests/session_set_save_handler_class_006.phpt create mode 100644 ext/session/tests/session_set_save_handler_class_007.phpt create mode 100644 ext/session/tests/session_set_save_handler_class_008.phpt create mode 100644 ext/session/tests/session_set_save_handler_class_009.phpt create mode 100644 ext/session/tests/session_set_save_handler_class_010.phpt create mode 100644 ext/session/tests/session_set_save_handler_class_011.phpt create mode 100644 ext/session/tests/session_set_save_handler_class_012.phpt create mode 100644 ext/session/tests/session_set_save_handler_class_013.phpt create mode 100644 ext/session/tests/session_set_save_handler_class_014.phpt diff --git a/NEWS b/NEWS index 92dcfe705d..41f394da1c 100644 --- a/NEWS +++ b/NEWS @@ -54,6 +54,7 @@ PHP NEWS - Improved Session extension: . Expose session status via new function, session_status (FR #52982) (Arpad) + . Added support for object-oriented session handlers. (Arpad) - Improved SPL extension: . Immediately reject wrong usages of directories under Spl(Temp)FileObject diff --git a/ext/session/config.m4 b/ext/session/config.m4 index d65e872a3a..1c3ba78368 100644 --- a/ext/session/config.m4 +++ b/ext/session/config.m4 @@ -11,7 +11,7 @@ PHP_ARG_WITH(mm,for mm support, if test "$PHP_SESSION" != "no"; then PHP_PWRITE_TEST PHP_PREAD_TEST - PHP_NEW_EXTENSION(session, session.c mod_files.c mod_mm.c mod_user.c, $ext_shared) + PHP_NEW_EXTENSION(session, mod_user_class.c session.c mod_files.c mod_mm.c mod_user.c, $ext_shared) PHP_ADD_EXTENSION_DEP(session, hash, true) PHP_ADD_EXTENSION_DEP(session, spl) PHP_SUBST(SESSION_SHARED_LIBADD) diff --git a/ext/session/config.w32 b/ext/session/config.w32 index 531fed1fb6..c8b217aad9 100644 --- a/ext/session/config.w32 +++ b/ext/session/config.w32 @@ -4,7 +4,7 @@ ARG_ENABLE("session", "session support", "yes"); if (PHP_SESSION == "yes") { - EXTENSION("session", "session.c mod_files.c mod_mm.c mod_user.c", false /* never shared */); + EXTENSION("session", "mod_user_class.c session.c mod_files.c mod_mm.c mod_user.c", false /* never shared */); AC_DEFINE("HAVE_PHP_SESSION", 1, "Session support"); PHP_INSTALL_HEADERS("ext/session/", "mod_mm.h php_session.h mod_files.h mod_user.h"); } diff --git a/ext/session/mod_user.c b/ext/session/mod_user.c index 61a3586bd0..f643b9eee6 100644 --- a/ext/session/mod_user.c +++ b/ext/session/mod_user.c @@ -62,15 +62,10 @@ static zval *ps_call_handler(zval *func, int argc, zval **argv TSRMLS_DC) return retval; } -#define STDVARS1 \ +#define STDVARS \ zval *retval; \ int ret = FAILURE -#define STDVARS \ - STDVARS1; \ - char *mdata = PS_GET_MOD_DATA(); \ - if (!mdata) { return FAILURE; } - #define PSF(a) PS(mod_user_names).name.ps_##a #define FINISH \ @@ -84,32 +79,28 @@ static zval *ps_call_handler(zval *func, int argc, zval **argv TSRMLS_DC) PS_OPEN_FUNC(user) { zval *args[2]; - static char dummy = 0; - STDVARS1; + STDVARS; SESS_ZVAL_STRING((char*)save_path, args[0]); SESS_ZVAL_STRING((char*)session_name, args[1]); retval = ps_call_handler(PSF(open), 2, args TSRMLS_CC); - if (retval) { - /* This is necessary to fool the session module. Yes, it's safe to - * use a static. Neither mod_user nor the session module itself will - * ever touch this pointer. It could be set to 0xDEADBEEF for all the - * difference it makes, but for the sake of paranoia it's set to some - * valid value. */ - PS_SET_MOD_DATA(&dummy); - } + PS(mod_user_implemented) = 1; FINISH; } PS_CLOSE_FUNC(user) { - STDVARS1; + STDVARS; - retval = ps_call_handler(PSF(close), 0, NULL TSRMLS_CC); + if (!PS(mod_user_implemented)) { + /* already closed */ + return SUCCESS; + } - PS_SET_MOD_DATA(NULL); + retval = ps_call_handler(PSF(close), 0, NULL TSRMLS_CC); + PS(mod_user_implemented) = 0; FINISH; } diff --git a/ext/session/mod_user_class.c b/ext/session/mod_user_class.c new file mode 100644 index 0000000000..efc39c3fa6 --- /dev/null +++ b/ext/session/mod_user_class.c @@ -0,0 +1,144 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2011 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Arpad Ray | + +----------------------------------------------------------------------+ + */ + +/* $Id$ */ + +#include "php.h" +#include "php_session.h" + +#define PS_SANITY_CHECK \ + if (PS(default_mod) == NULL) { \ + php_error_docref(NULL TSRMLS_CC, E_CORE_ERROR, "Called default SessionHandler but session.save_handler is user"); \ + RETURN_FALSE; \ + } + +#define PS_SANITY_CHECK_IS_OPEN \ + PS_SANITY_CHECK; \ + if (!PS(mod_user_is_open)) { \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Parent session handler is not open"); \ + RETURN_FALSE; \ + } + +/* {{{ proto bool SessionHandler::open(string save_path, string session_name) + Wraps the old open handler */ +PHP_METHOD(SessionHandler, open) +{ + char *save_path = NULL, *session_name = NULL; + int save_path_len, session_name_len; + + PS_SANITY_CHECK; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &save_path, &save_path_len, &session_name, &session_name_len) == FAILURE) { + return; + } + + PS(mod_user_is_open) = 1; + RETVAL_LONG(PS(default_mod)->s_open(&PS(mod_data), save_path, session_name TSRMLS_CC)); +} +/* }}} */ + +/* {{{ proto bool SessionHandler::close() + Wraps the old close handler */ +PHP_METHOD(SessionHandler, close) +{ + PS_SANITY_CHECK_IS_OPEN; + + // don't return on failure, since not closing the default handler + // could result in memory leaks or other nasties + zend_parse_parameters_none(); + + PS(mod_user_is_open) = 0; + RETVAL_LONG(PS(default_mod)->s_close(&PS(mod_data) TSRMLS_CC)); +} +/* }}} */ + +/* {{{ proto bool SessionHandler::read(string id) + Wraps the old read handler */ +PHP_METHOD(SessionHandler, read) +{ + char *key, *val; + int key_len, val_len; + + PS_SANITY_CHECK_IS_OPEN; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) == FAILURE) { + return; + } + + if (PS(default_mod)->s_read(&PS(mod_data), key, &val, &val_len TSRMLS_CC) == FAILURE) { + RETVAL_FALSE; + return; + } + + RETVAL_STRINGL(val, val_len, 1); + efree(val); + return; +} +/* }}} */ + +/* {{{ proto bool SessionHandler::write(string id, string data) + Wraps the old write handler */ +PHP_METHOD(SessionHandler, write) +{ + char *key, *val; + int key_len, val_len; + + PS_SANITY_CHECK_IS_OPEN; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &key, &key_len, &val, &val_len) == FAILURE) { + return; + } + + RETVAL_LONG(PS(default_mod)->s_write(&PS(mod_data), key, val, val_len TSRMLS_CC)); +} +/* }}} */ + +/* {{{ proto bool SessionHandler::destroy(string id) + Wraps the old destroy handler */ +PHP_METHOD(SessionHandler, destroy) +{ + char *key; + int key_len; + + PS_SANITY_CHECK_IS_OPEN; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) == FAILURE) { + return; + } + + PS(mod_user_is_open) = 0; + RETVAL_LONG(PS(default_mod)->s_destroy(&PS(mod_data), key TSRMLS_CC)); +} +/* }}} */ + +/* {{{ proto bool SessionHandler::gc(int maxlifetime) + Wraps the old gc handler */ +PHP_METHOD(SessionHandler, gc) +{ + long maxlifetime; + int nrdels; + + PS_SANITY_CHECK; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &maxlifetime) == FAILURE) { + return; + } + + RETVAL_LONG(PS(default_mod)->s_gc(&PS(mod_data), maxlifetime, &nrdels TSRMLS_CC)); +} +/* }}} */ diff --git a/ext/session/package.xml b/ext/session/package.xml index fdf85fc447..03fcbf66da 100644 --- a/ext/session/package.xml +++ b/ext/session/package.xml @@ -40,6 +40,7 @@ package.xml added to support installation using pear installer + diff --git a/ext/session/php_session.h b/ext/session/php_session.h index 767ed48c88..b11aad2074 100644 --- a/ext/session/php_session.h +++ b/ext/session/php_session.h @@ -129,6 +129,7 @@ typedef struct _php_ps_globals { zend_bool cookie_secure; zend_bool cookie_httponly; ps_module *mod; + ps_module *default_mod; void *mod_data; php_session_status session_status; long gc_probability; @@ -147,6 +148,8 @@ typedef struct _php_ps_globals { zval *ps_gc; } name; } mod_user_names; + int mod_user_implemented; + int mod_user_is_open; const struct ps_serializer_struct *serializer; zval *http_session_vars; zend_bool auto_start; @@ -268,4 +271,14 @@ PHPAPI ZEND_EXTERN_MODULE_GLOBALS(ps) void php_session_auto_start(void *data); void php_session_shutdown(void *data); +#define PS_CLASS_NAME "SessionHandler" +extern zend_class_entry *php_session_class_entry; + +extern PHP_METHOD(SessionHandler, open); +extern PHP_METHOD(SessionHandler, close); +extern PHP_METHOD(SessionHandler, read); +extern PHP_METHOD(SessionHandler, write); +extern PHP_METHOD(SessionHandler, destroy); +extern PHP_METHOD(SessionHandler, gc); + #endif diff --git a/ext/session/session.c b/ext/session/session.c index 4e2578a447..bd4efd0cdb 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -50,6 +50,7 @@ #include "ext/standard/info.h" #include "ext/standard/php_smart_str.h" #include "ext/standard/url.h" +#include "ext/standard/basic_functions.h" #include "mod_files.h" #include "mod_user.h" @@ -63,6 +64,9 @@ PHPAPI ZEND_DECLARE_MODULE_GLOBALS(ps); static int php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra TSRMLS_DC); static int (*php_session_rfc1867_orig_callback)(unsigned int event, void *event_data, void **extra TSRMLS_DC); +/* SessionHandler class */ +zend_class_entry *php_session_class_entry; + /* *********** * Helpers * *********** */ @@ -82,6 +86,7 @@ static inline void php_rinit_session_globals(TSRMLS_D) /* {{{ */ 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(http_session_vars) = NULL; } @@ -95,7 +100,7 @@ static inline void php_rshutdown_session_globals(TSRMLS_D) /* {{{ */ PS(http_session_vars) = NULL; } /* Do NOT destroy PS(mod_user_names) here! */ - if (PS(mod_data)) { + if (PS(mod_data) || PS(mod_user_implemented)) { zend_try { PS(mod)->s_close(&PS(mod_data) TSRMLS_CC); } zend_end_try(); @@ -472,7 +477,7 @@ static void php_session_save_current_state(TSRMLS_D) /* {{{ */ int ret = FAILURE; IF_SESSION_VARS() { - if (PS(mod_data)) { + if (PS(mod_data) || PS(mod_user_implemented)) { char *val; int vallen; @@ -494,7 +499,7 @@ static void php_session_save_current_state(TSRMLS_D) /* {{{ */ } } - if (PS(mod_data)) { + if (PS(mod_data) || PS(mod_user_implemented)) { PS(mod)->s_close(&PS(mod_data) TSRMLS_CC); } } @@ -526,6 +531,8 @@ static PHP_INI_MH(OnUpdateSaveHandler) /* {{{ */ } return FAILURE; } + + PS(default_mod) = PS(mod); PS(mod) = tmp; return SUCCESS; @@ -1420,7 +1427,7 @@ PHPAPI void php_session_start(TSRMLS_D) /* {{{ */ php_session_cache_limiter(TSRMLS_C); - if (PS(mod_data) && PS(gc_probability) > 0) { + if ((PS(mod_data) || PS(mod_user_implemented)) && PS(gc_probability) > 0) { int nrdels = -1; nrand = (int) ((float) PS(gc_divisor) * php_combined_lcg(TSRMLS_C)); @@ -1555,7 +1562,7 @@ static PHP_FUNCTION(session_module_name) zval_dtor(return_value); RETURN_FALSE; } - if (PS(mod_data)) { + if (PS(mod_data) || PS(mod_user_implemented)) { PS(mod)->s_close(&PS(mod_data) TSRMLS_CC); } PS(mod_data) = NULL; @@ -1577,14 +1584,86 @@ static PHP_FUNCTION(session_set_save_handler) RETURN_FALSE; } - if (argc != 6) { + if (argc != 1 && argc != 2 && argc != 6) { WRONG_PARAM_COUNT; } + if (argc <= 2) { + zval *obj = NULL, *callback = NULL; + zend_uint func_name_len; + char *func_name; + HashPosition pos; + zend_function *default_mptr, *current_mptr; + ulong func_index; + php_shutdown_function_entry shutdown_function_entry; + zend_bool register_shutdown = 1; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|b", &obj, php_session_class_entry, ®ister_shutdown) == FAILURE) { + return; + } + + /* Find implemented methods */ + zend_hash_internal_pointer_reset_ex(&php_session_class_entry->function_table, &pos); + i = 0; + while (zend_hash_get_current_data_ex(&php_session_class_entry->function_table, (void **) &default_mptr, &pos) == SUCCESS) { + zend_hash_get_current_key_ex(&php_session_class_entry->function_table, &func_name, &func_name_len, &func_index, 0, &pos); + + if (zend_hash_find(&Z_OBJCE_P(obj)->function_table, func_name, func_name_len, (void **)¤t_mptr) == SUCCESS) { + if (PS(mod_user_names).names[i] != NULL) { + zval_ptr_dtor(&PS(mod_user_names).names[i]); + } + + MAKE_STD_ZVAL(callback); + array_init_size(callback, 2); + Z_ADDREF_P(obj); + add_next_index_zval(callback, obj); + add_next_index_stringl(callback, func_name, func_name_len - 1, 1); + PS(mod_user_names).names[i] = callback; + } else { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Session handler's function table is corrupt"); + RETURN_FALSE; + } + + zend_hash_move_forward_ex(&php_session_class_entry->function_table, &pos); + ++i; + } + + if (register_shutdown) { + /* create shutdown function */ + shutdown_function_entry.arg_count = 1; + shutdown_function_entry.arguments = (zval **) safe_emalloc(sizeof(zval *), 1, 0); + + MAKE_STD_ZVAL(callback); + ZVAL_STRING(callback, "session_register_shutdown", 1); + shutdown_function_entry.arguments[0] = callback; + + /* add shutdown function, removing the old one if it exists */ + if (!register_user_shutdown_function("session_shutdown", &shutdown_function_entry)) { + zval_ptr_dtor(&callback); + efree(shutdown_function_entry.arguments); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to register session shutdown function"); + RETURN_FALSE; + } + } else { + /* remove shutdown function */ + remove_user_shutdown_function("session_shutdown"); + } + + PS(mod_user_implemented) = 1; + if (PS(mod) && PS(session_status) == php_session_none && PS(mod) != &ps_mod_user) { + zend_alter_ini_entry("session.save_handler", sizeof("session.save_handler"), "user", sizeof("user")-1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + } + + RETURN_TRUE; + } + if (zend_parse_parameters(argc TSRMLS_CC, "+", &args, &num_args) == FAILURE) { return; } + /* remove shutdown function */ + remove_user_shutdown_function("session_shutdown"); + for (i = 0; i < 6; i++) { if (!zend_is_callable(*args[i], 0, &name TSRMLS_CC)) { efree(args); @@ -1594,8 +1673,12 @@ static PHP_FUNCTION(session_set_save_handler) } efree(name); } + + PS(mod_user_implemented) = 1; - zend_alter_ini_entry("session.save_handler", sizeof("session.save_handler"), "user", sizeof("user")-1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + if (PS(mod) && PS(mod) != &ps_mod_user) { + zend_alter_ini_entry("session.save_handler", sizeof("session.save_handler"), "user", sizeof("user")-1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME); + } for (i = 0; i < 6; i++) { if (PS(mod_user_names).names[i] != NULL) { @@ -1842,6 +1925,43 @@ static PHP_FUNCTION(session_status) } /* }}} */ +/* {{{ proto void session_register_shutdown(void) + Registers session_write_close() as a shutdown function */ +static PHP_FUNCTION(session_register_shutdown) +{ + php_shutdown_function_entry shutdown_function_entry; + zval *callback; + + /* This function is registered itself as a shutdown function by + * session_set_save_handler($obj). The reason we now register another + * shutdown function is in case the user registered their own shutdown + * function after calling session_set_save_handler(), which expects + * the session still to be available. + */ + + shutdown_function_entry.arg_count = 1; + shutdown_function_entry.arguments = (zval **) safe_emalloc(sizeof(zval *), 1, 0); + + MAKE_STD_ZVAL(callback); + ZVAL_STRING(callback, "session_write_close", 1); + shutdown_function_entry.arguments[0] = callback; + + if (!append_user_shutdown_function(shutdown_function_entry)) { + zval_ptr_dtor(&callback); + efree(shutdown_function_entry.arguments); + + /* Unable to register shutdown function, presumably because of lack + * of memory, so flush the session now. It would be done in rshutdown + * anyway but the handler will have had it's dtor called by then. + * If the user does have a later shutdown function which needs the + * session then tough luck. + */ + php_session_flush(TSRMLS_C); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to register session flush function"); + } +} +/* }}} */ + /* {{{ arginfo */ ZEND_BEGIN_ARG_INFO_EX(arginfo_session_name, 0, 0, 0) ZEND_ARG_INFO(0, name) @@ -1894,6 +2014,31 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_session_set_cookie_params, 0, 0, 1) ZEND_ARG_INFO(0, secure) ZEND_ARG_INFO(0, httponly) ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_open, 0) + ZEND_ARG_INFO(0, save_path) + ZEND_ARG_INFO(0, session_name) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_close, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_read, 0) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_write, 0) + ZEND_ARG_INFO(0, key) + ZEND_ARG_INFO(0, val) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_destroy, 0) + ZEND_ARG_INFO(0, key) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_session_class_gc, 0) + ZEND_ARG_INFO(0, maxlifetime) +ZEND_END_ARG_INFO() /* }}} */ /* {{{ session_functions[] @@ -1916,11 +2061,25 @@ static const zend_function_entry session_functions[] = { PHP_FE(session_get_cookie_params, arginfo_session_void) PHP_FE(session_write_close, arginfo_session_void) PHP_FE(session_status, arginfo_session_void) + PHP_FE(session_register_shutdown, arginfo_session_void) PHP_FALIAS(session_commit, session_write_close, arginfo_session_void) PHP_FE_END }; /* }}} */ +/* {{{ session class functions[] + */ +static const zend_function_entry php_session_class_functions[] = { + PHP_ME(SessionHandler, open, arginfo_session_class_open, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, close, arginfo_session_class_close, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, read, arginfo_session_class_read, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, write, arginfo_session_class_write, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, destroy, arginfo_session_class_destroy, ZEND_ACC_PUBLIC) + PHP_ME(SessionHandler, gc, arginfo_session_class_gc, ZEND_ACC_PUBLIC) + { NULL, NULL, NULL } +}; +/* }}} */ + /* ******************************** * Module Setup and Destruction * ******************************** */ @@ -1996,6 +2155,9 @@ static PHP_GINIT_FUNCTION(ps) /* {{{ */ ps_globals->serializer = NULL; ps_globals->mod_data = NULL; ps_globals->session_status = php_session_none; + ps_globals->default_mod = NULL; + ps_globals->mod_user_implemented = 0; + ps_globals->mod_user_is_open = 0; for (i = 0; i < 6; i++) { ps_globals->mod_user_names.names[i] = NULL; } @@ -2005,6 +2167,8 @@ static PHP_GINIT_FUNCTION(ps) /* {{{ */ static PHP_MINIT_FUNCTION(session) /* {{{ */ { + zend_class_entry ce; + zend_register_auto_global("_SESSION", sizeof("_SESSION")-1, 0, NULL TSRMLS_CC); PS(module_number) = module_number; /* if we really need this var we need to init it in zts mode as well! */ @@ -2018,6 +2182,10 @@ static PHP_MINIT_FUNCTION(session) /* {{{ */ php_session_rfc1867_orig_callback = php_rfc1867_callback; php_rfc1867_callback = php_session_rfc1867_callback; + /* Register base class */ + INIT_CLASS_ENTRY(ce, PS_CLASS_NAME, php_session_class_functions); + php_session_class_entry = zend_register_internal_class(&ce TSRMLS_CC); + REGISTER_LONG_CONSTANT("PHP_SESSION_DISABLED", php_session_disabled, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PHP_SESSION_NONE", php_session_none, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("PHP_SESSION_ACTIVE", php_session_active, CONST_CS | CONST_PERSISTENT); diff --git a/ext/session/tests/session_set_save_handler_class_001.phpt b/ext/session/tests/session_set_save_handler_class_001.phpt new file mode 100644 index 0000000000..667456cc1d --- /dev/null +++ b/ext/session/tests/session_set_save_handler_class_001.phpt @@ -0,0 +1,68 @@ +--TEST-- +Test session_set_save_handler() : basic class wrapping existing handler +--INI-- +session.save_handler=files +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- +i; + echo 'Open ', session_id(), "\n"; + return parent::open($path, $name); + } + public function read($key) { + ++$this->i; + echo 'Read ', session_id(), "\n"; + return parent::read($key); + } +} + +$oldHandler = ini_get('session.save_handler'); +$handler = new MySession; +session_set_save_handler($handler); +session_start(); + +var_dump(session_id(), $oldHandler, ini_get('session.save_handler'), $handler->i, $_SESSION); + +$_SESSION['foo'] = "hello"; + +session_write_close(); +session_unset(); + +session_start(); +var_dump($_SESSION); + +session_write_close(); +session_unset(); + +--EXPECTF-- +*** Testing session_set_save_handler() : basic class wrapping existing handler *** +Open +Read %x +string(32) "%x" +string(5) "files" +string(4) "user" +int(2) +array(0) { +} +Open %x +Read %x +array(1) { + ["foo"]=> + string(5) "hello" +} diff --git a/ext/session/tests/session_set_save_handler_class_002.phpt b/ext/session/tests/session_set_save_handler_class_002.phpt new file mode 100644 index 0000000000..e907b2132c --- /dev/null +++ b/ext/session/tests/session_set_save_handler_class_002.phpt @@ -0,0 +1,113 @@ +--TEST-- +Test session_set_save_handler() : full handler implementation +--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); + } + + 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; + } +} + +$handler = new MySession2; +session_set_save_handler(array($handler, 'open'), array($handler, 'close'), + array($handler, 'read'), array($handler, 'write'), array($handler, 'destroy'), array($handler, 'gc')); +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(); + +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() : full handler implementation *** +string(32) "%x" +string(4) "user" +array(1) { + ["foo"]=> + string(5) "hello" +} +array(1) { + ["foo"]=> + string(5) "hello" +} +string(32) "%x" +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_class_003.phpt b/ext/session/tests/session_set_save_handler_class_003.phpt new file mode 100644 index 0000000000..858a2e2a6a --- /dev/null +++ b/ext/session/tests/session_set_save_handler_class_003.phpt @@ -0,0 +1,78 @@ +--TEST-- +Test session_set_save_handler() : inheritance +--INI-- +session.save_handler=files +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- +i; + return parent::open($path, $name); + } + public function read($key) { + ++$this->i; + return parent::read($key); + } +} + +class MySession4 extends MySession3 { + public function write($id, $data) { + $this->i = "hai"; + return parent::write($id, $data); + } +} + +$handler = new MySession3; +session_set_save_handler($handler); +session_start(); + +$_SESSION['foo'] = "hello"; + +session_write_close(); +session_unset(); + +session_start(); + +var_dump($_SESSION, $handler->i); + +session_write_close(); +session_unset(); + +$handler = new MySession4; +session_set_save_handler($handler); + +session_start(); + +session_write_close(); +session_unset(); + +var_dump(session_id(), $_SESSION, $handler->i); + +--EXPECTF-- +*** Testing session_set_save_handler() : inheritance *** +array(1) { + ["foo"]=> + string(5) "hello" +} +int(4) +string(32) "%x" +array(1) { + ["foo"]=> + string(5) "hello" +} +string(3) "hai" diff --git a/ext/session/tests/session_set_save_handler_class_004.phpt b/ext/session/tests/session_set_save_handler_class_004.phpt new file mode 100644 index 0000000000..b9a5f4581f --- /dev/null +++ b/ext/session/tests/session_set_save_handler_class_004.phpt @@ -0,0 +1,48 @@ +--TEST-- +Test session_set_save_handler() : default object +--INI-- +session.save_handler=files +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- + + string(5) "hello" +} +array(1) { + ["foo"]=> + string(5) "hello" +} diff --git a/ext/session/tests/session_set_save_handler_class_005.phpt b/ext/session/tests/session_set_save_handler_class_005.phpt new file mode 100644 index 0000000000..4a7a54d1e5 --- /dev/null +++ b/ext/session/tests/session_set_save_handler_class_005.phpt @@ -0,0 +1,54 @@ +--TEST-- +Test session_set_save_handler() : incomplete implementation +--INI-- +session.save_handler=files +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- + +--FILE-- +state = 'destroyed'; + } +} + +class MySession7 extends SessionHandler { + public $foo; + public function close() { + var_dump($this->foo); + @var_dump($GLOBALS['bar']); + return parent::close(); + } +} + +$bar = new MySession7_Foo; +$handler = new MySession7; +$handler->foo = new MySession7_Foo; +session_set_save_handler($handler); +session_start(); + +ob_end_flush(); +?> +--EXPECTF-- +*** Testing session_set_save_handler() : using objects in close *** +object(MySession7_Foo)#%d (%d) { + ["state"]=> + string(2) "ok" +} +object(MySession7_Foo)#%d (%d) { + ["state"]=> + string(2) "ok" +} diff --git a/ext/session/tests/session_set_save_handler_class_007.phpt b/ext/session/tests/session_set_save_handler_class_007.phpt new file mode 100644 index 0000000000..8d71ce44a8 --- /dev/null +++ b/ext/session/tests/session_set_save_handler_class_007.phpt @@ -0,0 +1,74 @@ +--TEST-- +Test session_set_save_handler() : manual shutdown, reopen +--INI-- +session.save_handler=files +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- +num = $num; + echo "(#$this->num) constructor called\n"; + } + public function __destruct() { + echo "(#$this->num) destructor called\n"; + } + public function finish() { + $id = session_id(); + echo "(#$this->num) finish called $id\n"; + session_write_close(); + } + public function write($id, $data) { + echo "(#$this->num) writing $id = $data\n"; + return parent::write($id, $data); + } + public function close() { + $id = session_id(); + echo "(#$this->num) closing $id\n"; + return parent::close(); + } +} + +$handler = new MySession(1); +session_set_save_handler($handler); +session_start(); + +$_SESSION['foo'] = 'bar'; + +// explicit close +$handler->finish(); + +$handler = new MySession(2); +session_set_save_handler($handler); +session_start(); + +// implicit close (called by shutdown function) +echo "done\n"; +ob_end_flush(); +?> +--EXPECTF-- +*** Testing session_set_save_handler() : manual shutdown, reopen *** +(#1) constructor called +(#1) finish called %x +(#1) writing %x = foo|s:3:"bar"; +(#1) closing %x +(#2) constructor called +(#1) destructor called +done +(#2) writing %x = foo|s:3:"bar"; +(#2) closing %x +(#2) destructor called diff --git a/ext/session/tests/session_set_save_handler_class_008.phpt b/ext/session/tests/session_set_save_handler_class_008.phpt new file mode 100644 index 0000000000..799592045b --- /dev/null +++ b/ext/session/tests/session_set_save_handler_class_008.phpt @@ -0,0 +1,65 @@ +--TEST-- +Test session_set_save_handler() : manual shutdown +--INI-- +session.save_handler=files +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- +num = $num; + echo "(#$this->num) constructor called\n"; + } + public function __destruct() { + echo "(#$this->num) destructor called\n"; + } + public function finish() { + $id = session_id(); + echo "(#$this->num) finish called $id\n"; + session_write_close(); + } + public function write($id, $data) { + echo "(#$this->num) writing $id = $data\n"; + return parent::write($id, $data); + } + public function close() { + $id = session_id(); + echo "(#$this->num) closing $id\n"; + return parent::close(); + } +} + +$handler = new MySession(1); +session_set_save_handler($handler); +session_start(); + +$_SESSION['foo'] = 'bar'; + +// explicit close +$handler->finish(); + +echo "done\n"; +ob_end_flush(); +?> +--EXPECTF-- +*** Testing session_set_save_handler() : manual shutdown *** +(#1) constructor called +(#1) finish called %x +(#1) writing %x = foo|s:3:"bar"; +(#1) closing %x +done +(#1) destructor called diff --git a/ext/session/tests/session_set_save_handler_class_009.phpt b/ext/session/tests/session_set_save_handler_class_009.phpt new file mode 100644 index 0000000000..33787448c0 --- /dev/null +++ b/ext/session/tests/session_set_save_handler_class_009.phpt @@ -0,0 +1,62 @@ +--TEST-- +Test session_set_save_handler() : implicit shutdown +--INI-- +session.save_handler=files +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- +num = $num; + echo "(#$this->num) constructor called\n"; + } + public function __destruct() { + echo "(#$this->num) destructor called\n"; + } + public function finish() { + $id = session_id(); + echo "(#$this->num) finish called $id\n"; + $this->shutdown(); + } + public function write($id, $data) { + echo "(#$this->num) writing $id = $data\n"; + return parent::write($id, $data); + } + public function close() { + $id = session_id(); + echo "(#$this->num) closing $id\n"; + return parent::close(); + } +} + +$handler = new MySession(1); +session_set_save_handler($handler); +session_start(); + +$_SESSION['foo'] = 'bar'; + +// implicit close +echo "done\n"; +ob_end_flush(); +?> +--EXPECTF-- +*** Testing session_set_save_handler() : implicit shutdown *** +(#1) constructor called +done +(#1) writing %x = foo|s:3:"bar"; +(#1) closing %x +(#1) destructor called diff --git a/ext/session/tests/session_set_save_handler_class_010.phpt b/ext/session/tests/session_set_save_handler_class_010.phpt new file mode 100644 index 0000000000..e60134ff75 --- /dev/null +++ b/ext/session/tests/session_set_save_handler_class_010.phpt @@ -0,0 +1,63 @@ +--TEST-- +Test session_set_save_handler() : manual shutdown function +--INI-- +session.save_handler=files +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- +num = $num; + echo "(#$this->num) constructor called\n"; + } + public function __destruct() { + echo "(#$this->num) destructor called\n"; + } + public function finish() { + $id = session_id(); + echo "(#$this->num) finish called $id\n"; + session_write_close(); + } + public function write($id, $data) { + echo "(#$this->num) writing $id = $data\n"; + return parent::write($id, $data); + } + public function close() { + $id = session_id(); + echo "(#$this->num) closing $id\n"; + return parent::close(); + } +} + +$handler = new MySession(1); +session_set_save_handler($handler, false); +register_shutdown_function(array($handler, 'finish')); +session_start(); + +$_SESSION['foo'] = 'bar'; + +echo "done\n"; +ob_end_flush(); +?> +--EXPECTF-- +*** Testing session_set_save_handler() : manual shutdown function *** +(#1) constructor called +done +(#1) finish called %x +(#1) writing %x = foo|s:3:"bar"; +(#1) closing %x +(#1) destructor called diff --git a/ext/session/tests/session_set_save_handler_class_011.phpt b/ext/session/tests/session_set_save_handler_class_011.phpt new file mode 100644 index 0000000000..7fa3657694 --- /dev/null +++ b/ext/session/tests/session_set_save_handler_class_011.phpt @@ -0,0 +1,66 @@ +--TEST-- +Test session_set_save_handler() : shutdown failure +--INI-- +session.save_handler=files +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- +num = $num; + echo "(#$this->num) constructor called\n"; + } + public function __destruct() { + echo "(#$this->num) destructor called\n"; + $this->destroyed = true; + } + public function write($id, $data) { + if ($this->destroyed) { + echo "(#$this->num) destroyed, cannot write\n"; + } else { + echo "(#$this->num) writing $id = $data\n"; + } + return parent::write($id, $data); + } + public function close() { + $id = session_id(); + if ($this->destroyed) { + echo "(#$this->num) destroyed, cannot write\n"; + } else { + echo "(#$this->num) closing $id\n"; + } + return parent::close(); + } +} + +$handler = new MySession(1); +session_set_save_handler($handler, false); +session_start(); + +$_SESSION['foo'] = 'bar'; + +echo "done\n"; +ob_end_flush(); +?> +--EXPECTF-- +*** Testing session_set_save_handler() : shutdown failure *** +(#1) constructor called +done +(#1) destructor called +(#1) destroyed, cannot write +(#1) destroyed, cannot write diff --git a/ext/session/tests/session_set_save_handler_class_012.phpt b/ext/session/tests/session_set_save_handler_class_012.phpt new file mode 100644 index 0000000000..f7f50d255f --- /dev/null +++ b/ext/session/tests/session_set_save_handler_class_012.phpt @@ -0,0 +1,59 @@ +--TEST-- +Test session_set_save_handler() : incorrect arguments for existing handler open +--INI-- +session.save_handler=files +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- +i; + echo 'Open ', session_id(), "\n"; + return parent::open(); + } + public function read($key) { + ++$this->i; + echo 'Read ', session_id(), "\n"; + return parent::read($key); + } +} + +$oldHandler = ini_get('session.save_handler'); +$handler = new MySession; +session_set_save_handler($handler); +session_start(); + +var_dump(session_id(), $oldHandler, ini_get('session.save_handler'), $handler->i, $_SESSION); + +--EXPECTF-- +*** Testing session_set_save_handler() : incorrect arguments for existing handler open *** +Open + +Warning: SessionHandler::open() expects exactly 2 parameters, 0 given in %s on line %d +Read %x + +Warning: SessionHandler::read(): Parent session handler is not open in %s on line %d +string(32) "%x" +string(5) "files" +string(4) "user" +int(2) +array(0) { +} + +Warning: Unknown: Parent session handler is not open in Unknown on line 0 + +Warning: Unknown: Parent session handler is not open in Unknown on line 0 diff --git a/ext/session/tests/session_set_save_handler_class_013.phpt b/ext/session/tests/session_set_save_handler_class_013.phpt new file mode 100644 index 0000000000..f54aec4686 --- /dev/null +++ b/ext/session/tests/session_set_save_handler_class_013.phpt @@ -0,0 +1,56 @@ +--TEST-- +Test session_set_save_handler() : incorrect arguments for existing handler close +--INI-- +session.save_handler=files +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- +i; + echo 'Open ', session_id(), "\n"; + return parent::open($path, $name); + } + public function read($key) { + ++$this->i; + echo 'Read ', session_id(), "\n"; + return parent::read($key); + } + public function close() { + return parent::close(false); + } +} + +$oldHandler = ini_get('session.save_handler'); +$handler = new MySession; +session_set_save_handler($handler); +session_start(); + +var_dump(session_id(), $oldHandler, ini_get('session.save_handler'), $handler->i, $_SESSION); + +--EXPECTF-- +*** Testing session_set_save_handler() : incorrect arguments for existing handler close *** +Open +Read %x +string(32) "%x" +string(5) "files" +string(4) "user" +int(2) +array(0) { +} + +Warning: SessionHandler::close() expects exactly 0 parameters, 1 given in %s on line %d diff --git a/ext/session/tests/session_set_save_handler_class_014.phpt b/ext/session/tests/session_set_save_handler_class_014.phpt new file mode 100644 index 0000000000..31b56c5ae5 --- /dev/null +++ b/ext/session/tests/session_set_save_handler_class_014.phpt @@ -0,0 +1,32 @@ +--TEST-- +Test session_set_save_handler() : calling default handler when save_handler=user +--INI-- +session.save_handler=user +session.name=PHPSESSID +--SKIPIF-- + +--FILE-- +highlight_comment = INI_STR("highlight.comment"); diff --git a/ext/standard/basic_functions.h b/ext/standard/basic_functions.h index 4435f4bc4c..32b6d51317 100644 --- a/ext/standard/basic_functions.h +++ b/ext/standard/basic_functions.h @@ -251,4 +251,13 @@ typedef struct { PHPAPI double php_get_nan(void); PHPAPI double php_get_inf(void); +typedef struct _php_shutdown_function_entry { + zval **arguments; + int arg_count; +} php_shutdown_function_entry; + +PHPAPI extern zend_bool register_user_shutdown_function(char *function_name, php_shutdown_function_entry *shutdown_function_entry); +PHPAPI extern zend_bool remove_user_shutdown_function(char *function_name); +PHPAPI extern zend_bool append_user_shutdown_function(php_shutdown_function_entry shutdown_function_entry); + #endif /* BASIC_FUNCTIONS_H */ -- cgit v1.2.1