diff options
author | Arnaud Le Blanc <lbarnaud@php.net> | 2010-05-10 16:09:00 +0000 |
---|---|---|
committer | Arnaud Le Blanc <lbarnaud@php.net> | 2010-05-10 16:09:00 +0000 |
commit | da9448f3fd5810f029656668ed3b218f4be9c8f6 (patch) | |
tree | 1f5bbbf0180ee4506c783126d8364265d6084a56 | |
parent | d5a646480dc68a9b8bfaf8ddeb8160f499053a8b (diff) | |
download | php-git-da9448f3fd5810f029656668ed3b218f4be9c8f6.tar.gz |
Added upload progress feedback in session data as describied
in RFC : http://wiki.php.net/rfc/session_upload_progress
-rw-r--r-- | ext/session/php_session.h | 27 | ||||
-rw-r--r-- | ext/session/session.c | 325 |
2 files changed, 350 insertions, 2 deletions
diff --git a/ext/session/php_session.h b/ext/session/php_session.h index 8d3c9d0b4e..1f32811361 100644 --- a/ext/session/php_session.h +++ b/ext/session/php_session.h @@ -95,6 +95,26 @@ typedef enum { php_session_active } php_session_status; +typedef struct _php_session_rfc1867_progress { + + size_t sname_len; + zval sid; + smart_str key; + + long update_step; + long next_update; + double next_update_time; + zend_bool cancel_upload; + zend_bool apply_trans_sid; + size_t content_length; + + zval *data; /* the array exported to session data */ + zval *post_bytes_processed; /* data["bytes_processed"] */ + zval *files; /* data["files"] array */ + zval *current_file; /* array of currently uploading file */ + zval *current_file_bytes_processed; +} php_session_rfc1867_progress; + typedef struct _php_ps_globals { char *save_path; char *session_name; @@ -143,6 +163,13 @@ typedef struct _php_ps_globals { 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 */ + smart_str rfc1867_prefix; /* session.upload_progress.prefix */ + smart_str rfc1867_name; /* session.upload_progress.name */ + long rfc1867_freq; /* session.upload_progress.freq */ + double rfc1867_min_freq; /* session.upload_progress.min_freq */ } php_ps_globals; typedef php_ps_globals zend_ps_globals; diff --git a/ext/session/session.c b/ext/session/session.c index c7a23a5747..7fb1b67345 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -36,6 +36,8 @@ #include "php_ini.h" #include "SAPI.h" +#include "rfc1867.h" +#include "php_variables.h" #include "php_session.h" #include "ext/standard/md5.h" #include "ext/standard/sha1.h" @@ -57,6 +59,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); + /* *********** * Helpers * *********** */ @@ -625,6 +630,45 @@ static PHP_INI_MH(OnUpdateHashFunc) /* {{{ */ } /* }}} */ +static PHP_INI_MH(OnUpdateRfc1867Freq) /* {{{ */ +{ + int tmp; + tmp = zend_atoi(new_value, new_value_length); + if(tmp < 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "session.upload_progress.freq must be greater than or equal to zero"); + return FAILURE; + } + if(new_value_length > 0 && new_value[new_value_length-1] == '%') { + if(tmp > 100) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "session.upload_progress.freq cannot be over 100%%"); + return FAILURE; + } + PS(rfc1867_freq) = -tmp; + } else { + PS(rfc1867_freq) = tmp; + } + return SUCCESS; +} /* }}} */ + +static ZEND_INI_MH(OnUpdateSmartStr) /* {{{ */ +{ + smart_str *p; +#ifndef ZTS + char *base = (char *) mh_arg2; +#else + char *base; + + base = (char *) ts_resource(*((int *) mh_arg2)); +#endif + + p = (smart_str *) (base+(size_t) mh_arg1); + + smart_str_sets(p, new_value); + + return SUCCESS; +} +/* }}} */ + /* {{{ PHP_INI */ PHP_INI_BEGIN() @@ -660,6 +704,17 @@ PHP_INI_BEGIN() 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) + /* Upload progress */ + STD_PHP_INI_BOOLEAN("session.upload_progress.enabled", + "1", ZEND_INI_PERDIR, OnUpdateBool, rfc1867_enabled, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.upload_progress.prefix", + "upload_progress_", ZEND_INI_PERDIR, OnUpdateSmartStr, rfc1867_prefix, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.upload_progress.name", + "PHP_SESSION_UPLOAD_PROGRESS", ZEND_INI_PERDIR, OnUpdateSmartStr, rfc1867_name, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.upload_progress.freq", "1%", ZEND_INI_PERDIR, OnUpdateRfc1867Freq, rfc1867_freq, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.upload_progress.min_freq", + "1", ZEND_INI_PERDIR, OnUpdateReal, rfc1867_min_freq,php_ps_globals, ps_globals) + /* Commented out until future discussion */ /* PHP_INI_ENTRY("session.encode_sources", "globals,track", PHP_INI_ALL, NULL) */ PHP_INI_END() @@ -1832,7 +1887,7 @@ static const zend_function_entry session_functions[] = { * Module Setup and Destruction * ******************************** */ -static PHP_RINIT_FUNCTION(session) /* {{{ */ +static int php_rinit_session(zend_bool auto_start TSRMLS_DC) /* {{{ */ { php_rinit_session_globals(TSRMLS_C); @@ -1860,11 +1915,16 @@ static PHP_RINIT_FUNCTION(session) /* {{{ */ return SUCCESS; } - if (PS(auto_start)) { + if (auto_start) { php_session_start(TSRMLS_C); } return SUCCESS; +} /* }}} */ + +static PHP_RINIT_FUNCTION(session) /* {{{ */ +{ + return php_rinit_session(PS(auto_start) TSRMLS_CC); } /* }}} */ @@ -1917,6 +1977,8 @@ static PHP_MINIT_FUNCTION(session) /* {{{ */ #ifdef HAVE_LIBMM PHP_MINIT(ps_mm) (INIT_FUNC_ARGS_PASSTHRU); #endif + php_session_rfc1867_orig_callback = php_rfc1867_callback; + php_rfc1867_callback = php_session_rfc1867_callback; return SUCCESS; } /* }}} */ @@ -1991,6 +2053,265 @@ static const zend_module_dep session_deps[] = { /* {{{ */ }; /* }}} */ +/* ************************ + * Upload hook handling * + ************************ */ + +static void php_session_rfc1867_early_find_sid(php_session_rfc1867_progress *progress TSRMLS_DC) /* {{{ */ +{ + zval **ppid; + + if (PS(use_cookies)) { + sapi_module.treat_data(PARSE_COOKIE, NULL, NULL TSRMLS_CC); + if (PG(http_globals)[TRACK_VARS_COOKIE] + && zend_hash_find(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_COOKIE]), PS(session_name), progress->sname_len+1, (void **)&ppid) == SUCCESS) { + zval_dtor(&progress->sid); + ZVAL_ZVAL(&progress->sid, *ppid, 1, 0); + convert_to_string(&progress->sid); + progress->apply_trans_sid = 0; + return; + } + } + if (PS(use_only_cookies)) { + return; + } + sapi_module.treat_data(PARSE_GET, NULL, NULL TSRMLS_CC); + if (PG(http_globals)[TRACK_VARS_GET] + && zend_hash_find(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_GET]), PS(session_name), progress->sname_len+1, (void **)&ppid) == SUCCESS) { + + zval_dtor(&progress->sid); + ZVAL_ZVAL(&progress->sid, *ppid, 1, 0); + convert_to_string(&progress->sid); + } +} /* }}} */ + +static zend_bool php_session_rfc1867_check_cancel_upload(php_session_rfc1867_progress *progress TSRMLS_DC) /* {{{ */ +{ + zval **progress_ary, **cancel_upload; + + if (zend_symtable_find(Z_ARRVAL_P(PS(http_session_vars)), progress->key.c, progress->key.len+1, (void**)&progress_ary) != SUCCESS) { + return 0; + } + if (Z_TYPE_PP(progress_ary) != IS_ARRAY) { + return 0; + } + if (zend_hash_find(Z_ARRVAL_PP(progress_ary), "cancel_upload", sizeof("cancel_upload"), (void**)&cancel_upload) != SUCCESS) { + return 0; + } + return zend_is_true(*cancel_upload); + +} /* }}} */ + +static void php_session_rfc1867_update(php_session_rfc1867_progress *progress, int force_update TSRMLS_DC) /* {{{ */ +{ + if (!force_update) { + if (Z_LVAL_P(progress->post_bytes_processed) < progress->next_update) { + return; + } +#ifdef HAVE_GETTIMEOFDAY + if (PS(rfc1867_min_freq) > 0.0) { + struct timeval tv = {0}; + double dtv; + gettimeofday(&tv, NULL); + dtv = (double) tv.tv_sec + tv.tv_usec / 1000000.0; + if (dtv < progress->next_update_time) { + return; + } + progress->next_update_time = dtv + PS(rfc1867_min_freq); + } +#endif + progress->next_update = Z_LVAL_P(progress->post_bytes_processed) + progress->update_step; + } + + php_session_initialize(TSRMLS_C); + PS(session_status) = php_session_active; + IF_SESSION_VARS() { + if (!progress->cancel_upload && php_session_rfc1867_check_cancel_upload(progress TSRMLS_CC)) { + progress->cancel_upload = 1; + } + ZEND_SET_SYMBOL_WITH_LENGTH(Z_ARRVAL_P(PS(http_session_vars)), progress->key.c, progress->key.len+1, progress->data, 2, 0); + } + php_session_flush(TSRMLS_C); +} /* }}} */ + +static int php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra TSRMLS_DC) /* {{{ */ +{ + php_session_rfc1867_progress *progress; + int retval = SUCCESS; + + if (php_session_rfc1867_orig_callback) { + retval = php_session_rfc1867_orig_callback(event, event_data, extra TSRMLS_CC); + } + if (!PS(rfc1867_enabled)) { + return retval; + } + + progress = PS(rfc1867_progress); + + switch(event) { + case MULTIPART_EVENT_START: { + multipart_event_start *data = (multipart_event_start *) event_data; + progress = ecalloc(1, sizeof(php_session_rfc1867_progress)); + progress->content_length = data->content_length; + progress->sname_len = strlen(PS(session_name)); + PS(rfc1867_progress) = progress; + } + break; + case MULTIPART_EVENT_FORMDATA: { + multipart_event_formdata *data = (multipart_event_formdata *) event_data; + size_t value_len; + + /* orig callback may have modified *data->newlength */ + if (data->newlength) { + value_len = *data->newlength; + } else { + value_len = data->length; + } + + if (data->name && data->value && value_len) { + size_t name_len = strlen(data->name); + + if (name_len == progress->sname_len && memcmp(data->name, PS(session_name), name_len) == 0) { + zval_dtor(&progress->sid); + ZVAL_STRINGL(&progress->sid, (*data->value), value_len, 1); + convert_to_string(&progress->sid); + + } else if (name_len == PS(rfc1867_name).len && memcmp(data->name, PS(rfc1867_name).c, name_len) == 0) { + smart_str_free(&progress->key); + smart_str_appendl(&progress->key, PS(rfc1867_prefix).c, PS(rfc1867_prefix).len); + smart_str_appendl(&progress->key, *data->value, value_len); + smart_str_0(&progress->key); + + progress->apply_trans_sid = PS(use_trans_sid); + php_session_rfc1867_early_find_sid(progress TSRMLS_CC); + } + } + } + break; + case MULTIPART_EVENT_FILE_START: { + multipart_event_file_start *data = (multipart_event_file_start *) event_data; + + /* Do nothing when $_POST["PHP_SESSION_UPLOAD_PROGRESS"] is not set + * or when we have no session id */ + if (!Z_TYPE(progress->sid) || !progress->key.c) { + break; + } + + /* First FILE_START event, initializing data */ + if (!progress->data) { + + if (PS(rfc1867_freq) == 0) { + progress->update_step = 0; + } else if (PS(rfc1867_freq) < 0) { // % of total size + progress->update_step = progress->content_length * PS(rfc1867_freq) / 100; + } + progress->next_update = 0; + progress->next_update_time = 0.0; + + ALLOC_INIT_ZVAL(progress->data); + array_init(progress->data); + + ALLOC_INIT_ZVAL(progress->post_bytes_processed); + ZVAL_LONG(progress->post_bytes_processed, data->post_bytes_processed); + + ALLOC_INIT_ZVAL(progress->files); + array_init(progress->files); + + add_assoc_long_ex(progress->data, "start_time", sizeof("start_time"), (long)sapi_get_request_time(TSRMLS_C)); + add_assoc_long_ex(progress->data, "content_length", sizeof("content_length"), progress->content_length); + add_assoc_zval_ex(progress->data, "bytes_processed", sizeof("bytes_processed"), progress->post_bytes_processed); + add_assoc_bool_ex(progress->data, "done", sizeof("done"), 0); + add_assoc_zval_ex(progress->data, "files", sizeof("files"), progress->files); + + php_rinit_session(0 TSRMLS_CC); + PS(id) = estrndup(Z_STRVAL(progress->sid), Z_STRLEN(progress->sid)); + PS(apply_trans_sid) = progress->apply_trans_sid; + PS(send_cookie) = 0; + } + + ALLOC_INIT_ZVAL(progress->current_file); + array_init(progress->current_file); + + ALLOC_INIT_ZVAL(progress->current_file_bytes_processed); + ZVAL_LONG(progress->current_file_bytes_processed, 0); + + /* Each uploaded file has its own array. Trying to make it close to $_FILES entries. */ + add_assoc_string_ex(progress->current_file, "field_name", sizeof("field_name"), data->name, 1); + add_assoc_string_ex(progress->current_file, "name", sizeof("name"), *data->filename, 1); + add_assoc_null_ex(progress->current_file, "tmp_name", sizeof("tmp_name")); + add_assoc_long_ex(progress->current_file, "error", sizeof("error"), 0); + + add_assoc_long_ex(progress->current_file, "done", sizeof("done"), 0); + add_assoc_long_ex(progress->current_file, "start_time", sizeof("start_time"), (long)time(NULL)); + add_assoc_zval_ex(progress->current_file, "bytes_processed", sizeof("bytes_processed"), progress->current_file_bytes_processed); + + add_next_index_zval(progress->files, progress->current_file); + + Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed; + + php_session_rfc1867_update(progress, 0 TSRMLS_CC); + } + break; + case MULTIPART_EVENT_FILE_DATA: { + multipart_event_file_data *data = (multipart_event_file_data *) event_data; + + if (!Z_TYPE(progress->sid) || !progress->key.c) { + break; + } + + Z_LVAL_P(progress->current_file_bytes_processed) = data->offset + data->length; + Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed; + + php_session_rfc1867_update(progress, 0 TSRMLS_CC); + } + break; + case MULTIPART_EVENT_FILE_END: { + multipart_event_file_end *data = (multipart_event_file_end *) event_data; + + if (!Z_TYPE(progress->sid) || !progress->key.c) { + break; + } + + if (data->temp_filename) { + add_assoc_string_ex(progress->current_file, "tmp_name", sizeof("tmp_name"), data->temp_filename, 1); + } + add_assoc_long_ex(progress->current_file, "error", sizeof("error"), data->cancel_upload); + add_assoc_bool_ex(progress->current_file, "done", sizeof("done"), 1); + + Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed; + + php_session_rfc1867_update(progress, 0 TSRMLS_CC); + } + break; + case MULTIPART_EVENT_END: { + multipart_event_end *data = (multipart_event_end *) event_data; + + if (Z_TYPE(progress->sid) && progress->key.c) { + add_assoc_bool_ex(progress->data, "done", sizeof("done"), 1); + Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed; + php_session_rfc1867_update(progress, 1 TSRMLS_CC); + php_rshutdown_session_globals(TSRMLS_C); + } + + if (progress->data) { + zval_ptr_dtor(&progress->data); + } + zval_dtor(&progress->sid); + smart_str_free(&progress->key); + efree(progress); + progress = NULL; + PS(rfc1867_progress) = NULL; + } + break; + } + + if (progress && progress->cancel_upload) { + return FAILURE; + } + return retval; + +} /* }}} */ + zend_module_entry session_module_entry = { STANDARD_MODULE_HEADER_EX, NULL, |