--- embedaddon/php/main/output.c 2012/02/21 23:48:05 1.1.1.1 +++ embedaddon/php/main/output.c 2012/05/29 12:34:35 1.1.1.2 @@ -15,40 +15,84 @@ | Authors: Zeev Suraski | | Thies C. Arntzen | | Marcus Boerger | + | New API: Michael Wallner | +----------------------------------------------------------------------+ */ -/* $Id: output.c,v 1.1.1.1 2012/02/21 23:48:05 misho Exp $ */ +/* $Id: output.c,v 1.1.1.2 2012/05/29 12:34:35 misho Exp $ */ +#ifndef PHP_OUTPUT_DEBUG +# define PHP_OUTPUT_DEBUG 0 +#endif +#ifndef PHP_OUTPUT_NOINLINE +# define PHP_OUTPUT_NOINLINE 0 +#endif + #include "php.h" #include "ext/standard/head.h" -#include "ext/standard/basic_functions.h" #include "ext/standard/url_scanner_ex.h" -#if HAVE_ZLIB && !defined(COMPILE_DL_ZLIB) -#include "ext/zlib/php_zlib.h" -#endif #include "SAPI.h" +#include "zend_stack.h" +#include "php_output.h" -#define OB_DEFAULT_HANDLER_NAME "default output handler" +ZEND_DECLARE_MODULE_GLOBALS(output); -/* output functions */ -static int php_b_body_write(const char *str, uint str_length TSRMLS_DC); +const char php_output_default_handler_name[sizeof("default output handler")] = "default output handler"; +const char php_output_devnull_handler_name[sizeof("null output handler")] = "null output handler"; -static int php_ob_init(uint initial_size, uint block_size, zval *output_handler, uint chunk_size, zend_bool erase TSRMLS_DC); -static void php_ob_append(const char *text, uint text_length TSRMLS_DC); -#if 0 -static void php_ob_prepend(const char *text, uint text_length); +#if PHP_OUTPUT_NOINLINE || PHP_OUTPUT_DEBUG +# undef inline +# define inline #endif -#ifdef ZTS -int output_globals_id; -#else -php_output_globals output_globals; -#endif +/* {{{ aliases, conflict and reverse conflict hash tables */ +static HashTable php_output_handler_aliases; +static HashTable php_output_handler_conflicts; +static HashTable php_output_handler_reverse_conflicts; +/* }}} */ -/* {{{ php_default_output_func */ -PHPAPI int php_default_output_func(const char *str, uint str_len TSRMLS_DC) +/* {{{ forward declarations */ +static inline int php_output_lock_error(int op TSRMLS_DC); +static inline void php_output_op(int op, const char *str, size_t len TSRMLS_DC); + +static inline php_output_handler *php_output_handler_init(const char *name, size_t name_len, size_t chunk_size, int flags TSRMLS_DC); +static inline php_output_handler_status_t php_output_handler_op(php_output_handler *handler, php_output_context *context); +static inline int php_output_handler_append(php_output_handler *handler, const php_output_buffer *buf TSRMLS_DC); +static inline zval *php_output_handler_status(php_output_handler *handler, zval *entry); + +static inline php_output_context *php_output_context_init(php_output_context *context, int op TSRMLS_DC); +static inline void php_output_context_reset(php_output_context *context); +static inline void php_output_context_swap(php_output_context *context); +static inline void php_output_context_dtor(php_output_context *context); + +static inline int php_output_stack_pop(int flags TSRMLS_DC); + +static int php_output_stack_apply_op(void *h, void *c); +static int php_output_stack_apply_clean(void *h, void *c); +static int php_output_stack_apply_list(void *h, void *z); +static int php_output_stack_apply_status(void *h, void *z); + +static int php_output_handler_compat_func(void **handler_context, php_output_context *output_context); +static int php_output_handler_default_func(void **handler_context, php_output_context *output_context); +static int php_output_handler_devnull_func(void **handler_context, php_output_context *output_context); +/* }}} */ + +/* {{{ static void php_output_init_globals(zend_output_globals *G) + * Initialize the module globals on MINIT */ +static inline void php_output_init_globals(zend_output_globals *G) { + memset(G, 0, sizeof(*G)); +} +/* }}} */ + +/* {{{ stderr/stdout writer if not PHP_OUTPUT_ACTIVATED */ +static int php_output_stdout(const char *str, size_t str_len) +{ + fwrite(str, 1, str_len, stdout); + return str_len; +} +static int php_output_stderr(const char *str, size_t str_len) +{ fwrite(str, 1, str_len, stderr); /* See http://support.microsoft.com/kb/190351 */ #ifdef PHP_WIN32 @@ -56,712 +100,1215 @@ PHPAPI int php_default_output_func(const char *str, ui #endif return str_len; } +static int (*php_output_direct)(const char *str, size_t str_len) = php_output_stderr; /* }}} */ -/* {{{ php_output_init_globals */ -static void php_output_init_globals(php_output_globals *output_globals_p TSRMLS_DC) +/* {{{ void php_output_header(TSRMLS_D) */ +static void php_output_header(TSRMLS_D) { - OG(php_body_write) = php_default_output_func; - OG(php_header_write) = php_default_output_func; - OG(implicit_flush) = 0; - OG(output_start_filename) = NULL; - OG(output_start_lineno) = 0; + if (!SG(headers_sent)) { + if (!OG(output_start_filename)) { + if (zend_is_compiling(TSRMLS_C)) { + OG(output_start_filename) = zend_get_compiled_filename(TSRMLS_C); + OG(output_start_lineno) = zend_get_compiled_lineno(TSRMLS_C); + } else if (zend_is_executing(TSRMLS_C)) { + OG(output_start_filename) = zend_get_executed_filename(TSRMLS_C); + OG(output_start_lineno) = zend_get_executed_lineno(TSRMLS_C); + } +#if PHP_OUTPUT_DEBUG + fprintf(stderr, "!!! output started at: %s (%d)\n", OG(output_start_filename), OG(output_start_lineno)); +#endif + } + if (!php_header(TSRMLS_C)) { + OG(flags) |= PHP_OUTPUT_DISABLED; + } + } } /* }}} */ -/* {{{ php_output_startup - * Start output layer */ +/* {{{ void php_output_startup(void) + * Set up module globals and initalize the conflict and reverse conflict hash tables */ PHPAPI void php_output_startup(void) { + ZEND_INIT_MODULE_GLOBALS(output, php_output_init_globals, NULL); + zend_hash_init(&php_output_handler_aliases, 0, NULL, NULL, 1); + zend_hash_init(&php_output_handler_conflicts, 0, NULL, NULL, 1); + zend_hash_init(&php_output_handler_reverse_conflicts, 0, NULL, (void (*)(void *)) zend_hash_destroy, 1); + php_output_direct = php_output_stdout; +} +/* }}} */ + +/* {{{ void php_output_shutdown(void) + * Destroy module globals and the conflict and reverse conflict hash tables */ +PHPAPI void php_output_shutdown(void) +{ + php_output_direct = php_output_stderr; + zend_hash_destroy(&php_output_handler_aliases); + zend_hash_destroy(&php_output_handler_conflicts); + zend_hash_destroy(&php_output_handler_reverse_conflicts); +} +/* }}} */ + +/* {{{ SUCCESS|FAILURE php_output_activate(TSRMLS_D) + * Reset output globals and setup the output handler stack */ +PHPAPI int php_output_activate(TSRMLS_D) +{ #ifdef ZTS - ts_allocate_id(&output_globals_id, sizeof(php_output_globals), (ts_allocate_ctor) php_output_init_globals, NULL); + memset((*((void ***) tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(output_globals_id)], 0, sizeof(zend_output_globals)); #else - php_output_init_globals(&output_globals TSRMLS_CC); + memset(&output_globals, 0, sizeof(zend_output_globals)); #endif + + zend_stack_init(&OG(handlers)); + OG(flags) |= PHP_OUTPUT_ACTIVATED; + + return SUCCESS; } /* }}} */ -/* {{{ php_output_activate - * Initilize output global for activation */ -PHPAPI void php_output_activate(TSRMLS_D) +/* {{{ void php_output_deactivate(TSRMLS_D) + * Destroy the output handler stack */ +PHPAPI void php_output_deactivate(TSRMLS_D) { - OG(php_body_write) = php_ub_body_write; - OG(php_header_write) = sapi_module.ub_write; - OG(ob_nesting_level) = 0; - OG(ob_lock) = 0; - OG(disable_output) = 0; - OG(output_start_filename) = NULL; - OG(output_start_lineno) = 0; + php_output_handler **handler = NULL; + + php_output_header(TSRMLS_C); + + OG(flags) ^= PHP_OUTPUT_ACTIVATED; + OG(active) = NULL; + OG(running) = NULL; + + /* release all output handlers */ + if (OG(handlers).elements) { + while (SUCCESS == zend_stack_top(&OG(handlers), (void *) &handler)) { + php_output_handler_free(handler TSRMLS_CC); + zend_stack_del_top(&OG(handlers)); + } + zend_stack_destroy(&OG(handlers)); + } + } /* }}} */ -/* {{{ php_output_register_constants */ -void php_output_register_constants(TSRMLS_D) +/* {{{ void php_output_register_constants() */ +PHPAPI void php_output_register_constants(TSRMLS_D) { REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_START", PHP_OUTPUT_HANDLER_START, CONST_CS | CONST_PERSISTENT); - REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_CONT", PHP_OUTPUT_HANDLER_CONT, CONST_CS | CONST_PERSISTENT); - REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_END", PHP_OUTPUT_HANDLER_END, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_WRITE", PHP_OUTPUT_HANDLER_WRITE, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_FLUSH", PHP_OUTPUT_HANDLER_FLUSH, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_CLEAN", PHP_OUTPUT_HANDLER_CLEAN, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_FINAL", PHP_OUTPUT_HANDLER_FINAL, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_CONT", PHP_OUTPUT_HANDLER_WRITE, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_END", PHP_OUTPUT_HANDLER_FINAL, CONST_CS | CONST_PERSISTENT); + + REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_CLEANABLE", PHP_OUTPUT_HANDLER_CLEANABLE, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_FLUSHABLE", PHP_OUTPUT_HANDLER_FLUSHABLE, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_REMOVABLE", PHP_OUTPUT_HANDLER_REMOVABLE, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_STDFLAGS", PHP_OUTPUT_HANDLER_STDFLAGS, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_STARTED", PHP_OUTPUT_HANDLER_STARTED, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_DISABLED", PHP_OUTPUT_HANDLER_DISABLED, CONST_CS | CONST_PERSISTENT); } /* }}} */ -/* {{{ php_output_set_status - * Toggle output status. Do NOT use in application code, only in SAPIs where appropriate. */ -PHPAPI void php_output_set_status(zend_bool status TSRMLS_DC) +/* {{{ void php_output_set_status(int status TSRMLS_DC) + * Used by SAPIs to disable output */ +PHPAPI void php_output_set_status(int status TSRMLS_DC) { - OG(disable_output) = !status; + OG(flags) = status & 0xf; } /* }}} */ -/* {{{ php_body_write - * Write body part */ -PHPAPI int php_body_write(const char *str, uint str_length TSRMLS_DC) +/* {{{ int php_output_get_status(TSRMLS_C) + * Get output control status */ +PHPAPI int php_output_get_status(TSRMLS_D) { - return OG(php_body_write)(str, str_length TSRMLS_CC); + return ( + OG(flags) + | (OG(active) ? PHP_OUTPUT_ACTIVE : 0) + | (OG(running)? PHP_OUTPUT_LOCKED : 0) + ) & 0xff; } /* }}} */ -/* {{{ php_header_write - * Write HTTP header */ -PHPAPI int php_header_write(const char *str, uint str_length TSRMLS_DC) +/* {{{ int php_output_write_unbuffered(const char *str, size_t len TSRMLS_DC) + * Unbuffered write */ +PHPAPI int php_output_write_unbuffered(const char *str, size_t len TSRMLS_DC) { - if (OG(disable_output)) { + if (OG(flags) & PHP_OUTPUT_DISABLED) { return 0; - } else { - return OG(php_header_write)(str, str_length TSRMLS_CC); } + if (OG(flags) & PHP_OUTPUT_ACTIVATED) { + return sapi_module.ub_write(str, len TSRMLS_CC); + } + return php_output_direct(str, len); } /* }}} */ -/* {{{ php_start_ob_buffer - * Start output buffering */ -PHPAPI int php_start_ob_buffer(zval *output_handler, uint chunk_size, zend_bool erase TSRMLS_DC) +/* {{{ int php_output_write(const char *str, size_t len TSRMLS_DC) + * Buffered write */ +PHPAPI int php_output_write(const char *str, size_t len TSRMLS_DC) { - uint initial_size, block_size; + if (OG(flags) & PHP_OUTPUT_DISABLED) { + return 0; + } + if (OG(flags) & PHP_OUTPUT_ACTIVATED) { + php_output_op(PHP_OUTPUT_HANDLER_WRITE, str, len TSRMLS_CC); + return (int) len; + } + return php_output_direct(str, len); +} +/* }}} */ - if (OG(ob_lock)) { - if (SG(headers_sent) && !SG(request_info).headers_only) { - OG(php_body_write) = php_ub_body_write_no_header; - } else { - OG(php_body_write) = php_ub_body_write; +/* {{{ SUCCESS|FAILURE php_output_flush(TSRMLS_D) + * Flush the most recent output handlers buffer */ +PHPAPI int php_output_flush(TSRMLS_D) +{ + php_output_context context; + + if (OG(active) && (OG(active)->flags & PHP_OUTPUT_HANDLER_FLUSHABLE)) { + php_output_context_init(&context, PHP_OUTPUT_HANDLER_FLUSH TSRMLS_CC); + php_output_handler_op(OG(active), &context); + if (context.out.data && context.out.used) { + zend_stack_del_top(&OG(handlers)); + php_output_write(context.out.data, context.out.used TSRMLS_CC); + zend_stack_push(&OG(handlers), &OG(active), sizeof(php_output_handler *)); } - OG(ob_nesting_level) = 0; - php_error_docref("ref.outcontrol" TSRMLS_CC, E_ERROR, "Cannot use output buffering in output buffering display handlers"); - return FAILURE; + php_output_context_dtor(&context); + return SUCCESS; } - if (chunk_size > 0) { - if (chunk_size==1) { - chunk_size = 4096; - } - initial_size = (chunk_size*3/2); - block_size = chunk_size/2; - } else { - initial_size = 40*1024; - block_size = 10*1024; + return FAILURE; +} +/* }}} */ + +/* {{{ void php_output_flush_all(TSRMLS_C) + * Flush all output buffers subsequently */ +PHPAPI void php_output_flush_all(TSRMLS_D) +{ + if (OG(active)) { + php_output_op(PHP_OUTPUT_HANDLER_FLUSH, NULL, 0 TSRMLS_CC); } - return php_ob_init(initial_size, block_size, output_handler, chunk_size, erase TSRMLS_CC); } /* }}} */ -/* {{{ php_start_ob_buffer_named - * Start output buffering */ -PHPAPI int php_start_ob_buffer_named(const char *output_handler_name, uint chunk_size, zend_bool erase TSRMLS_DC) +/* {{{ SUCCESS|FAILURE php_output_clean(TSRMLS_D) + * Cleans the most recent output handlers buffer if the handler is cleanable */ +PHPAPI int php_output_clean(TSRMLS_D) { - zval *output_handler; - int result; + php_output_context context; - ALLOC_INIT_ZVAL(output_handler); - Z_STRLEN_P(output_handler) = strlen(output_handler_name); /* this can be optimized */ - Z_STRVAL_P(output_handler) = estrndup(output_handler_name, Z_STRLEN_P(output_handler)); - Z_TYPE_P(output_handler) = IS_STRING; - result = php_start_ob_buffer(output_handler, chunk_size, erase TSRMLS_CC); - zval_dtor(output_handler); - FREE_ZVAL(output_handler); - return result; + if (OG(active) && (OG(active)->flags & PHP_OUTPUT_HANDLER_CLEANABLE)) { + OG(active)->buffer.used = 0; + php_output_context_init(&context, PHP_OUTPUT_HANDLER_CLEAN TSRMLS_CC); + php_output_handler_op(OG(active), &context); + php_output_context_dtor(&context); + return SUCCESS; + } + return FAILURE; } /* }}} */ -/* {{{ php_end_ob_buffer - * End output buffering (one level) */ -PHPAPI void php_end_ob_buffer(zend_bool send_buffer, zend_bool just_flush TSRMLS_DC) +/* {{{ void php_output_clean_all(TSRMLS_D) + * Cleans all output handler buffers, without regard whether the handler is cleanable */ +PHPAPI void php_output_clean_all(TSRMLS_D) { - char *final_buffer=NULL; - unsigned int final_buffer_length=0; - zval *alternate_buffer=NULL; - char *to_be_destroyed_buffer, *to_be_destroyed_handler_name; - char *to_be_destroyed_handled_output[2] = { 0, 0 }; - int status; - php_ob_buffer *prev_ob_buffer_p=NULL; - php_ob_buffer orig_ob_buffer; + php_output_context context; - if (OG(ob_nesting_level)==0) { - return; + if (OG(active)) { + php_output_context_init(&context, PHP_OUTPUT_HANDLER_CLEAN TSRMLS_CC); + zend_stack_apply_with_argument(&OG(handlers), ZEND_STACK_APPLY_TOPDOWN, php_output_stack_apply_clean, &context); } - status = 0; - if (!OG(active_ob_buffer).status & PHP_OUTPUT_HANDLER_START) { - /* our first call */ - status |= PHP_OUTPUT_HANDLER_START; +} + +/* {{{ SUCCESS|FAILURE php_output_end(TSRMLS_D) + * Finalizes the most recent output handler at pops it off the stack if the handler is removable */ +PHPAPI int php_output_end(TSRMLS_D) +{ + if (php_output_stack_pop(PHP_OUTPUT_POP_TRY TSRMLS_CC)) { + return SUCCESS; } - if (just_flush) { - status |= PHP_OUTPUT_HANDLER_CONT; + return FAILURE; +} +/* }}} */ + +/* {{{ void php_output_end_all(TSRMLS_D) + * Finalizes all output handlers and ends output buffering without regard whether a handler is removable */ +PHPAPI void php_output_end_all(TSRMLS_D) +{ + while (OG(active) && php_output_stack_pop(PHP_OUTPUT_POP_FORCE TSRMLS_CC)); +} +/* }}} */ + +/* {{{ SUCCESS|FAILURE php_output_discard(TSRMLS_D) + * Discards the most recent output handlers buffer and pops it off the stack if the handler is removable */ +PHPAPI int php_output_discard(TSRMLS_D) +{ + if (php_output_stack_pop(PHP_OUTPUT_POP_DISCARD|PHP_OUTPUT_POP_TRY TSRMLS_CC)) { + return SUCCESS; + } + return FAILURE; +} +/* }}} */ + +/* {{{ void php_output_discard_all(TSRMLS_D) + * Discard all output handlers and buffers without regard whether a handler is removable */ +PHPAPI void php_output_discard_all(TSRMLS_D) +{ + while (OG(active)) { + php_output_stack_pop(PHP_OUTPUT_POP_DISCARD|PHP_OUTPUT_POP_FORCE TSRMLS_CC); + } +} +/* }}} */ + +/* {{{ int php_output_get_level(TSRMLS_D) + * Get output buffering level, ie. how many output handlers the stack contains */ +PHPAPI int php_output_get_level(TSRMLS_D) +{ + return OG(active) ? zend_stack_count(&OG(handlers)) : 0; +} +/* }}} */ + +/* {{{ SUCCESS|FAILURE php_output_get_contents(zval *z TSRMLS_DC) + * Get the contents of the active output handlers buffer */ +PHPAPI int php_output_get_contents(zval *p TSRMLS_DC) +{ + if (OG(active)) { + ZVAL_STRINGL(p, OG(active)->buffer.data, OG(active)->buffer.used, 1); + return SUCCESS; } else { - status |= PHP_OUTPUT_HANDLER_END; + ZVAL_NULL(p); + return FAILURE; } +} -#if 0 - { - FILE *fp; - fp = fopen("/tmp/ob_log", "a"); - fprintf(fp, "NestLevel: %d ObStatus: %d HandlerName: %s\n", OG(ob_nesting_level), status, OG(active_ob_buffer).handler_name); - fclose(fp); - } -#endif +/* {{{ SUCCESS|FAILURE php_output_get_length(zval *z TSRMLS_DC) + * Get the length of the active output handlers buffer */ +PHPAPI int php_output_get_length(zval *p TSRMLS_DC) +{ + if (OG(active)) { + ZVAL_LONG(p, OG(active)->buffer.used); + return SUCCESS; + } else { + ZVAL_NULL(p); + return FAILURE; + } +} +/* }}} */ - if (OG(active_ob_buffer).internal_output_handler) { - final_buffer = OG(active_ob_buffer).internal_output_handler_buffer; - final_buffer_length = OG(active_ob_buffer).internal_output_handler_buffer_size; - OG(active_ob_buffer).internal_output_handler(OG(active_ob_buffer).buffer, OG(active_ob_buffer).text_length, &final_buffer, &final_buffer_length, status TSRMLS_CC); - } else if (OG(active_ob_buffer).output_handler) { - zval **params[2]; - zval *orig_buffer; - zval *z_status; +/* {{{ php_output_handler* php_output_get_active_handler(TSRMLS_D) + * Get active output handler */ +PHPAPI php_output_handler* php_output_get_active_handler(TSRMLS_D) +{ + return OG(active); +} +/* }}} */ - ALLOC_INIT_ZVAL(orig_buffer); - ZVAL_STRINGL(orig_buffer, OG(active_ob_buffer).buffer, OG(active_ob_buffer).text_length, 1); +/* {{{ SUCCESS|FAILURE php_output_handler_start_default(TSRMLS_D) + * Start a "default output handler" */ +PHPAPI int php_output_start_default(TSRMLS_D) +{ + php_output_handler *handler; - ALLOC_INIT_ZVAL(z_status); - ZVAL_LONG(z_status, status); + handler = php_output_handler_create_internal(ZEND_STRL(php_output_default_handler_name), php_output_handler_default_func, 0, PHP_OUTPUT_HANDLER_STDFLAGS TSRMLS_CC); + if (SUCCESS == php_output_handler_start(handler TSRMLS_CC)) { + return SUCCESS; + } + php_output_handler_free(&handler TSRMLS_CC); + return FAILURE; +} +/* }}} */ - params[0] = &orig_buffer; - params[1] = &z_status; - OG(ob_lock) = 1; +/* {{{ SUCCESS|FAILURE php_output_handler_start_devnull(TSRMLS_D) + * Start a "null output handler" */ +PHPAPI int php_output_start_devnull(TSRMLS_D) +{ + php_output_handler *handler; - if (call_user_function_ex(CG(function_table), NULL, OG(active_ob_buffer).output_handler, &alternate_buffer, 2, params, 1, NULL TSRMLS_CC)==SUCCESS) { - if (alternate_buffer && !(Z_TYPE_P(alternate_buffer)==IS_BOOL && Z_BVAL_P(alternate_buffer)==0)) { - convert_to_string_ex(&alternate_buffer); - final_buffer = Z_STRVAL_P(alternate_buffer); - final_buffer_length = Z_STRLEN_P(alternate_buffer); - } - } - OG(ob_lock) = 0; - if (!just_flush) { - zval_ptr_dtor(&OG(active_ob_buffer).output_handler); - } - zval_ptr_dtor(&orig_buffer); - zval_ptr_dtor(&z_status); + handler = php_output_handler_create_internal(ZEND_STRL(php_output_devnull_handler_name), php_output_handler_devnull_func, PHP_OUTPUT_HANDLER_DEFAULT_SIZE, 0 TSRMLS_CC); + if (SUCCESS == php_output_handler_start(handler TSRMLS_CC)) { + return SUCCESS; } + php_output_handler_free(&handler TSRMLS_CC); + return FAILURE; +} +/* }}} */ - if (!final_buffer) { - final_buffer = OG(active_ob_buffer).buffer; - final_buffer_length = OG(active_ob_buffer).text_length; +/* {{{ SUCCESS|FAILURE php_output_start_user(zval *handler, size_t chunk_size, int flags TSRMLS_DC) + * Start a user level output handler */ +PHPAPI int php_output_start_user(zval *output_handler, size_t chunk_size, int flags TSRMLS_DC) +{ + php_output_handler *handler; + + if (output_handler) { + handler = php_output_handler_create_user(output_handler, chunk_size, flags TSRMLS_CC); + } else { + handler = php_output_handler_create_internal(ZEND_STRL(php_output_default_handler_name), php_output_handler_default_func, chunk_size, flags TSRMLS_CC); } + if (SUCCESS == php_output_handler_start(handler TSRMLS_CC)) { + return SUCCESS; + } + php_output_handler_free(&handler TSRMLS_CC); + return FAILURE; +} +/* }}} */ - if (OG(ob_nesting_level)==1) { /* end buffering */ - if (SG(headers_sent) && !SG(request_info).headers_only) { - OG(php_body_write) = php_ub_body_write_no_header; - } else { - OG(php_body_write) = php_ub_body_write; - } +/* {{{ SUCCESS|FAILURE php_output_start_internal(zval *name, php_output_handler_func_t handler, size_t chunk_size, int flags TSRMLS_DC) + * Start an internal output handler that does not have to maintain a non-global state */ +PHPAPI int php_output_start_internal(const char *name, size_t name_len, php_output_handler_func_t output_handler, size_t chunk_size, int flags TSRMLS_DC) +{ + php_output_handler *handler; + + handler = php_output_handler_create_internal(name, name_len, php_output_handler_compat_func, chunk_size, flags TSRMLS_CC); + php_output_handler_set_context(handler, output_handler, NULL TSRMLS_CC); + if (SUCCESS == php_output_handler_start(handler TSRMLS_CC)) { + return SUCCESS; } + php_output_handler_free(&handler TSRMLS_CC); + return FAILURE; +} +/* }}} */ - to_be_destroyed_buffer = OG(active_ob_buffer).buffer; - to_be_destroyed_handler_name = OG(active_ob_buffer).handler_name; - if (OG(active_ob_buffer).internal_output_handler - && (final_buffer != OG(active_ob_buffer).internal_output_handler_buffer) - && (final_buffer != OG(active_ob_buffer).buffer)) { - to_be_destroyed_handled_output[0] = final_buffer; +/* {{{ php_output_handler *php_output_handler_create_user(zval *handler, size_t chunk_size, int flags TSRMLS_DC) + * Create a user level output handler */ +PHPAPI php_output_handler *php_output_handler_create_user(zval *output_handler, size_t chunk_size, int flags TSRMLS_DC) +{ + char *handler_name = NULL, *error = NULL; + php_output_handler *handler = NULL; + php_output_handler_alias_ctor_t *alias = NULL; + php_output_handler_user_func_t *user = NULL; + + switch (Z_TYPE_P(output_handler)) { + case IS_NULL: + handler = php_output_handler_create_internal(ZEND_STRL(php_output_default_handler_name), php_output_handler_default_func, chunk_size, flags TSRMLS_CC); + break; + case IS_STRING: + if (Z_STRLEN_P(output_handler) && (alias = php_output_handler_alias(Z_STRVAL_P(output_handler), Z_STRLEN_P(output_handler) TSRMLS_CC))) { + handler = (*alias)(Z_STRVAL_P(output_handler), Z_STRLEN_P(output_handler), chunk_size, flags TSRMLS_CC); + break; + } + default: + user = ecalloc(1, sizeof(php_output_handler_user_func_t)); + if (SUCCESS == zend_fcall_info_init(output_handler, 0, &user->fci, &user->fcc, &handler_name, &error TSRMLS_CC)) { + handler = php_output_handler_init(handler_name, strlen(handler_name), chunk_size, (flags & ~0xf) | PHP_OUTPUT_HANDLER_USER TSRMLS_CC); + Z_ADDREF_P(output_handler); + user->zoh = output_handler; + handler->func.user = user; + } else { + efree(user); + } + if (error) { + php_error_docref("ref.outcontrol" TSRMLS_CC, E_WARNING, "%s", error); + efree(error); + } + if (handler_name) { + efree(handler_name); + } } - if (!just_flush) { - if (OG(active_ob_buffer).internal_output_handler) { - to_be_destroyed_handled_output[1] = OG(active_ob_buffer).internal_output_handler_buffer; + return handler; +} +/* }}} */ + +/* {{{ php_output_handler *php_output_handler_create_internal(zval *name, php_output_handler_context_func_t handler, size_t chunk_size, int flags TSRMLS_DC) + * Create an internal output handler that can maintain a non-global state */ +PHPAPI php_output_handler *php_output_handler_create_internal(const char *name, size_t name_len, php_output_handler_context_func_t output_handler, size_t chunk_size, int flags TSRMLS_DC) +{ + php_output_handler *handler; + + handler = php_output_handler_init(name, name_len, chunk_size, (flags & ~0xf) | PHP_OUTPUT_HANDLER_INTERNAL TSRMLS_CC); + handler->func.internal = output_handler; + + return handler; +} +/* }}} */ + +/* {{{ void php_output_handler_set_context(php_output_handler *handler, void *opaq, void (*dtor)(void* TSRMLS_DC) TSRMLS_DC) + * Set the context/state of an output handler. Calls the dtor of the previous context if there is one */ +PHPAPI void php_output_handler_set_context(php_output_handler *handler, void *opaq, void (*dtor)(void* TSRMLS_DC) TSRMLS_DC) +{ + if (handler->dtor && handler->opaq) { + handler->dtor(handler->opaq TSRMLS_CC); + } + handler->dtor = dtor; + handler->opaq = opaq; +} +/* }}} */ + +/* {{{ SUCCESS|FAILURE php_output_handler_start(php_output_handler *handler TSRMLS_DC) + * Starts the set up output handler and pushes it on top of the stack. Checks for any conflicts regarding the output handler to start */ +PHPAPI int php_output_handler_start(php_output_handler *handler TSRMLS_DC) +{ + HashPosition pos; + HashTable *rconflicts; + php_output_handler_conflict_check_t *conflict; + + if (php_output_lock_error(PHP_OUTPUT_HANDLER_START TSRMLS_CC) || !handler) { + return FAILURE; + } + if (SUCCESS == zend_hash_find(&php_output_handler_conflicts, handler->name, handler->name_len+1, (void *) &conflict)) { + if (SUCCESS != (*conflict)(handler->name, handler->name_len TSRMLS_CC)) { + return FAILURE; } } - if (OG(ob_nesting_level)>1) { /* restore previous buffer */ - zend_stack_top(&OG(ob_buffers), (void **) &prev_ob_buffer_p); - orig_ob_buffer = OG(active_ob_buffer); - OG(active_ob_buffer) = *prev_ob_buffer_p; - zend_stack_del_top(&OG(ob_buffers)); - if (!just_flush && OG(ob_nesting_level)==2) { /* destroy the stack */ - zend_stack_destroy(&OG(ob_buffers)); + if (SUCCESS == zend_hash_find(&php_output_handler_reverse_conflicts, handler->name, handler->name_len+1, (void *) &rconflicts)) { + for (zend_hash_internal_pointer_reset_ex(rconflicts, &pos); + zend_hash_get_current_data_ex(rconflicts, (void *) &conflict, &pos) == SUCCESS; + zend_hash_move_forward_ex(rconflicts, &pos) + ) { + if (SUCCESS != (*conflict)(handler->name, handler->name_len TSRMLS_CC)) { + return FAILURE; + } } } - OG(ob_nesting_level)--; + /* zend_stack_push never returns SUCCESS but FAILURE or stack level */ + if (FAILURE == (handler->level = zend_stack_push(&OG(handlers), &handler, sizeof(php_output_handler *)))) { + return FAILURE; + } + OG(active) = handler; + return SUCCESS; +} +/* }}} */ - if (send_buffer) { - if (just_flush) { /* if flush is called prior to proper end, ensure presence of NUL */ - final_buffer[final_buffer_length] = '\0'; +/* {{{ int php_output_handler_started(zval *name TSRMLS_DC) + * Check whether a certain output handler is in use */ +PHPAPI int php_output_handler_started(const char *name, size_t name_len TSRMLS_DC) +{ + php_output_handler ***handlers; + int i, count = php_output_get_level(TSRMLS_C); + + if (count) { + handlers = (php_output_handler ***) zend_stack_base(&OG(handlers)); + + for (i = 0; i < count; ++i) { + if (name_len == (*(handlers[i]))->name_len && !memcmp((*(handlers[i]))->name, name, name_len)) { + return 1; + } } - OG(php_body_write)(final_buffer, final_buffer_length TSRMLS_CC); } - if (just_flush) { /* we restored the previous ob, return to the current */ - if (prev_ob_buffer_p) { - zend_stack_push(&OG(ob_buffers), &OG(active_ob_buffer), sizeof(php_ob_buffer)); - OG(active_ob_buffer) = orig_ob_buffer; + return 0; +} +/* }}} */ + +/* {{{ int php_output_handler_conflict(zval *handler_new, zval *handler_old TSRMLS_DC) + * Check whether a certain handler is in use and issue a warning that the new handler would conflict with the already used one */ +PHPAPI int php_output_handler_conflict(const char *handler_new, size_t handler_new_len, const char *handler_set, size_t handler_set_len TSRMLS_DC) +{ + if (php_output_handler_started(handler_set, handler_set_len TSRMLS_CC)) { + if (handler_new_len != handler_set_len || memcmp(handler_new, handler_set, handler_set_len)) { + php_error_docref("ref.outcontrol" TSRMLS_CC, E_WARNING, "output handler '%s' conflicts with '%s'", handler_new, handler_set); + } else { + php_error_docref("ref.outcontrol" TSRMLS_CC, E_WARNING, "output handler '%s' cannot be used twice", handler_new); } - OG(ob_nesting_level)++; + return 1; } + return 0; +} +/* }}} */ - if (alternate_buffer) { - zval_ptr_dtor(&alternate_buffer); +/* {{{ SUCCESS|FAILURE php_output_handler_conflict_register(zval *name, php_output_handler_conflict_check_t check_func TSRMLS_DC) + * Register a conflict checking function on MINIT */ +PHPAPI int php_output_handler_conflict_register(const char *name, size_t name_len, php_output_handler_conflict_check_t check_func TSRMLS_DC) +{ + if (!EG(current_module)) { + zend_error(E_ERROR, "Cannot register an output handler conflict outside of MINIT"); + return FAILURE; } + return zend_hash_update(&php_output_handler_conflicts, name, name_len+1, &check_func, sizeof(php_output_handler_conflict_check_t *), NULL); +} +/* }}} */ - if (status & PHP_OUTPUT_HANDLER_END) { - efree(to_be_destroyed_handler_name); +/* {{{ SUCCESS|FAILURE php_output_handler_reverse_conflict_register(zval *name, php_output_handler_conflict_check_t check_func TSRMLS_DC) + * Register a reverse conflict checking function on MINIT */ +PHPAPI int php_output_handler_reverse_conflict_register(const char *name, size_t name_len, php_output_handler_conflict_check_t check_func TSRMLS_DC) +{ + HashTable rev, *rev_ptr = NULL; + + if (!EG(current_module)) { + zend_error(E_ERROR, "Cannot register a reverse output handler conflict outside of MINIT"); + return FAILURE; } - if (!just_flush) { - efree(to_be_destroyed_buffer); + + if (SUCCESS == zend_hash_find(&php_output_handler_reverse_conflicts, name, name_len+1, (void *) &rev_ptr)) { + return zend_hash_next_index_insert(rev_ptr, &check_func, sizeof(php_output_handler_conflict_check_t *), NULL); } else { - OG(active_ob_buffer).text_length = 0; - OG(active_ob_buffer).status |= PHP_OUTPUT_HANDLER_START; - OG(php_body_write) = php_b_body_write; + zend_hash_init(&rev, 1, NULL, NULL, 1); + if (SUCCESS != zend_hash_next_index_insert(&rev, &check_func, sizeof(php_output_handler_conflict_check_t *), NULL)) { + zend_hash_destroy(&rev); + return FAILURE; + } + if (SUCCESS != zend_hash_update(&php_output_handler_reverse_conflicts, name, name_len+1, &rev, sizeof(HashTable), NULL)) { + zend_hash_destroy(&rev); + return FAILURE; + } + return SUCCESS; } - if (to_be_destroyed_handled_output[0]) { - efree(to_be_destroyed_handled_output[0]); +} +/* }}} */ + +/* {{{ php_output_handler_alias_ctor_t php_output_handler_alias(zval *name TSRMLS_DC) + * Get an internal output handler for a user handler if it exists */ +PHPAPI php_output_handler_alias_ctor_t *php_output_handler_alias(const char *name, size_t name_len TSRMLS_DC) +{ + php_output_handler_alias_ctor_t *func = NULL; + + zend_hash_find(&php_output_handler_aliases, name, name_len+1, (void *) &func); + return func; +} +/* }}} */ + +/* {{{ SUCCESS|FAILURE php_output_handler_alias_register(zval *name, php_output_handler_alias_ctor_t func TSRMLS_DC) + * Registers an internal output handler as alias for a user handler */ +PHPAPI int php_output_handler_alias_register(const char *name, size_t name_len, php_output_handler_alias_ctor_t func TSRMLS_DC) +{ + if (!EG(current_module)) { + zend_error(E_ERROR, "Cannot register an output handler alias outside of MINIT"); + return FAILURE; } - if (to_be_destroyed_handled_output[1]) { - efree(to_be_destroyed_handled_output[1]); + return zend_hash_update(&php_output_handler_aliases, name, name_len+1, &func, sizeof(php_output_handler_alias_ctor_t *), NULL); +} +/* }}} */ + +/* {{{ SUCCESS|FAILURE php_output_handler_hook(php_output_handler_hook_t type, void *arg TSMRLS_DC) + * Output handler hook for output handler functions to check/modify the current handlers abilities */ +PHPAPI int php_output_handler_hook(php_output_handler_hook_t type, void *arg TSRMLS_DC) +{ + if (OG(running)) { + switch (type) { + case PHP_OUTPUT_HANDLER_HOOK_GET_OPAQ: + *(void ***) arg = &OG(running)->opaq; + return SUCCESS; + case PHP_OUTPUT_HANDLER_HOOK_GET_FLAGS: + *(int *) arg = OG(running)->flags; + return SUCCESS; + case PHP_OUTPUT_HANDLER_HOOK_GET_LEVEL: + *(int *) arg = OG(running)->level; + return SUCCESS; + case PHP_OUTPUT_HANDLER_HOOK_IMMUTABLE: + OG(running)->flags &= ~(PHP_OUTPUT_HANDLER_REMOVABLE|PHP_OUTPUT_HANDLER_CLEANABLE); + return SUCCESS; + case PHP_OUTPUT_HANDLER_HOOK_DISABLE: + OG(running)->flags |= PHP_OUTPUT_HANDLER_DISABLED; + return SUCCESS; + default: + break; + } } + return FAILURE; } /* }}} */ -/* {{{ php_end_ob_buffers - * End output buffering (all buffers) */ -PHPAPI void php_end_ob_buffers(zend_bool send_buffer TSRMLS_DC) +/* {{{ void php_output_handler_dtor(php_output_handler *handler TSRMLS_DC) + * Destroy an output handler */ +PHPAPI void php_output_handler_dtor(php_output_handler *handler TSRMLS_DC) { - while (OG(ob_nesting_level)!=0) { - php_end_ob_buffer(send_buffer, 0 TSRMLS_CC); + STR_FREE(handler->name); + STR_FREE(handler->buffer.data); + if (handler->flags & PHP_OUTPUT_HANDLER_USER) { + zval_ptr_dtor(&handler->func.user->zoh); + efree(handler->func.user); } + if (handler->dtor && handler->opaq) { + handler->dtor(handler->opaq TSRMLS_CC); + } + memset(handler, 0, sizeof(*handler)); } /* }}} */ -/* {{{ php_start_implicit_flush - */ -PHPAPI void php_start_implicit_flush(TSRMLS_D) +/* {{{ void php_output_handler_free(php_output_handler **handler TSMRLS_DC) + * Destroy and free an output handler */ +PHPAPI void php_output_handler_free(php_output_handler **h TSRMLS_DC) { - OG(implicit_flush) = 1; + if (*h) { + php_output_handler_dtor(*h TSRMLS_CC); + efree(*h); + *h = NULL; + } } /* }}} */ -/* {{{ php_end_implicit_flush - */ -PHPAPI void php_end_implicit_flush(TSRMLS_D) +/* void php_output_set_implicit_flush(int enabled TSRMLS_DC) + * Enable or disable implicit flush */ +PHPAPI void php_output_set_implicit_flush(int flush TSRMLS_DC) { - OG(implicit_flush) = 0; + if (flush) { + OG(flags) |= PHP_OUTPUT_IMPLICITFLUSH; + } else { + OG(flags) &= ~PHP_OUTPUT_IMPLICITFLUSH; + } } /* }}} */ -/* {{{ char *php_get_output_start_filename(TSRMLS_D) - * Return filename start output something */ -PHPAPI char *php_get_output_start_filename(TSRMLS_D) +/* {{{ char *php_output_get_start_filename(TSRMLS_D) + * Get the file name where output has started */ +PHPAPI const char *php_output_get_start_filename(TSRMLS_D) { return OG(output_start_filename); } /* }}} */ -/* {{{ char *php_get_output_start_lineno(TSRMLS_D) - * Return line number start output something */ -PHPAPI int php_get_output_start_lineno(TSRMLS_D) +/* {{{ int php_output_get_start_lineno(TSRMLS_D) + * Get the line number where output has started */ +PHPAPI int php_output_get_start_lineno(TSRMLS_D) { return OG(output_start_lineno); } /* }}} */ -/* {{{ php_ob_set_internal_handler - */ -PHPAPI void php_ob_set_internal_handler(php_output_handler_func_t internal_output_handler, uint buffer_size, char *handler_name, zend_bool erase TSRMLS_DC) +/* {{{ static int php_output_lock_error(int op TSRMLS_DC) + * Checks whether an unallowed operation is attempted from within the output handler and issues a fatal error */ +static inline int php_output_lock_error(int op TSRMLS_DC) { - if (OG(ob_nesting_level) == 0 || OG(active_ob_buffer).internal_output_handler || strcmp(OG(active_ob_buffer).handler_name, OB_DEFAULT_HANDLER_NAME)) { - php_start_ob_buffer(NULL, buffer_size, erase TSRMLS_CC); + /* if there's no ob active, ob has been stopped */ + if (op && OG(active) && OG(running)) { + /* fatal error */ + php_output_deactivate(TSRMLS_C); + php_error_docref("ref.outcontrol" TSRMLS_CC, E_ERROR, "Cannot use output buffering in output buffering display handlers"); + return 1; } + return 0; +} +/* }}} */ - OG(active_ob_buffer).internal_output_handler = internal_output_handler; - OG(active_ob_buffer).internal_output_handler_buffer = (char *) emalloc(buffer_size); - OG(active_ob_buffer).internal_output_handler_buffer_size = buffer_size; - if (OG(active_ob_buffer).handler_name) { - efree(OG(active_ob_buffer).handler_name); +/* {{{ static php_output_context *php_output_context_init(php_output_context *context, int op TSRMLS_DC) + * Initialize a new output context */ +static inline php_output_context *php_output_context_init(php_output_context *context, int op TSRMLS_DC) +{ + if (!context) { + context = emalloc(sizeof(php_output_context)); } - OG(active_ob_buffer).handler_name = estrdup(handler_name); - OG(active_ob_buffer).erase = erase; + + memset(context, 0, sizeof(php_output_context)); + TSRMLS_SET_CTX(context->tsrm_ls); + context->op = op; + + return context; } /* }}} */ -/* - * Output buffering - implementation - */ - -/* {{{ php_ob_allocate - */ -static inline void php_ob_allocate(uint text_length TSRMLS_DC) +/* {{{ static void php_output_context_reset(php_output_context *context) + * Reset an output context */ +static inline void php_output_context_reset(php_output_context *context) { - uint new_len = OG(active_ob_buffer).text_length + text_length; + int op = context->op; + php_output_context_dtor(context); + memset(context, 0, sizeof(php_output_context)); + context->op = op; +} +/* }}} */ - if (OG(active_ob_buffer).size < new_len) { - uint buf_size = OG(active_ob_buffer).size; - while (buf_size <= new_len) { - buf_size += OG(active_ob_buffer).block_size; - } - - OG(active_ob_buffer).buffer = (char *) erealloc(OG(active_ob_buffer).buffer, buf_size+1); - OG(active_ob_buffer).size = buf_size; +/* {{{ static void php_output_context_feed(php_output_context *context, char *, size_t, size_t) + * Feed output contexts input buffer */ +static inline void php_output_context_feed(php_output_context *context, char *data, size_t size, size_t used, zend_bool free) +{ + if (context->in.free && context->in.data) { + efree(context->in.data); } - OG(active_ob_buffer).text_length = new_len; + context->in.data = data; + context->in.used = used; + context->in.free = free; + context->in.size = size; } /* }}} */ -/* {{{ php_ob_init_conflict - * Returns 1 if handler_set is already used and generates error message */ -PHPAPI int php_ob_init_conflict(char *handler_new, char *handler_set TSRMLS_DC) +/* {{{ static void php_output_context_swap(php_output_context *context) + * Swap output contexts buffers */ +static inline void php_output_context_swap(php_output_context *context) { - if (php_ob_handler_used(handler_set TSRMLS_CC)) { - php_error_docref("ref.outcontrol" TSRMLS_CC, E_WARNING, "output handler '%s' conflicts with '%s'", handler_new, handler_set); - return 1; + if (context->in.free && context->in.data) { + efree(context->in.data); } - return 0; + context->in.data = context->out.data; + context->in.used = context->out.used; + context->in.free = context->out.free; + context->in.size = context->out.size; + context->out.data = NULL; + context->out.used = 0; + context->out.free = 0; + context->out.size = 0; } /* }}} */ -/* {{{ php_ob_init_named - */ -static int php_ob_init_named(uint initial_size, uint block_size, char *handler_name, zval *output_handler, uint chunk_size, zend_bool erase TSRMLS_DC) +/* {{{ static void php_output_context_pass(php_output_context *context) + * Pass input to output buffer */ +static inline void php_output_context_pass(php_output_context *context) { - php_ob_buffer tmp_buf; + context->out.data = context->in.data; + context->out.used = context->in.used; + context->out.size = context->in.size; + context->out.free = context->in.free; + context->in.data = NULL; + context->in.used = 0; + context->in.free = 0; + context->in.size = 0; +} +/* }}} */ - if (output_handler && !zend_is_callable(output_handler, 0, NULL TSRMLS_CC)) { - return FAILURE; +/* {{{ static void php_output_context_dtor(php_output_context *context) + * Destroy the contents of an output context */ +static inline void php_output_context_dtor(php_output_context *context) +{ + if (context->in.free && context->in.data) { + efree(context->in.data); + context->in.data = NULL; } + if (context->out.free && context->out.data) { + efree(context->out.data); + context->out.data = NULL; + } +} +/* }}} */ - tmp_buf.block_size = block_size; - tmp_buf.size = initial_size; - tmp_buf.buffer = (char *) emalloc(initial_size+1); - tmp_buf.text_length = 0; - tmp_buf.output_handler = output_handler; - tmp_buf.chunk_size = chunk_size; - tmp_buf.status = 0; - tmp_buf.internal_output_handler = NULL; - tmp_buf.internal_output_handler_buffer = NULL; - tmp_buf.internal_output_handler_buffer_size = 0; - tmp_buf.handler_name = estrdup(handler_name&&handler_name[0]?handler_name:OB_DEFAULT_HANDLER_NAME); - tmp_buf.erase = erase; +/* {{{ static php_output_handler *php_output_handler_init(zval *name, size_t chunk_size, int flags TSRMLS_DC) + * Allocates and initializes a php_output_handler structure */ +static inline php_output_handler *php_output_handler_init(const char *name, size_t name_len, size_t chunk_size, int flags TSRMLS_DC) +{ + php_output_handler *handler; - if (OG(ob_nesting_level)>0) { -#if HAVE_ZLIB && !defined(COMPILE_DL_ZLIB) - if (!strncmp(handler_name, "ob_gzhandler", sizeof("ob_gzhandler")) && php_ob_gzhandler_check(TSRMLS_C)) { - return FAILURE; - } -#endif - if (OG(ob_nesting_level)==1) { /* initialize stack */ - zend_stack_init(&OG(ob_buffers)); - } - zend_stack_push(&OG(ob_buffers), &OG(active_ob_buffer), sizeof(php_ob_buffer)); - } - OG(ob_nesting_level)++; - OG(active_ob_buffer) = tmp_buf; - OG(php_body_write) = php_b_body_write; - return SUCCESS; + handler = ecalloc(1, sizeof(php_output_handler)); + handler->name = estrndup(name, name_len); + handler->name_len = name_len; + handler->size = chunk_size; + handler->flags = flags; + handler->buffer.size = PHP_OUTPUT_HANDLER_INITBUF_SIZE(chunk_size); + handler->buffer.data = emalloc(handler->buffer.size); + + return handler; } /* }}} */ -/* {{{ php_ob_handler_from_string - * Create zval output handler from string */ -static zval* php_ob_handler_from_string(const char *handler_name, int len TSRMLS_DC) +/* {{{ static int php_output_handler_appen(php_output_handler *handler, const php_output_buffer *buf TSRMLS_DC) + * Appends input to the output handlers buffer and indicates whether the buffer does not have to be processed by the output handler */ +static inline int php_output_handler_append(php_output_handler *handler, const php_output_buffer *buf TSRMLS_DC) { - zval *output_handler; + if (buf->used) { + OG(flags) |= PHP_OUTPUT_WRITTEN; + /* store it away */ + if ((handler->buffer.size - handler->buffer.used) <= buf->used) { + size_t grow_int = PHP_OUTPUT_HANDLER_INITBUF_SIZE(handler->size); + size_t grow_buf = PHP_OUTPUT_HANDLER_INITBUF_SIZE(buf->used - (handler->buffer.size - handler->buffer.used)); + size_t grow_max = MAX(grow_int, grow_buf); - ALLOC_INIT_ZVAL(output_handler); - Z_STRLEN_P(output_handler) = len; - Z_STRVAL_P(output_handler) = estrndup(handler_name, len); - Z_TYPE_P(output_handler) = IS_STRING; - return output_handler; + handler->buffer.data = erealloc(handler->buffer.data, handler->buffer.size + grow_max); + handler->buffer.size += grow_max; + } + memcpy(handler->buffer.data + handler->buffer.used, buf->data, buf->used); + handler->buffer.used += buf->used; + + /* chunked buffering */ + if (handler->size && (handler->buffer.used >= handler->size)) { + /* store away errors and/or any intermediate output */ + return OG(running) ? 1 : 0; + } + } + return 1; } /* }}} */ -/* {{{ php_ob_init - */ -static int php_ob_init(uint initial_size, uint block_size, zval *output_handler, uint chunk_size, zend_bool erase TSRMLS_DC) +/* {{{ static php_output_handler_status_t php_output_handler_op(php_output_handler *handler, php_output_context *context) + * Output handler operation dispatcher, applying context op to the php_output_handler handler */ +static inline php_output_handler_status_t php_output_handler_op(php_output_handler *handler, php_output_context *context) { - int result = FAILURE, handler_len, len; - char *handler_name, *next_handler_name; - HashPosition pos; - zval **tmp; - zval *handler_zval; + php_output_handler_status_t status; + int original_op = context->op; + PHP_OUTPUT_TSRMLS(context); - if (output_handler && output_handler->type == IS_STRING) { - handler_name = Z_STRVAL_P(output_handler); - handler_len = Z_STRLEN_P(output_handler); +#if PHP_OUTPUT_DEBUG + fprintf(stderr, ">>> op(%d, " + "handler=%p, " + "name=%s, " + "flags=%d, " + "buffer.data=%s, " + "buffer.used=%zu, " + "buffer.size=%zu, " + "in.data=%s, " + "in.used=%zu)\n", + context->op, + handler, + handler->name, + handler->flags, + handler->buffer.used?handler->buffer.data:"", + handler->buffer.used, + handler->buffer.size, + context->in.used?context->in.data:"", + context->in.used + ); +#endif - result = SUCCESS; - if (handler_len && handler_name[0] != '\0') { - while ((next_handler_name=strchr(handler_name, ',')) != NULL) { - len = next_handler_name-handler_name; - next_handler_name = estrndup(handler_name, len); - handler_zval = php_ob_handler_from_string(next_handler_name, len TSRMLS_CC); - result = php_ob_init_named(initial_size, block_size, next_handler_name, handler_zval, chunk_size, erase TSRMLS_CC); - if (result != SUCCESS) { - zval_dtor(handler_zval); - FREE_ZVAL(handler_zval); + if (php_output_lock_error(context->op TSRMLS_CC)) { + /* fatal error */ + return PHP_OUTPUT_HANDLER_FAILURE; + } + + /* storable? */ + if (php_output_handler_append(handler, &context->in TSRMLS_CC) && !context->op) { + context->op = original_op; + return PHP_OUTPUT_HANDLER_NO_DATA; + } else { + /* need to start? */ + if (!(handler->flags & PHP_OUTPUT_HANDLER_STARTED)) { + context->op |= PHP_OUTPUT_HANDLER_START; + } + + OG(running) = handler; + if (handler->flags & PHP_OUTPUT_HANDLER_USER) { + zval *retval = NULL, *ob_data, *ob_mode; + + MAKE_STD_ZVAL(ob_data); + ZVAL_STRINGL(ob_data, handler->buffer.data, handler->buffer.used, 1); + MAKE_STD_ZVAL(ob_mode); + ZVAL_LONG(ob_mode, (long) context->op); + zend_fcall_info_argn(&handler->func.user->fci TSRMLS_CC, 2, &ob_data, &ob_mode); + +#define PHP_OUTPUT_USER_SUCCESS(retval) (retval && !(Z_TYPE_P(retval) == IS_BOOL && Z_BVAL_P(retval)==0)) + if (SUCCESS == zend_fcall_info_call(&handler->func.user->fci, &handler->func.user->fcc, &retval, NULL TSRMLS_CC) && PHP_OUTPUT_USER_SUCCESS(retval)) { + /* user handler may have returned TRUE */ + status = PHP_OUTPUT_HANDLER_NO_DATA; + if (Z_TYPE_P(retval) != IS_BOOL) { + convert_to_string_ex(&retval); + if (Z_STRLEN_P(retval)) { + context->out.data = estrndup(Z_STRVAL_P(retval), Z_STRLEN_P(retval)); + context->out.used = Z_STRLEN_P(retval); + context->out.free = 1; + status = PHP_OUTPUT_HANDLER_SUCCESS; + } } - handler_name += len+1; - handler_len -= len+1; - efree(next_handler_name); + } else { + /* call failed, pass internal buffer along */ + status = PHP_OUTPUT_HANDLER_FAILURE; } - } - if (result == SUCCESS) { - handler_zval = php_ob_handler_from_string(handler_name, handler_len TSRMLS_CC); - result = php_ob_init_named(initial_size, block_size, handler_name, handler_zval, chunk_size, erase TSRMLS_CC); - if (result != SUCCESS) { - zval_dtor(handler_zval); - FREE_ZVAL(handler_zval); + + zend_fcall_info_argn(&handler->func.user->fci TSRMLS_CC, 0); + zval_ptr_dtor(&ob_data); + zval_ptr_dtor(&ob_mode); + if (retval) { + zval_ptr_dtor(&retval); } - } - } else if (output_handler && output_handler->type == IS_ARRAY) { - /* do we have array(object,method) */ - if (zend_is_callable(output_handler, 0, &handler_name TSRMLS_CC)) { - SEPARATE_ZVAL(&output_handler); - Z_ADDREF_P(output_handler); - result = php_ob_init_named(initial_size, block_size, handler_name, output_handler, chunk_size, erase TSRMLS_CC); - efree(handler_name); + } else { - efree(handler_name); - /* init all array elements recursively */ - zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(output_handler), &pos); - while (zend_hash_get_current_data_ex(Z_ARRVAL_P(output_handler), (void **)&tmp, &pos) == SUCCESS) { - result = php_ob_init(initial_size, block_size, *tmp, chunk_size, erase TSRMLS_CC); - if (result == FAILURE) { - break; + + php_output_context_feed(context, handler->buffer.data, handler->buffer.size, handler->buffer.used, 0); + + if (SUCCESS == handler->func.internal(&handler->opaq, context)) { + if (context->out.used) { + status = PHP_OUTPUT_HANDLER_SUCCESS; + } else { + status = PHP_OUTPUT_HANDLER_NO_DATA; } - zend_hash_move_forward_ex(Z_ARRVAL_P(output_handler), &pos); + } else { + status = PHP_OUTPUT_HANDLER_FAILURE; } } - } else if (output_handler && output_handler->type == IS_OBJECT) { - /* do we have callable object */ - if (zend_is_callable(output_handler, 0, &handler_name TSRMLS_CC)) { - SEPARATE_ZVAL(&output_handler); - Z_ADDREF_P(output_handler); - result = php_ob_init_named(initial_size, block_size, handler_name, output_handler, chunk_size, erase TSRMLS_CC); - efree(handler_name); - } else { - efree(handler_name); - php_error_docref(NULL TSRMLS_CC, E_ERROR, "No method name given: use ob_start(array($object,'method')) to specify instance $object and the name of a method of class %s to use as output handler", Z_OBJCE_P(output_handler)->name); - result = FAILURE; - } - } else { - result = php_ob_init_named(initial_size, block_size, OB_DEFAULT_HANDLER_NAME, NULL, chunk_size, erase TSRMLS_CC); + handler->flags |= PHP_OUTPUT_HANDLER_STARTED; + OG(running) = NULL; } - return result; -} -/* }}} */ -/* {{{ php_ob_list_each - */ -static int php_ob_list_each(php_ob_buffer *ob_buffer, zval *ob_handler_array) -{ - add_next_index_string(ob_handler_array, ob_buffer->handler_name, 1); - return 0; + switch (status) { + case PHP_OUTPUT_HANDLER_FAILURE: + /* disable this handler */ + handler->flags |= PHP_OUTPUT_HANDLER_DISABLED; + /* discard any output */ + if (context->out.data && context->out.free) { + efree(context->out.data); + } + /* returns handlers buffer */ + context->out.data = handler->buffer.data; + context->out.used = handler->buffer.used; + context->out.free = 1; + handler->buffer.data = NULL; + handler->buffer.used = 0; + handler->buffer.size = 0; + break; + case PHP_OUTPUT_HANDLER_NO_DATA: + /* handler ate all */ + php_output_context_reset(context); + /* no break */ + case PHP_OUTPUT_HANDLER_SUCCESS: + /* no more buffered data */ + handler->buffer.used = 0; + break; + } + + context->op = original_op; + return status; } /* }}} */ -/* {{{ php_ob_used_each - Sets handler_name to NULL is found */ -static int php_ob_handler_used_each(php_ob_buffer *ob_buffer, char **handler_name) + +/* {{{ static void php_output_op(int op, const char *str, size_t len TSRMLS_DC) + * Output op dispatcher, passes input and output handlers output through the output handler stack until it gets written to the SAPI */ +static inline void php_output_op(int op, const char *str, size_t len TSRMLS_DC) { - if (!strcmp(ob_buffer->handler_name, *handler_name)) { - *handler_name = NULL; - return 1; + php_output_context context; + php_output_handler **active; + int obh_cnt; + + if (php_output_lock_error(op TSRMLS_CC)) { + return; } - return 0; -} -/* }}} */ -/* {{{ php_ob_used - returns 1 if given handler_name is used as output_handler */ -PHPAPI int php_ob_handler_used(char *handler_name TSRMLS_DC) -{ - char *tmp = handler_name; + php_output_context_init(&context, op TSRMLS_CC); - if (OG(ob_nesting_level)) { - if (!strcmp(OG(active_ob_buffer).handler_name, handler_name)) { - return 1; + /* + * broken up for better performance: + * - apply op to the one active handler; note that OG(active) might be popped off the stack on a flush + * - or apply op to the handler stack + */ + if (OG(active) && (obh_cnt = zend_stack_count(&OG(handlers)))) { + context.in.data = (char *) str; + context.in.used = len; + + if (obh_cnt > 1) { + zend_stack_apply_with_argument(&OG(handlers), ZEND_STACK_APPLY_TOPDOWN, php_output_stack_apply_op, &context); + } else if ((SUCCESS == zend_stack_top(&OG(handlers), (void *) &active)) && (!((*active)->flags & PHP_OUTPUT_HANDLER_DISABLED))) { + php_output_handler_op(*active, &context); + } else { + php_output_context_pass(&context); } - if (OG(ob_nesting_level)>1) { - zend_stack_apply_with_argument(&OG(ob_buffers), ZEND_STACK_APPLY_BOTTOMUP, (int (*)(void *element, void *)) php_ob_handler_used_each, &tmp); - } + } else { + context.out.data = (char *) str; + context.out.used = len; } - return tmp ? 0 : 1; -} -/* }}} */ -/* {{{ php_ob_append - */ -static inline void php_ob_append(const char *text, uint text_length TSRMLS_DC) -{ - char *target; - int original_ob_text_length; + if (context.out.data && context.out.used) { + php_output_header(TSRMLS_C); - original_ob_text_length=OG(active_ob_buffer).text_length; + if (!(OG(flags) & PHP_OUTPUT_DISABLED)) { +#if PHP_OUTPUT_DEBUG + fprintf(stderr, "::: sapi_write('%s', %zu)\n", context.out.data, context.out.used); +#endif + sapi_module.ub_write(context.out.data, context.out.used TSRMLS_CC); - php_ob_allocate(text_length TSRMLS_CC); - target = OG(active_ob_buffer).buffer+original_ob_text_length; - memcpy(target, text, text_length); - target[text_length]=0; + if (OG(flags) & PHP_OUTPUT_IMPLICITFLUSH) { + sapi_flush(TSRMLS_C); + } - /* If implicit_flush is On or chunked buffering, send contents to next buffer and return. */ - if (OG(active_ob_buffer).chunk_size - && OG(active_ob_buffer).text_length >= OG(active_ob_buffer).chunk_size) { - - php_end_ob_buffer(1, 1 TSRMLS_CC); - return; + OG(flags) |= PHP_OUTPUT_SENT; + } } + php_output_context_dtor(&context); } /* }}} */ -#if 0 -static inline void php_ob_prepend(const char *text, uint text_length) +/* {{{ static int php_output_stack_apply_op(void *h, void *c) + * Operation callback for the stack apply function */ +static int php_output_stack_apply_op(void *h, void *c) { - char *p, *start; - TSRMLS_FETCH(); + int was_disabled; + php_output_handler_status_t status; + php_output_handler *handler = *(php_output_handler **) h; + php_output_context *context = (php_output_context *) c; - php_ob_allocate(text_length TSRMLS_CC); + if ((was_disabled = (handler->flags & PHP_OUTPUT_HANDLER_DISABLED))) { + status = PHP_OUTPUT_HANDLER_FAILURE; + } else { + status = php_output_handler_op(handler, context); + } - /* php_ob_allocate() may change OG(ob_buffer), so we can't initialize p&start earlier */ - p = OG(ob_buffer)+OG(ob_text_length); - start = OG(ob_buffer); + /* + * handler ate all => break + * handler returned data or failed resp. is disabled => continue + */ + switch (status) { + case PHP_OUTPUT_HANDLER_NO_DATA: + return 1; - while (--p>=start) { - p[text_length] = *p; - } - memcpy(OG(ob_buffer), text, text_length); - OG(ob_buffer)[OG(active_ob_buffer).text_length]=0; -} -#endif + case PHP_OUTPUT_HANDLER_SUCCESS: + /* swap contexts buffers, unless this is the last handler in the stack */ + if (handler->level) { + php_output_context_swap(context); + } + return 0; -/* {{{ php_ob_get_buffer - * Return the current output buffer */ -PHPAPI int php_ob_get_buffer(zval *p TSRMLS_DC) -{ - if (OG(ob_nesting_level)==0) { - return FAILURE; + case PHP_OUTPUT_HANDLER_FAILURE: + default: + if (was_disabled) { + /* pass input along, if it's the last handler in the stack */ + if (!handler->level) { + php_output_context_pass(context); + } + } else { + /* swap buffers, unless this is the last handler */ + if (handler->level) { + php_output_context_swap(context); + } + } + return 0; } - ZVAL_STRINGL(p, OG(active_ob_buffer).buffer, OG(active_ob_buffer).text_length, 1); - return SUCCESS; } /* }}} */ -/* {{{ php_ob_get_length - * Return the size of the current output buffer */ -PHPAPI int php_ob_get_length(zval *p TSRMLS_DC) +/* {{{ static int php_output_stack_apply_clean(void *h, void *c) + * Clean callback for the stack apply function */ +static int php_output_stack_apply_clean(void *h, void *c) { - if (OG(ob_nesting_level) == 0) { - return FAILURE; - } - ZVAL_LONG(p, OG(active_ob_buffer).text_length); - return SUCCESS; + php_output_handler *handler = *(php_output_handler **) h; + php_output_context *context = (php_output_context *) c; + + handler->buffer.used = 0; + php_output_handler_op(handler, context); + php_output_context_reset(context); + return 0; } /* }}} */ -/* - * Wrapper functions - implementation - */ - -/* buffered output function */ -static int php_b_body_write(const char *str, uint str_length TSRMLS_DC) +/* {{{ static int php_output_stack_apply_list(void *h, void *z) + * List callback for the stack apply function */ +static int php_output_stack_apply_list(void *h, void *z) { - php_ob_append(str, str_length TSRMLS_CC); - return str_length; + php_output_handler *handler = *(php_output_handler **) h; + zval *array = (zval *) z; + + add_next_index_stringl(array, handler->name, handler->name_len, 1); + return 0; } +/* }}} */ -/* {{{ php_ub_body_write_no_header - */ -PHPAPI int php_ub_body_write_no_header(const char *str, uint str_length TSRMLS_DC) +/* {{{ static int php_output_stack_apply_status(void *h, void *z) + * Status callback for the stack apply function */ +static int php_output_stack_apply_status(void *h, void *z) { - int result; + php_output_handler *handler = *(php_output_handler **) h; + zval *array = (zval *) z; - if (OG(disable_output)) { - return 0; - } + add_next_index_zval(array, php_output_handler_status(handler, NULL)); - result = OG(php_header_write)(str, str_length TSRMLS_CC); + return 0; +} - if (OG(implicit_flush)) { - sapi_flush(TSRMLS_C); +/* {{{ static zval *php_output_handler_status(php_output_handler *handler, zval *entry) + * Returns an array with the status of the output handler */ +static inline zval *php_output_handler_status(php_output_handler *handler, zval *entry) +{ + if (!entry) { + MAKE_STD_ZVAL(entry); + array_init(entry); } - return result; + add_assoc_stringl(entry, "name", handler->name, handler->name_len, 1); + add_assoc_long(entry, "type", (long) (handler->flags & 0xf)); + add_assoc_long(entry, "flags", (long) handler->flags); + add_assoc_long(entry, "level", (long) handler->level); + add_assoc_long(entry, "chunk_size", (long) handler->size); + add_assoc_long(entry, "buffer_size", (long) handler->buffer.size); + add_assoc_long(entry, "buffer_used", (long) handler->buffer.used); + + return entry; } /* }}} */ -/* {{{ php_ub_body_write - */ -PHPAPI int php_ub_body_write(const char *str, uint str_length TSRMLS_DC) +/* {{{ static int php_output_stack_pop(int flags TSRMLS_DC) + * Pops an output handler off the stack */ +static inline int php_output_stack_pop(int flags TSRMLS_DC) { - int result = 0; + php_output_context context; + php_output_handler **current, *orphan = OG(active); - if (SG(request_info).headers_only) { - if(SG(headers_sent)) { - return 0; + if (!orphan) { + if (!(flags & PHP_OUTPUT_POP_SILENT)) { + php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to %s buffer. No buffer to %s", (flags&PHP_OUTPUT_POP_DISCARD)?"discard":"send", (flags&PHP_OUTPUT_POP_DISCARD)?"discard":"send"); } - php_header(TSRMLS_C); - zend_bailout(); - } - if (php_header(TSRMLS_C)) { - if (zend_is_compiling(TSRMLS_C)) { - OG(output_start_filename) = zend_get_compiled_filename(TSRMLS_C); - OG(output_start_lineno) = zend_get_compiled_lineno(TSRMLS_C); - } else if (zend_is_executing(TSRMLS_C)) { - OG(output_start_filename) = zend_get_executed_filename(TSRMLS_C); - OG(output_start_lineno) = zend_get_executed_lineno(TSRMLS_C); + return 0; + } else if (!(flags & PHP_OUTPUT_POP_FORCE) && !(orphan->flags & PHP_OUTPUT_HANDLER_REMOVABLE)) { + if (!(flags & PHP_OUTPUT_POP_SILENT)) { + php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to %s buffer of %s (%d)", (flags&PHP_OUTPUT_POP_DISCARD)?"discard":"send", orphan->name, orphan->level); } + return 0; + } else { + php_output_context_init(&context, PHP_OUTPUT_HANDLER_FINAL TSRMLS_CC); - OG(php_body_write) = php_ub_body_write_no_header; - result = php_ub_body_write_no_header(str, str_length TSRMLS_CC); - } + /* don't run the output handler if it's disabled */ + if (!(orphan->flags & PHP_OUTPUT_HANDLER_DISABLED)) { + /* didn't it start yet? */ + if (!(orphan->flags & PHP_OUTPUT_HANDLER_STARTED)) { + context.op |= PHP_OUTPUT_HANDLER_START; + } + /* signal that we're cleaning up */ + if (flags & PHP_OUTPUT_POP_DISCARD) { + context.op |= PHP_OUTPUT_HANDLER_CLEAN; + orphan->buffer.used = 0; + } + php_output_handler_op(orphan, &context); + } - return result; + /* pop it off the stack */ + zend_stack_del_top(&OG(handlers)); + if (SUCCESS == zend_stack_top(&OG(handlers), (void *) ¤t)) { + OG(active) = *current; + } else { + OG(active) = NULL; + } + + /* pass output along */ + if (context.out.data && context.out.used && !(flags & PHP_OUTPUT_POP_DISCARD)) { + php_output_write(context.out.data, context.out.used TSRMLS_CC); + } + + /* destroy the handler (after write!) */ + php_output_handler_free(&orphan TSRMLS_CC); + php_output_context_dtor(&context); + + return 1; + } } /* }}} */ -/* {{{ int php_ob_buffer_status(php_ob_buffer *ob_buffer, zval *result) - */ -static int php_ob_buffer_status(php_ob_buffer *ob_buffer, zval *result) +/* {{{ static SUCCESS|FAILURE php_output_handler_compat_func(void *ctx, php_output_context *) + * php_output_handler_context_func_t for php_output_handler_func_t output handlers */ +static int php_output_handler_compat_func(void **handler_context, php_output_context *output_context) { - zval *elem; + php_output_handler_func_t func = *(php_output_handler_func_t *) handler_context; + PHP_OUTPUT_TSRMLS(output_context); - MAKE_STD_ZVAL(elem); - array_init(elem); + if (func) { + char *out_str = NULL; + uint out_len = 0; - add_assoc_long(elem, "chunk_size", ob_buffer->chunk_size); - if (!ob_buffer->chunk_size) { - add_assoc_long(elem, "size", ob_buffer->size); - add_assoc_long(elem, "block_size", ob_buffer->block_size); + func(output_context->in.data, output_context->in.used, &out_str, &out_len, output_context->op TSRMLS_CC); + + if (out_str) { + output_context->out.data = out_str; + output_context->out.used = out_len; + output_context->out.free = 1; + } else { + php_output_context_pass(output_context); + } + + return SUCCESS; } - if (ob_buffer->internal_output_handler) { - add_assoc_long(elem, "type", PHP_OUTPUT_HANDLER_INTERNAL); - add_assoc_long(elem, "buffer_size", ob_buffer->internal_output_handler_buffer_size); - } else { - add_assoc_long(elem, "type", PHP_OUTPUT_HANDLER_USER); - } - add_assoc_long(elem, "status", ob_buffer->status); - add_assoc_string(elem, "name", ob_buffer->handler_name, 1); - add_assoc_bool(elem, "del", ob_buffer->erase); - add_next_index_zval(result, elem); + return FAILURE; +} +/* }}} */ +/* {{{ static SUCCESS|FAILURE php_output_handler_default_func(void *ctx, php_output_context *) + * Default output handler */ +static int php_output_handler_default_func(void **handler_context, php_output_context *output_context) +{ + php_output_context_pass(output_context); return SUCCESS; } /* }}} */ +/* {{{ static SUCCESS|FAILURE php_output_handler_devnull_func(void *ctx, php_output_context *) + * Null output handler */ +static int php_output_handler_devnull_func(void **handler_context, php_output_context *output_context) +{ + return SUCCESS; +} +/* }}} */ + /* * USERLAND (nearly 1:1 of old output.c) */ -/* {{{ proto bool ob_start([string|array user_function [, int chunk_size [, bool erase]]]) +/* {{{ proto bool ob_start([string|array user_function [, int chunk_size [, int flags]]]) Turn on Output Buffering (specifying an optional output handler). */ PHP_FUNCTION(ob_start) { zval *output_handler = NULL; long chunk_size = 0; - zend_bool erase = 1; + long flags = PHP_OUTPUT_HANDLER_STDFLAGS; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|z/lb", &output_handler, &chunk_size, &erase) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|z/ll", &output_handler, &chunk_size, &flags) == FAILURE) { return; } @@ -769,7 +1316,8 @@ PHP_FUNCTION(ob_start) chunk_size = 0; } - if (php_start_ob_buffer(output_handler, chunk_size, erase TSRMLS_CC) == FAILURE) { + if (php_output_start_user(output_handler, chunk_size, flags TSRMLS_CC) == FAILURE) { + php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to create buffer"); RETURN_FALSE; } RETURN_TRUE; @@ -784,17 +1332,15 @@ PHP_FUNCTION(ob_flush) return; } - if (!OG(ob_nesting_level)) { + if (!OG(active)) { php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to flush buffer. No buffer to flush"); RETURN_FALSE; } - if (!OG(active_ob_buffer).status && !OG(active_ob_buffer).erase) { - php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to flush buffer %s", OG(active_ob_buffer).handler_name); + if (SUCCESS != php_output_flush(TSRMLS_C)) { + php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to flush buffer of %s (%d)", OG(active)->name, OG(active)->level); RETURN_FALSE; } - - php_end_ob_buffer(1, 1 TSRMLS_CC); RETURN_TRUE; } /* }}} */ @@ -807,17 +1353,15 @@ PHP_FUNCTION(ob_clean) return; } - if (!OG(ob_nesting_level)) { + if (!OG(active)) { php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer. No buffer to delete"); RETURN_FALSE; } - if (!OG(active_ob_buffer).status && !OG(active_ob_buffer).erase) { - php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer %s", OG(active_ob_buffer).handler_name); + if (SUCCESS != php_output_clean(TSRMLS_C)) { + php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer of %s (%d)", OG(active)->name, OG(active)->level); RETURN_FALSE; } - - php_end_ob_buffer(0, 1 TSRMLS_CC); RETURN_TRUE; } /* }}} */ @@ -830,18 +1374,12 @@ PHP_FUNCTION(ob_end_flush) return; } - if (!OG(ob_nesting_level)) { + if (!OG(active)) { php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete and flush buffer. No buffer to delete or flush"); RETURN_FALSE; } - if (OG(ob_nesting_level) && !OG(active_ob_buffer).status && !OG(active_ob_buffer).erase) { - php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer %s", OG(active_ob_buffer).handler_name); - RETURN_FALSE; - } - - php_end_ob_buffer(1, 0 TSRMLS_CC); - RETURN_TRUE; + RETURN_BOOL(SUCCESS == php_output_end(TSRMLS_C)); } /* }}} */ @@ -853,18 +1391,12 @@ PHP_FUNCTION(ob_end_clean) return; } - if (!OG(ob_nesting_level)) { + if (!OG(active)) { php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer. No buffer to delete"); RETURN_FALSE; } - if (OG(ob_nesting_level) && !OG(active_ob_buffer).status && !OG(active_ob_buffer).erase) { - php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer %s", OG(active_ob_buffer).handler_name); - RETURN_FALSE; - } - - php_end_ob_buffer(0, 0 TSRMLS_CC); - RETURN_TRUE; + RETURN_BOOL(SUCCESS == php_output_discard(TSRMLS_C)); } /* }}} */ @@ -876,23 +1408,14 @@ PHP_FUNCTION(ob_get_flush) return; } - if (php_ob_get_buffer(return_value TSRMLS_CC) == FAILURE) { - RETURN_FALSE; - } - - if (!OG(ob_nesting_level)) { + if (php_output_get_contents(return_value TSRMLS_CC) == FAILURE) { php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete and flush buffer. No buffer to delete or flush"); - zval_dtor(return_value); RETURN_FALSE; } - if (OG(ob_nesting_level) && !OG(active_ob_buffer).status && !OG(active_ob_buffer).erase) { - php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer %s", OG(active_ob_buffer).handler_name); - zval_dtor(return_value); - RETURN_FALSE; + if (SUCCESS != php_output_end(TSRMLS_C)) { + php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer of %s (%d)", OG(active)->name, OG(active)->level); } - - php_end_ob_buffer(1, 0 TSRMLS_CC); } /* }}} */ @@ -904,22 +1427,18 @@ PHP_FUNCTION(ob_get_clean) return; } - if (php_ob_get_buffer(return_value TSRMLS_CC) == FAILURE) { + if(!OG(active)) { RETURN_FALSE; } - if (!OG(ob_nesting_level)) { + if (php_output_get_contents(return_value TSRMLS_CC) == FAILURE) { php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer. No buffer to delete"); - zval_dtor(return_value); RETURN_FALSE; } - if (OG(ob_nesting_level) && !OG(active_ob_buffer).status && !OG(active_ob_buffer).erase) { - php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer %s", OG(active_ob_buffer).handler_name); - zval_dtor(return_value); - RETURN_FALSE; - } - php_end_ob_buffer(0, 0 TSRMLS_CC); + if (SUCCESS != php_output_discard(TSRMLS_C)) { + php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer of %s (%d)", OG(active)->name, OG(active)->level); + } } /* }}} */ @@ -931,7 +1450,7 @@ PHP_FUNCTION(ob_get_contents) return; } - if (php_ob_get_buffer(return_value TSRMLS_CC) == FAILURE) { + if (php_output_get_contents(return_value TSRMLS_CC) == FAILURE) { RETURN_FALSE; } } @@ -945,7 +1464,7 @@ PHP_FUNCTION(ob_get_level) return; } - RETURN_LONG(OG(ob_nesting_level)); + RETURN_LONG(php_output_get_level(TSRMLS_C)); } /* }}} */ @@ -957,7 +1476,7 @@ PHP_FUNCTION(ob_get_length) return; } - if (php_ob_get_length(return_value TSRMLS_CC) == FAILURE) { + if (php_output_get_length(return_value TSRMLS_CC) == FAILURE) { RETURN_FALSE; } } @@ -973,12 +1492,11 @@ PHP_FUNCTION(ob_list_handlers) array_init(return_value); - if (OG(ob_nesting_level)) { - if (OG(ob_nesting_level) > 1) { - zend_stack_apply_with_argument(&OG(ob_buffers), ZEND_STACK_APPLY_BOTTOMUP, (int (*)(void *element, void *)) php_ob_list_each, return_value); - } - php_ob_list_each(&OG(active_ob_buffer), return_value); + if (!OG(active)) { + return; } + + zend_stack_apply_with_argument(&OG(handlers), ZEND_STACK_APPLY_BOTTOMUP, php_output_stack_apply_list, return_value); } /* }}} */ @@ -994,23 +1512,14 @@ PHP_FUNCTION(ob_get_status) array_init(return_value); + if (!OG(active)) { + return; + } + if (full_status) { - if (OG(ob_nesting_level) > 1) { - zend_stack_apply_with_argument(&OG(ob_buffers), ZEND_STACK_APPLY_BOTTOMUP, (int (*)(void *elem, void *))php_ob_buffer_status, return_value); - } - if (OG(ob_nesting_level) > 0 && php_ob_buffer_status(&OG(active_ob_buffer), return_value) == FAILURE) { - RETURN_FALSE; - } - } else if (OG(ob_nesting_level) > 0) { - add_assoc_long(return_value, "level", OG(ob_nesting_level)); - if (OG(active_ob_buffer).internal_output_handler) { - add_assoc_long(return_value, "type", PHP_OUTPUT_HANDLER_INTERNAL); - } else { - add_assoc_long(return_value, "type", PHP_OUTPUT_HANDLER_USER); - } - add_assoc_long(return_value, "status", OG(active_ob_buffer).status); - add_assoc_string(return_value, "name", OG(active_ob_buffer).handler_name, 1); - add_assoc_bool(return_value, "del", OG(active_ob_buffer).erase); + zend_stack_apply_with_argument(&OG(handlers), ZEND_STACK_APPLY_BOTTOMUP, php_output_stack_apply_status, return_value); + } else { + php_output_handler_status(OG(active), return_value); } } /* }}} */ @@ -1025,11 +1534,7 @@ PHP_FUNCTION(ob_implicit_flush) return; } - if (flag) { - php_start_implicit_flush(TSRMLS_C); - } else { - php_end_implicit_flush(TSRMLS_C); - } + php_output_set_implicit_flush(flag TSRMLS_CC); } /* }}} */