Index: ext/session/php_session.h
===================================================================
RCS file: /repository/php-src/ext/session/php_session.h,v
retrieving revision 1.116
diff -u -r1.116 php_session.h
--- ext/session/php_session.h	7 Mar 2008 23:20:15 -0000	1.116
+++ ext/session/php_session.h	8 Sep 2008 09:43:57 -0000
@@ -95,6 +95,23 @@
 	php_session_active
 } php_session_status;
 
+typedef struct _php_session_rfc1867_progress {
+	zval      prefix;
+	zval      name;
+	zval      sname;
+	zval      sid;
+	zval      key;
+	long      update_step;
+	long      next_update;
+	zend_bool apply_trans_sid;
+	zval      *data;
+	size_t    content_length;
+	zval      *post_bytes_processed;
+	zval      *files;
+	zval      *current_file;
+	zval      *current_file_bytes_processed;
+} php_session_rfc1867_progress;
+
 typedef struct _php_ps_globals {
 	char *save_path;
 	char *session_name;
@@ -143,6 +160,12 @@
 	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;
+	zstr rfc1867_prefix;
+	zstr rfc1867_name;
+	long rfc1867_freq;
 } php_ps_globals;
 
 typedef php_ps_globals zend_ps_globals;
Index: ext/session/session.c
===================================================================
RCS file: /repository/php-src/ext/session/session.c,v
retrieving revision 1.492
diff -u -r1.492 session.c
--- ext/session/session.c	6 Aug 2008 05:34:55 -0000	1.492
+++ ext/session/session.c	8 Sep 2008 09:43:57 -0000
@@ -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_orig_callback)(unsigned int event, void *event_data, void **extra TSRMLS_DC);
+static int php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra TSRMLS_DC);
+
 /* ***********
    * Helpers *
    *********** */
@@ -630,12 +635,32 @@
 		return SUCCESS;
 	}
 }
-#endif /* HAVE_HASH_EXT */
+#endif /* HAVE_HASH_EXT }}} */
 
 	return FAILURE;
 }
 /* }}} */
 
+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;
+} /* }}} */
+
 /* {{{ PHP_INI
  */
 PHP_INI_BEGIN()
@@ -663,6 +688,15 @@
 	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, OnUpdateUTF8String,  rfc1867_prefix,  php_ps_globals, ps_globals)
+	STD_PHP_INI_ENTRY("session.upload_progress.name",
+	                          "PHP_SESSION_UPLOAD_PROGRESS", ZEND_INI_PERDIR, OnUpdateUTF8String,  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)
+
 	/* Commented out until future discussion */
 	/* PHP_INI_ENTRY("session.encode_sources", "globals,track", PHP_INI_ALL, NULL) */
 PHP_INI_END()
@@ -1917,7 +1951,7 @@
    * Module Setup and Destruction *
    ******************************** */
 
-static PHP_RINIT_FUNCTION(session) /* {{{ */
+static inline int php_rinit_session(zend_bool auto_start TSRMLS_DC) /* {{{ */
 {
 	php_rinit_session_globals(TSRMLS_C);
 
@@ -1945,11 +1979,16 @@
 		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);
 }
 /* }}} */
 
@@ -2002,6 +2041,8 @@
 #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;
 }
 /* }}} */
@@ -2070,6 +2111,248 @@
 }
 /* }}} */
 
+/* ************************
+   * Upload hook handling *
+   ************************ */
+
+#define USTR_EQUAL(s1, len1, s2, len2) \
+	((len1) == (len2) && \
+	(UG(unicode) ? \
+	 (zend_u_binary_strcmp((s1).u, (len1), (s2).u, (len2)) == 0) \
+	 : (zend_binary_strcmp((s1).s, (len1), (s2).s, (len2)) == 0)))
+
+static inline 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_u_hash_find(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_COOKIE]), Z_TYPE(progress->sname), Z_UNIVAL(progress->sname), Z_UNILEN(progress->sname)+1, (void **)&ppid) == SUCCESS) {
+
+			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_u_hash_find(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_GET]), Z_TYPE(progress->sname), Z_UNIVAL(progress->sname), Z_UNILEN(progress->sname)+1, (void **)&ppid) == SUCCESS) {
+
+		ZVAL_ZVAL(&progress->sid, *ppid, 1, 0);
+		convert_to_string(&progress->sid);
+	}
+} /* }}} */
+
+static inline void php_session_rfc1867_update(php_session_rfc1867_progress *progress TSRMLS_DC) /* {{{ */
+{
+	php_session_initialize(TSRMLS_C);
+	PS(session_status) = php_session_active;
+	IF_SESSION_VARS() {
+		Z_ADDREF_P(progress->data);
+		zend_u_symtable_update(Z_ARRVAL_P(PS(http_session_vars)), Z_TYPE(progress->key), Z_UNIVAL(progress->key), Z_UNILEN(progress->key)+1, &progress->data, sizeof(zval *), NULL);
+	}
+	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;
+
+			/* We need to have a runtime-encoding encoded PS(session_name) so that
+			 * we can compare to incomming POST variables name. zvals also allow
+			 * to keep track of length. */
+			ZVAL_RT_STRING(&progress->sname, PS(session_name),   ZSTR_DUPLICATE);
+			ZVAL_TEXT(&progress->prefix,     PS(rfc1867_prefix), 0);
+			ZVAL_TEXT(&progress->name,       PS(rfc1867_name),   0);
+
+			PS(rfc1867_progress) = progress;
+		}
+		break;
+		case MULTIPART_EVENT_FORMDATA: {
+			multipart_event_formdata *data = (multipart_event_formdata *) event_data;
+			size_t name_len, value_len;
+			zstr str;
+			
+			/* orig callback may have modified *data->newlength */
+			if (data->newlength) {
+				value_len = *data->newlength;
+			} else {
+				value_len = data->length;
+			}
+
+			/* Search PS(session_name) and PS(rfc1867_name) in incomming POST variables */
+			if (data->name.v && data->value && value_len) {
+				name_len = USTR_LEN(data->name);
+
+				if (USTR_EQUAL(Z_UNIVAL(progress->sname), Z_UNILEN(progress->sname), data->name, name_len)) {
+					zval_dtor(&progress->sid);
+					ZVAL_TEXTL(&progress->sid, (*data->value), value_len, ZSTR_DUPLICATE);
+					convert_to_string(&progress->sid);
+
+				} else if (USTR_EQUAL(Z_UNIVAL(progress->name), Z_UNILEN(progress->name), data->name, name_len)) {
+					size_t len = Z_UNILEN(progress->prefix) + value_len;
+					str.v = emalloc(TEXT_BYTES(len+1));
+					memcpy(str.s, Z_UNIVAL(progress->prefix).v, TEXT_BYTES(Z_UNILEN(progress->prefix)));
+					memcpy(str.s+TEXT_BYTES(Z_UNILEN(progress->prefix)), (*data->value).v, TEXT_BYTES(value_len+1));
+
+					zval_dtor(&progress->key);
+					ZVAL_TEXTL(&progress->key, str, len, 0);
+
+					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[session.upload_progress.name] is not set 
+			 * or when no session id was sent */
+			if (!Z_TYPE(progress->sid) || !Z_TYPE(progress->key)) {
+				break;
+			}
+			
+			/* First FILE_START event, initializing */
+			if (!progress->data) {
+
+				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;
+
+				if (PS(rfc1867_freq) >= 0) {
+					progress->update_step = PS(rfc1867_freq);
+				} else if (PS(rfc1867_freq) < 0) { /* % of total size */
+					progress->update_step = progress->content_length * PS(rfc1867_freq) / 100;
+				}
+				progress->next_update = 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_ascii_assoc_long_ex(progress->data, "start_time",      sizeof("start_time"),      (long)sapi_get_request_time(TSRMLS_C));
+				add_ascii_assoc_long_ex(progress->data, "content_length",  sizeof("content_length"),  progress->content_length);
+				add_ascii_assoc_zval_ex(progress->data, "bytes_processed", sizeof("bytes_processed"), progress->post_bytes_processed);
+				add_ascii_assoc_bool_ex(progress->data, "done",            sizeof("done"),            0);
+				add_ascii_assoc_zval_ex(progress->data, "files",           sizeof("files"),           progress->files);
+			}
+
+			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);
+
+			add_ascii_assoc_text_ex(progress->current_file, "field_name",      sizeof("field_name"),      data->name, 1);
+			add_ascii_assoc_text_ex(progress->current_file, "name",            sizeof("name"),            *data->filename, 1);
+			add_ascii_assoc_null_ex(progress->current_file, "tmp_name",        sizeof("tmp_name"));
+			add_ascii_assoc_long_ex(progress->current_file, "error",           sizeof("error"),           0);
+
+			add_ascii_assoc_long_ex(progress->current_file, "done",            sizeof("done"),            0);
+			add_ascii_assoc_long_ex(progress->current_file, "start_time",      sizeof("start_time"),      (long)time(NULL));
+			add_ascii_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;
+
+			if (data->post_bytes_processed >= progress->next_update) {
+				php_session_rfc1867_update(progress TSRMLS_CC);
+				progress->next_update += progress->update_step;
+			}
+		}
+		break;
+		case MULTIPART_EVENT_FILE_DATA: {
+			multipart_event_file_data *data = (multipart_event_file_data *) event_data;
+
+			if (!Z_TYPE(progress->sid) || !Z_TYPE(progress->key)) {
+				break;
+			}
+			
+			Z_LVAL_P(progress->current_file_bytes_processed) = data->offset + data->length;
+			Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed;
+
+			if (data->post_bytes_processed >= progress->next_update) {
+				php_session_rfc1867_update(progress TSRMLS_CC);
+				progress->next_update += progress->update_step;
+			}
+		}
+		break;
+		case MULTIPART_EVENT_FILE_END: {
+			multipart_event_file_end *data = (multipart_event_file_end *) event_data;
+
+			if (!Z_TYPE(progress->sid) || !Z_TYPE(progress->key)) {
+				break;
+			}
+			
+			if (data->temp_filename.v) {
+				add_ascii_assoc_text_ex(progress->current_file, "tmp_name",  sizeof("tmp_name"), data->temp_filename, 1);
+			}
+			add_ascii_assoc_long_ex(progress->current_file, "error", sizeof("error"), data->cancel_upload);
+			add_ascii_assoc_bool_ex(progress->current_file, "done",  sizeof("done"),  1);
+
+			Z_LVAL_P(progress->post_bytes_processed) = data->post_bytes_processed;
+
+			if (data->post_bytes_processed >= progress->next_update) {
+				php_session_rfc1867_update(progress TSRMLS_CC);
+				progress->next_update += progress->update_step;
+			}
+		}
+		break;
+		case MULTIPART_EVENT_END: {
+			multipart_event_end *data = (multipart_event_end *) event_data;
+
+			if (Z_TYPE(progress->sid) && Z_TYPE(progress->key)) {
+				add_ascii_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 TSRMLS_CC);
+				php_rshutdown_session_globals(TSRMLS_C);
+			}
+
+			zval_dtor(&progress->sname);
+			zval_dtor(&progress->sid);
+			zval_dtor(&progress->key);
+			if (progress->data) {
+				zval_ptr_dtor(&progress->data);
+			}
+			efree(progress);
+		}
+		break;
+	}
+
+	return retval;
+
+} /* }}} */
+
 zend_module_entry session_module_entry = {
 	STANDARD_MODULE_HEADER,
 	"session",
