1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2014 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Zeev Suraski <zeev@zend.com>                                |
16   |          Thies C. Arntzen <thies@thieso.net>                         |
17   |          Marcus Boerger <helly@php.net>                              |
18   | New API: Michael Wallner <mike@php.net>                              |
19   +----------------------------------------------------------------------+
20*/
21
22/* $Id$ */
23
24#ifndef PHP_OUTPUT_DEBUG
25# define PHP_OUTPUT_DEBUG 0
26#endif
27#ifndef PHP_OUTPUT_NOINLINE
28# define PHP_OUTPUT_NOINLINE 0
29#endif
30
31#include "php.h"
32#include "ext/standard/head.h"
33#include "ext/standard/url_scanner_ex.h"
34#include "SAPI.h"
35#include "zend_stack.h"
36#include "php_output.h"
37
38ZEND_DECLARE_MODULE_GLOBALS(output);
39
40const char php_output_default_handler_name[sizeof("default output handler")] = "default output handler";
41const char php_output_devnull_handler_name[sizeof("null output handler")] = "null output handler";
42
43#if PHP_OUTPUT_NOINLINE || PHP_OUTPUT_DEBUG
44# undef inline
45# define inline
46#endif
47
48/* {{{ aliases, conflict and reverse conflict hash tables */
49static HashTable php_output_handler_aliases;
50static HashTable php_output_handler_conflicts;
51static HashTable php_output_handler_reverse_conflicts;
52/* }}} */
53
54/* {{{ forward declarations */
55static inline int php_output_lock_error(int op TSRMLS_DC);
56static inline void php_output_op(int op, const char *str, size_t len TSRMLS_DC);
57
58static inline php_output_handler *php_output_handler_init(const char *name, size_t name_len, size_t chunk_size, int flags TSRMLS_DC);
59static inline php_output_handler_status_t php_output_handler_op(php_output_handler *handler, php_output_context *context);
60static inline int php_output_handler_append(php_output_handler *handler, const php_output_buffer *buf TSRMLS_DC);
61static inline zval *php_output_handler_status(php_output_handler *handler, zval *entry);
62
63static inline php_output_context *php_output_context_init(php_output_context *context, int op TSRMLS_DC);
64static inline void php_output_context_reset(php_output_context *context);
65static inline void php_output_context_swap(php_output_context *context);
66static inline void php_output_context_dtor(php_output_context *context);
67
68static inline int php_output_stack_pop(int flags TSRMLS_DC);
69
70static int php_output_stack_apply_op(void *h, void *c);
71static int php_output_stack_apply_clean(void *h, void *c);
72static int php_output_stack_apply_list(void *h, void *z);
73static int php_output_stack_apply_status(void *h, void *z);
74
75static int php_output_handler_compat_func(void **handler_context, php_output_context *output_context);
76static int php_output_handler_default_func(void **handler_context, php_output_context *output_context);
77static int php_output_handler_devnull_func(void **handler_context, php_output_context *output_context);
78/* }}} */
79
80/* {{{ static void php_output_init_globals(zend_output_globals *G)
81 * Initialize the module globals on MINIT */
82static inline void php_output_init_globals(zend_output_globals *G)
83{
84    memset(G, 0, sizeof(*G));
85}
86/* }}} */
87
88/* {{{ stderr/stdout writer if not PHP_OUTPUT_ACTIVATED */
89static int php_output_stdout(const char *str, size_t str_len)
90{
91    fwrite(str, 1, str_len, stdout);
92    return str_len;
93}
94static int php_output_stderr(const char *str, size_t str_len)
95{
96    fwrite(str, 1, str_len, stderr);
97/* See http://support.microsoft.com/kb/190351 */
98#ifdef PHP_WIN32
99    fflush(stderr);
100#endif
101    return str_len;
102}
103static int (*php_output_direct)(const char *str, size_t str_len) = php_output_stderr;
104/* }}} */
105
106/* {{{ void php_output_header(TSRMLS_D) */
107static void php_output_header(TSRMLS_D)
108{
109    if (!SG(headers_sent)) {
110        if (!OG(output_start_filename)) {
111            if (zend_is_compiling(TSRMLS_C)) {
112                OG(output_start_filename) = zend_get_compiled_filename(TSRMLS_C);
113                OG(output_start_lineno) = zend_get_compiled_lineno(TSRMLS_C);
114            } else if (zend_is_executing(TSRMLS_C)) {
115                OG(output_start_filename) = zend_get_executed_filename(TSRMLS_C);
116                OG(output_start_lineno) = zend_get_executed_lineno(TSRMLS_C);
117            }
118#if PHP_OUTPUT_DEBUG
119            fprintf(stderr, "!!! output started at: %s (%d)\n", OG(output_start_filename), OG(output_start_lineno));
120#endif
121        }
122        if (!php_header(TSRMLS_C)) {
123            OG(flags) |= PHP_OUTPUT_DISABLED;
124        }
125    }
126}
127/* }}} */
128
129/* {{{ void php_output_startup(void)
130 * Set up module globals and initalize the conflict and reverse conflict hash tables */
131PHPAPI void php_output_startup(void)
132{
133    ZEND_INIT_MODULE_GLOBALS(output, php_output_init_globals, NULL);
134    zend_hash_init(&php_output_handler_aliases, 0, NULL, NULL, 1);
135    zend_hash_init(&php_output_handler_conflicts, 0, NULL, NULL, 1);
136    zend_hash_init(&php_output_handler_reverse_conflicts, 0, NULL, (void (*)(void *)) zend_hash_destroy, 1);
137    php_output_direct = php_output_stdout;
138}
139/* }}} */
140
141/* {{{ void php_output_shutdown(void)
142 * Destroy module globals and the conflict and reverse conflict hash tables */
143PHPAPI void php_output_shutdown(void)
144{
145    php_output_direct = php_output_stderr;
146    zend_hash_destroy(&php_output_handler_aliases);
147    zend_hash_destroy(&php_output_handler_conflicts);
148    zend_hash_destroy(&php_output_handler_reverse_conflicts);
149}
150/* }}} */
151
152/* {{{ SUCCESS|FAILURE php_output_activate(TSRMLS_D)
153 * Reset output globals and setup the output handler stack */
154PHPAPI int php_output_activate(TSRMLS_D)
155{
156#ifdef ZTS
157    memset((*((void ***) tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(output_globals_id)], 0, sizeof(zend_output_globals));
158#else
159    memset(&output_globals, 0, sizeof(zend_output_globals));
160#endif
161
162    zend_stack_init(&OG(handlers));
163    OG(flags) |= PHP_OUTPUT_ACTIVATED;
164
165    return SUCCESS;
166}
167/* }}} */
168
169/* {{{ void php_output_deactivate(TSRMLS_D)
170 * Destroy the output handler stack */
171PHPAPI void php_output_deactivate(TSRMLS_D)
172{
173    php_output_handler **handler = NULL;
174
175    php_output_header(TSRMLS_C);
176
177    OG(flags) ^= PHP_OUTPUT_ACTIVATED;
178    OG(active) = NULL;
179    OG(running) = NULL;
180
181    /* release all output handlers */
182    if (OG(handlers).elements) {
183        while (SUCCESS == zend_stack_top(&OG(handlers), (void *) &handler)) {
184            php_output_handler_free(handler TSRMLS_CC);
185            zend_stack_del_top(&OG(handlers));
186        }
187        zend_stack_destroy(&OG(handlers));
188    }
189
190}
191/* }}} */
192
193/* {{{ void php_output_register_constants() */
194PHPAPI void php_output_register_constants(TSRMLS_D)
195{
196    REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_START", PHP_OUTPUT_HANDLER_START, CONST_CS | CONST_PERSISTENT);
197    REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_WRITE", PHP_OUTPUT_HANDLER_WRITE, CONST_CS | CONST_PERSISTENT);
198    REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_FLUSH", PHP_OUTPUT_HANDLER_FLUSH, CONST_CS | CONST_PERSISTENT);
199    REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_CLEAN", PHP_OUTPUT_HANDLER_CLEAN, CONST_CS | CONST_PERSISTENT);
200    REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_FINAL", PHP_OUTPUT_HANDLER_FINAL, CONST_CS | CONST_PERSISTENT);
201    REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_CONT", PHP_OUTPUT_HANDLER_WRITE, CONST_CS | CONST_PERSISTENT);
202    REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_END", PHP_OUTPUT_HANDLER_FINAL, CONST_CS | CONST_PERSISTENT);
203
204    REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_CLEANABLE", PHP_OUTPUT_HANDLER_CLEANABLE, CONST_CS | CONST_PERSISTENT);
205    REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_FLUSHABLE", PHP_OUTPUT_HANDLER_FLUSHABLE, CONST_CS | CONST_PERSISTENT);
206    REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_REMOVABLE", PHP_OUTPUT_HANDLER_REMOVABLE, CONST_CS | CONST_PERSISTENT);
207    REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_STDFLAGS", PHP_OUTPUT_HANDLER_STDFLAGS, CONST_CS | CONST_PERSISTENT);
208    REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_STARTED", PHP_OUTPUT_HANDLER_STARTED, CONST_CS | CONST_PERSISTENT);
209    REGISTER_MAIN_LONG_CONSTANT("PHP_OUTPUT_HANDLER_DISABLED", PHP_OUTPUT_HANDLER_DISABLED, CONST_CS | CONST_PERSISTENT);
210}
211/* }}} */
212
213/* {{{ void php_output_set_status(int status TSRMLS_DC)
214 * Used by SAPIs to disable output */
215PHPAPI void php_output_set_status(int status TSRMLS_DC)
216{
217    OG(flags) = (OG(flags) & ~0xf) | (status & 0xf);
218}
219/* }}} */
220
221/* {{{ int php_output_get_status(TSRMLS_C)
222 * Get output control status */
223PHPAPI int php_output_get_status(TSRMLS_D)
224{
225    return (
226        OG(flags)
227        |   (OG(active) ? PHP_OUTPUT_ACTIVE : 0)
228        |   (OG(running)? PHP_OUTPUT_LOCKED : 0)
229    ) & 0xff;
230}
231/* }}} */
232
233/* {{{ int php_output_write_unbuffered(const char *str, size_t len TSRMLS_DC)
234 * Unbuffered write */
235PHPAPI int php_output_write_unbuffered(const char *str, size_t len TSRMLS_DC)
236{
237#if PHP_DEBUG
238    if (len > UINT_MAX) {
239        php_error(E_WARNING, "Attempt to output more than UINT_MAX bytes at once; "
240                "output will be truncated %lu => %lu",
241                (unsigned long) len, (unsigned long) (len % UINT_MAX));
242    }
243#endif
244    if (OG(flags) & PHP_OUTPUT_DISABLED) {
245        return 0;
246    }
247    if (OG(flags) & PHP_OUTPUT_ACTIVATED) {
248        return sapi_module.ub_write(str, len TSRMLS_CC);
249    }
250    return php_output_direct(str, len);
251}
252/* }}} */
253
254/* {{{ int php_output_write(const char *str, size_t len TSRMLS_DC)
255 * Buffered write */
256PHPAPI int php_output_write(const char *str, size_t len TSRMLS_DC)
257{
258#if PHP_DEBUG
259    if (len > UINT_MAX) {
260        php_error(E_WARNING, "Attempt to output more than UINT_MAX bytes at once; "
261                "output will be truncated %lu => %lu",
262                (unsigned long) len, (unsigned long) (len % UINT_MAX));
263    }
264#endif
265    if (OG(flags) & PHP_OUTPUT_DISABLED) {
266        return 0;
267    }
268    if (OG(flags) & PHP_OUTPUT_ACTIVATED) {
269        php_output_op(PHP_OUTPUT_HANDLER_WRITE, str, len TSRMLS_CC);
270        return (int) len;
271    }
272    return php_output_direct(str, len);
273}
274/* }}} */
275
276/* {{{ SUCCESS|FAILURE php_output_flush(TSRMLS_D)
277 * Flush the most recent output handlers buffer */
278PHPAPI int php_output_flush(TSRMLS_D)
279{
280    php_output_context context;
281
282    if (OG(active) && (OG(active)->flags & PHP_OUTPUT_HANDLER_FLUSHABLE)) {
283        php_output_context_init(&context, PHP_OUTPUT_HANDLER_FLUSH TSRMLS_CC);
284        php_output_handler_op(OG(active), &context);
285        if (context.out.data && context.out.used) {
286            zend_stack_del_top(&OG(handlers));
287            php_output_write(context.out.data, context.out.used TSRMLS_CC);
288            zend_stack_push(&OG(handlers), &OG(active), sizeof(php_output_handler *));
289        }
290        php_output_context_dtor(&context);
291        return SUCCESS;
292    }
293    return FAILURE;
294}
295/* }}} */
296
297/* {{{ void php_output_flush_all(TSRMLS_C)
298 * Flush all output buffers subsequently */
299PHPAPI void php_output_flush_all(TSRMLS_D)
300{
301    if (OG(active)) {
302        php_output_op(PHP_OUTPUT_HANDLER_FLUSH, NULL, 0 TSRMLS_CC);
303    }
304}
305/* }}} */
306
307/* {{{ SUCCESS|FAILURE php_output_clean(TSRMLS_D)
308 * Cleans the most recent output handlers buffer if the handler is cleanable */
309PHPAPI int php_output_clean(TSRMLS_D)
310{
311    php_output_context context;
312
313    if (OG(active) && (OG(active)->flags & PHP_OUTPUT_HANDLER_CLEANABLE)) {
314        php_output_context_init(&context, PHP_OUTPUT_HANDLER_CLEAN TSRMLS_CC);
315        php_output_handler_op(OG(active), &context);
316        php_output_context_dtor(&context);
317        return SUCCESS;
318    }
319    return FAILURE;
320}
321/* }}} */
322
323/* {{{ void php_output_clean_all(TSRMLS_D)
324 * Cleans all output handler buffers, without regard whether the handler is cleanable */
325PHPAPI void php_output_clean_all(TSRMLS_D)
326{
327    php_output_context context;
328
329    if (OG(active)) {
330        php_output_context_init(&context, PHP_OUTPUT_HANDLER_CLEAN TSRMLS_CC);
331        zend_stack_apply_with_argument(&OG(handlers), ZEND_STACK_APPLY_TOPDOWN, php_output_stack_apply_clean, &context);
332    }
333}
334
335/* {{{ SUCCESS|FAILURE php_output_end(TSRMLS_D)
336 * Finalizes the most recent output handler at pops it off the stack if the handler is removable */
337PHPAPI int php_output_end(TSRMLS_D)
338{
339    if (php_output_stack_pop(PHP_OUTPUT_POP_TRY TSRMLS_CC)) {
340        return SUCCESS;
341    }
342    return FAILURE;
343}
344/* }}} */
345
346/* {{{ void php_output_end_all(TSRMLS_D)
347 * Finalizes all output handlers and ends output buffering without regard whether a handler is removable */
348PHPAPI void php_output_end_all(TSRMLS_D)
349{
350    while (OG(active) && php_output_stack_pop(PHP_OUTPUT_POP_FORCE TSRMLS_CC));
351}
352/* }}} */
353
354/* {{{ SUCCESS|FAILURE php_output_discard(TSRMLS_D)
355 * Discards the most recent output handlers buffer and pops it off the stack if the handler is removable */
356PHPAPI int php_output_discard(TSRMLS_D)
357{
358    if (php_output_stack_pop(PHP_OUTPUT_POP_DISCARD|PHP_OUTPUT_POP_TRY TSRMLS_CC)) {
359        return SUCCESS;
360    }
361    return FAILURE;
362}
363/* }}} */
364
365/* {{{ void php_output_discard_all(TSRMLS_D)
366 * Discard all output handlers and buffers without regard whether a handler is removable */
367PHPAPI void php_output_discard_all(TSRMLS_D)
368{
369    while (OG(active)) {
370        php_output_stack_pop(PHP_OUTPUT_POP_DISCARD|PHP_OUTPUT_POP_FORCE TSRMLS_CC);
371    }
372}
373/* }}} */
374
375/* {{{ int php_output_get_level(TSRMLS_D)
376 * Get output buffering level, ie. how many output handlers the stack contains */
377PHPAPI int php_output_get_level(TSRMLS_D)
378{
379    return OG(active) ? zend_stack_count(&OG(handlers)) : 0;
380}
381/* }}} */
382
383/* {{{ SUCCESS|FAILURE php_output_get_contents(zval *z TSRMLS_DC)
384 * Get the contents of the active output handlers buffer */
385PHPAPI int php_output_get_contents(zval *p TSRMLS_DC)
386{
387    if (OG(active)) {
388        ZVAL_STRINGL(p, OG(active)->buffer.data, OG(active)->buffer.used, 1);
389        return SUCCESS;
390    } else {
391        ZVAL_NULL(p);
392        return FAILURE;
393    }
394}
395
396/* {{{ SUCCESS|FAILURE php_output_get_length(zval *z TSRMLS_DC)
397 * Get the length of the active output handlers buffer */
398PHPAPI int php_output_get_length(zval *p TSRMLS_DC)
399{
400    if (OG(active)) {
401        ZVAL_LONG(p, OG(active)->buffer.used);
402        return SUCCESS;
403    } else {
404        ZVAL_NULL(p);
405        return FAILURE;
406    }
407}
408/* }}} */
409
410/* {{{ php_output_handler* php_output_get_active_handler(TSRMLS_D)
411 * Get active output handler */
412PHPAPI php_output_handler* php_output_get_active_handler(TSRMLS_D)
413{
414    return OG(active);
415}
416/* }}} */
417
418/* {{{ SUCCESS|FAILURE php_output_handler_start_default(TSRMLS_D)
419 * Start a "default output handler" */
420PHPAPI int php_output_start_default(TSRMLS_D)
421{
422    php_output_handler *handler;
423
424    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);
425    if (SUCCESS == php_output_handler_start(handler TSRMLS_CC)) {
426        return SUCCESS;
427    }
428    php_output_handler_free(&handler TSRMLS_CC);
429    return FAILURE;
430}
431/* }}} */
432
433/* {{{ SUCCESS|FAILURE php_output_handler_start_devnull(TSRMLS_D)
434 * Start a "null output handler" */
435PHPAPI int php_output_start_devnull(TSRMLS_D)
436{
437    php_output_handler *handler;
438
439    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);
440    if (SUCCESS == php_output_handler_start(handler TSRMLS_CC)) {
441        return SUCCESS;
442    }
443    php_output_handler_free(&handler TSRMLS_CC);
444    return FAILURE;
445}
446/* }}} */
447
448/* {{{ SUCCESS|FAILURE php_output_start_user(zval *handler, size_t chunk_size, int flags TSRMLS_DC)
449 * Start a user level output handler */
450PHPAPI int php_output_start_user(zval *output_handler, size_t chunk_size, int flags TSRMLS_DC)
451{
452    php_output_handler *handler;
453
454    if (output_handler) {
455        handler = php_output_handler_create_user(output_handler, chunk_size, flags TSRMLS_CC);
456    } else {
457        handler = php_output_handler_create_internal(ZEND_STRL(php_output_default_handler_name), php_output_handler_default_func, chunk_size, flags TSRMLS_CC);
458    }
459    if (SUCCESS == php_output_handler_start(handler TSRMLS_CC)) {
460        return SUCCESS;
461    }
462    php_output_handler_free(&handler TSRMLS_CC);
463    return FAILURE;
464}
465/* }}} */
466
467/* {{{ SUCCESS|FAILURE php_output_start_internal(zval *name, php_output_handler_func_t handler, size_t chunk_size, int flags TSRMLS_DC)
468 * Start an internal output handler that does not have to maintain a non-global state */
469PHPAPI 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)
470{
471    php_output_handler *handler;
472
473    handler = php_output_handler_create_internal(name, name_len, php_output_handler_compat_func, chunk_size, flags TSRMLS_CC);
474    php_output_handler_set_context(handler, output_handler, NULL TSRMLS_CC);
475    if (SUCCESS == php_output_handler_start(handler TSRMLS_CC)) {
476        return SUCCESS;
477    }
478    php_output_handler_free(&handler TSRMLS_CC);
479    return FAILURE;
480}
481/* }}} */
482
483/* {{{ php_output_handler *php_output_handler_create_user(zval *handler, size_t chunk_size, int flags TSRMLS_DC)
484 * Create a user level output handler */
485PHPAPI php_output_handler *php_output_handler_create_user(zval *output_handler, size_t chunk_size, int flags TSRMLS_DC)
486{
487    char *handler_name = NULL, *error = NULL;
488    php_output_handler *handler = NULL;
489    php_output_handler_alias_ctor_t *alias = NULL;
490    php_output_handler_user_func_t *user = NULL;
491
492    switch (Z_TYPE_P(output_handler)) {
493        case IS_NULL:
494            handler = php_output_handler_create_internal(ZEND_STRL(php_output_default_handler_name), php_output_handler_default_func, chunk_size, flags TSRMLS_CC);
495            break;
496        case IS_STRING:
497            if (Z_STRLEN_P(output_handler) && (alias = php_output_handler_alias(Z_STRVAL_P(output_handler), Z_STRLEN_P(output_handler) TSRMLS_CC))) {
498                handler = (*alias)(Z_STRVAL_P(output_handler), Z_STRLEN_P(output_handler), chunk_size, flags TSRMLS_CC);
499                break;
500            }
501        default:
502            user = ecalloc(1, sizeof(php_output_handler_user_func_t));
503            if (SUCCESS == zend_fcall_info_init(output_handler, 0, &user->fci, &user->fcc, &handler_name, &error TSRMLS_CC)) {
504                handler = php_output_handler_init(handler_name, strlen(handler_name), chunk_size, (flags & ~0xf) | PHP_OUTPUT_HANDLER_USER TSRMLS_CC);
505                Z_ADDREF_P(output_handler);
506                user->zoh = output_handler;
507                handler->func.user = user;
508            } else {
509                efree(user);
510            }
511            if (error) {
512                php_error_docref("ref.outcontrol" TSRMLS_CC, E_WARNING, "%s", error);
513                efree(error);
514            }
515            if (handler_name) {
516                efree(handler_name);
517            }
518    }
519
520    return handler;
521}
522/* }}} */
523
524/* {{{ 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)
525 * Create an internal output handler that can maintain a non-global state */
526PHPAPI 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)
527{
528    php_output_handler *handler;
529
530    handler = php_output_handler_init(name, name_len, chunk_size, (flags & ~0xf) | PHP_OUTPUT_HANDLER_INTERNAL TSRMLS_CC);
531    handler->func.internal = output_handler;
532
533    return handler;
534}
535/* }}} */
536
537/* {{{ void php_output_handler_set_context(php_output_handler *handler, void *opaq, void (*dtor)(void* TSRMLS_DC) TSRMLS_DC)
538 * Set the context/state of an output handler. Calls the dtor of the previous context if there is one */
539PHPAPI void php_output_handler_set_context(php_output_handler *handler, void *opaq, void (*dtor)(void* TSRMLS_DC) TSRMLS_DC)
540{
541    if (handler->dtor && handler->opaq) {
542        handler->dtor(handler->opaq TSRMLS_CC);
543    }
544    handler->dtor = dtor;
545    handler->opaq = opaq;
546}
547/* }}} */
548
549/* {{{ SUCCESS|FAILURE php_output_handler_start(php_output_handler *handler TSRMLS_DC)
550 * Starts the set up output handler and pushes it on top of the stack. Checks for any conflicts regarding the output handler to start */
551PHPAPI int php_output_handler_start(php_output_handler *handler TSRMLS_DC)
552{
553    HashPosition pos;
554    HashTable *rconflicts;
555    php_output_handler_conflict_check_t *conflict;
556
557    if (php_output_lock_error(PHP_OUTPUT_HANDLER_START TSRMLS_CC) || !handler) {
558        return FAILURE;
559    }
560    if (SUCCESS == zend_hash_find(&php_output_handler_conflicts, handler->name, handler->name_len+1, (void *) &conflict)) {
561        if (SUCCESS != (*conflict)(handler->name, handler->name_len TSRMLS_CC)) {
562            return FAILURE;
563        }
564    }
565    if (SUCCESS == zend_hash_find(&php_output_handler_reverse_conflicts, handler->name, handler->name_len+1, (void *) &rconflicts)) {
566        for (zend_hash_internal_pointer_reset_ex(rconflicts, &pos);
567            zend_hash_get_current_data_ex(rconflicts, (void *) &conflict, &pos) == SUCCESS;
568            zend_hash_move_forward_ex(rconflicts, &pos)
569        ) {
570            if (SUCCESS != (*conflict)(handler->name, handler->name_len TSRMLS_CC)) {
571                return FAILURE;
572            }
573        }
574    }
575    /* zend_stack_push never returns SUCCESS but FAILURE or stack level */
576    if (FAILURE == (handler->level = zend_stack_push(&OG(handlers), &handler, sizeof(php_output_handler *)))) {
577        return FAILURE;
578    }
579    OG(active) = handler;
580    return SUCCESS;
581}
582/* }}} */
583
584/* {{{ int php_output_handler_started(zval *name TSRMLS_DC)
585 * Check whether a certain output handler is in use */
586PHPAPI int php_output_handler_started(const char *name, size_t name_len TSRMLS_DC)
587{
588    php_output_handler ***handlers;
589    int i, count = php_output_get_level(TSRMLS_C);
590
591    if (count) {
592        handlers = (php_output_handler ***) zend_stack_base(&OG(handlers));
593
594        for (i = 0; i < count; ++i) {
595            if (name_len == (*(handlers[i]))->name_len && !memcmp((*(handlers[i]))->name, name, name_len)) {
596                return 1;
597            }
598        }
599    }
600
601    return 0;
602}
603/* }}} */
604
605/* {{{ int php_output_handler_conflict(zval *handler_new, zval *handler_old TSRMLS_DC)
606 * Check whether a certain handler is in use and issue a warning that the new handler would conflict with the already used one */
607PHPAPI 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)
608{
609    if (php_output_handler_started(handler_set, handler_set_len TSRMLS_CC)) {
610        if (handler_new_len != handler_set_len || memcmp(handler_new, handler_set, handler_set_len)) {
611            php_error_docref("ref.outcontrol" TSRMLS_CC, E_WARNING, "output handler '%s' conflicts with '%s'", handler_new, handler_set);
612        } else {
613            php_error_docref("ref.outcontrol" TSRMLS_CC, E_WARNING, "output handler '%s' cannot be used twice", handler_new);
614        }
615        return 1;
616    }
617    return 0;
618}
619/* }}} */
620
621/* {{{ SUCCESS|FAILURE php_output_handler_conflict_register(zval *name, php_output_handler_conflict_check_t check_func TSRMLS_DC)
622 * Register a conflict checking function on MINIT */
623PHPAPI int php_output_handler_conflict_register(const char *name, size_t name_len, php_output_handler_conflict_check_t check_func TSRMLS_DC)
624{
625    if (!EG(current_module)) {
626        zend_error(E_ERROR, "Cannot register an output handler conflict outside of MINIT");
627        return FAILURE;
628    }
629    return zend_hash_update(&php_output_handler_conflicts, name, name_len+1, &check_func, sizeof(php_output_handler_conflict_check_t *), NULL);
630}
631/* }}} */
632
633/* {{{ SUCCESS|FAILURE php_output_handler_reverse_conflict_register(zval *name, php_output_handler_conflict_check_t check_func TSRMLS_DC)
634 * Register a reverse conflict checking function on MINIT */
635PHPAPI int php_output_handler_reverse_conflict_register(const char *name, size_t name_len, php_output_handler_conflict_check_t check_func TSRMLS_DC)
636{
637    HashTable rev, *rev_ptr = NULL;
638
639    if (!EG(current_module)) {
640        zend_error(E_ERROR, "Cannot register a reverse output handler conflict outside of MINIT");
641        return FAILURE;
642    }
643
644    if (SUCCESS == zend_hash_find(&php_output_handler_reverse_conflicts, name, name_len+1, (void *) &rev_ptr)) {
645        return zend_hash_next_index_insert(rev_ptr, &check_func, sizeof(php_output_handler_conflict_check_t *), NULL);
646    } else {
647        zend_hash_init(&rev, 1, NULL, NULL, 1);
648        if (SUCCESS != zend_hash_next_index_insert(&rev, &check_func, sizeof(php_output_handler_conflict_check_t *), NULL)) {
649            zend_hash_destroy(&rev);
650            return FAILURE;
651        }
652        if (SUCCESS != zend_hash_update(&php_output_handler_reverse_conflicts, name, name_len+1, &rev, sizeof(HashTable), NULL)) {
653            zend_hash_destroy(&rev);
654            return FAILURE;
655        }
656        return SUCCESS;
657    }
658}
659/* }}} */
660
661/* {{{ php_output_handler_alias_ctor_t php_output_handler_alias(zval *name TSRMLS_DC)
662 * Get an internal output handler for a user handler if it exists */
663PHPAPI php_output_handler_alias_ctor_t *php_output_handler_alias(const char *name, size_t name_len TSRMLS_DC)
664{
665    php_output_handler_alias_ctor_t *func = NULL;
666
667    zend_hash_find(&php_output_handler_aliases, name, name_len+1, (void *) &func);
668    return func;
669}
670/* }}} */
671
672/* {{{ SUCCESS|FAILURE php_output_handler_alias_register(zval *name, php_output_handler_alias_ctor_t func TSRMLS_DC)
673 * Registers an internal output handler as alias for a user handler */
674PHPAPI int php_output_handler_alias_register(const char *name, size_t name_len, php_output_handler_alias_ctor_t func TSRMLS_DC)
675{
676    if (!EG(current_module)) {
677        zend_error(E_ERROR, "Cannot register an output handler alias outside of MINIT");
678        return FAILURE;
679    }
680    return zend_hash_update(&php_output_handler_aliases, name, name_len+1, &func, sizeof(php_output_handler_alias_ctor_t *), NULL);
681}
682/* }}} */
683
684/* {{{ SUCCESS|FAILURE php_output_handler_hook(php_output_handler_hook_t type, void *arg TSMRLS_DC)
685 * Output handler hook for output handler functions to check/modify the current handlers abilities */
686PHPAPI int php_output_handler_hook(php_output_handler_hook_t type, void *arg TSRMLS_DC)
687{
688    if (OG(running)) {
689        switch (type) {
690            case PHP_OUTPUT_HANDLER_HOOK_GET_OPAQ:
691                *(void ***) arg = &OG(running)->opaq;
692                return SUCCESS;
693            case PHP_OUTPUT_HANDLER_HOOK_GET_FLAGS:
694                *(int *) arg = OG(running)->flags;
695                return SUCCESS;
696            case PHP_OUTPUT_HANDLER_HOOK_GET_LEVEL:
697                *(int *) arg = OG(running)->level;
698                return SUCCESS;
699            case PHP_OUTPUT_HANDLER_HOOK_IMMUTABLE:
700                OG(running)->flags &= ~(PHP_OUTPUT_HANDLER_REMOVABLE|PHP_OUTPUT_HANDLER_CLEANABLE);
701                return SUCCESS;
702            case PHP_OUTPUT_HANDLER_HOOK_DISABLE:
703                OG(running)->flags |= PHP_OUTPUT_HANDLER_DISABLED;
704                return SUCCESS;
705            default:
706                break;
707        }
708    }
709    return FAILURE;
710}
711/* }}} */
712
713/* {{{ void php_output_handler_dtor(php_output_handler *handler TSRMLS_DC)
714 * Destroy an output handler */
715PHPAPI void php_output_handler_dtor(php_output_handler *handler TSRMLS_DC)
716{
717    STR_FREE(handler->name);
718    STR_FREE(handler->buffer.data);
719    if (handler->flags & PHP_OUTPUT_HANDLER_USER) {
720        zval_ptr_dtor(&handler->func.user->zoh);
721        efree(handler->func.user);
722    }
723    if (handler->dtor && handler->opaq) {
724        handler->dtor(handler->opaq TSRMLS_CC);
725    }
726    memset(handler, 0, sizeof(*handler));
727}
728/* }}} */
729
730/* {{{ void php_output_handler_free(php_output_handler **handler TSMRLS_DC)
731 * Destroy and free an output handler */
732PHPAPI void php_output_handler_free(php_output_handler **h TSRMLS_DC)
733{
734    if (*h) {
735        php_output_handler_dtor(*h TSRMLS_CC);
736        efree(*h);
737        *h = NULL;
738    }
739}
740/* }}} */
741
742/* void php_output_set_implicit_flush(int enabled TSRMLS_DC)
743 * Enable or disable implicit flush */
744PHPAPI void php_output_set_implicit_flush(int flush TSRMLS_DC)
745{
746    if (flush) {
747        OG(flags) |= PHP_OUTPUT_IMPLICITFLUSH;
748    } else {
749        OG(flags) &= ~PHP_OUTPUT_IMPLICITFLUSH;
750    }
751}
752/* }}} */
753
754/* {{{ char *php_output_get_start_filename(TSRMLS_D)
755 * Get the file name where output has started */
756PHPAPI const char *php_output_get_start_filename(TSRMLS_D)
757{
758    return OG(output_start_filename);
759}
760/* }}} */
761
762/* {{{ int php_output_get_start_lineno(TSRMLS_D)
763 * Get the line number where output has started */
764PHPAPI int php_output_get_start_lineno(TSRMLS_D)
765{
766    return OG(output_start_lineno);
767}
768/* }}} */
769
770/* {{{ static int php_output_lock_error(int op TSRMLS_DC)
771 * Checks whether an unallowed operation is attempted from within the output handler and issues a fatal error */
772static inline int php_output_lock_error(int op TSRMLS_DC)
773{
774    /* if there's no ob active, ob has been stopped */
775    if (op && OG(active) && OG(running)) {
776        /* fatal error */
777        php_output_deactivate(TSRMLS_C);
778        php_error_docref("ref.outcontrol" TSRMLS_CC, E_ERROR, "Cannot use output buffering in output buffering display handlers");
779        return 1;
780    }
781    return 0;
782}
783/* }}} */
784
785/* {{{ static php_output_context *php_output_context_init(php_output_context *context, int op TSRMLS_DC)
786 * Initialize a new output context */
787static inline php_output_context *php_output_context_init(php_output_context *context, int op TSRMLS_DC)
788{
789    if (!context) {
790        context = emalloc(sizeof(php_output_context));
791    }
792
793    memset(context, 0, sizeof(php_output_context));
794    TSRMLS_SET_CTX(context->tsrm_ls);
795    context->op = op;
796
797    return context;
798}
799/* }}} */
800
801/* {{{ static void php_output_context_reset(php_output_context *context)
802 * Reset an output context */
803static inline void php_output_context_reset(php_output_context *context)
804{
805    int op = context->op;
806    php_output_context_dtor(context);
807    memset(context, 0, sizeof(php_output_context));
808    context->op = op;
809}
810/* }}} */
811
812/* {{{ static void php_output_context_feed(php_output_context *context, char *, size_t, size_t)
813 * Feed output contexts input buffer */
814static inline void php_output_context_feed(php_output_context *context, char *data, size_t size, size_t used, zend_bool free)
815{
816    if (context->in.free && context->in.data) {
817        efree(context->in.data);
818    }
819    context->in.data = data;
820    context->in.used = used;
821    context->in.free = free;
822    context->in.size = size;
823}
824/* }}} */
825
826/* {{{ static void php_output_context_swap(php_output_context *context)
827 * Swap output contexts buffers */
828static inline void php_output_context_swap(php_output_context *context)
829{
830    if (context->in.free && context->in.data) {
831        efree(context->in.data);
832    }
833    context->in.data = context->out.data;
834    context->in.used = context->out.used;
835    context->in.free = context->out.free;
836    context->in.size = context->out.size;
837    context->out.data = NULL;
838    context->out.used = 0;
839    context->out.free = 0;
840    context->out.size = 0;
841}
842/* }}} */
843
844/* {{{ static void php_output_context_pass(php_output_context *context)
845 * Pass input to output buffer */
846static inline void php_output_context_pass(php_output_context *context)
847{
848    context->out.data = context->in.data;
849    context->out.used = context->in.used;
850    context->out.size = context->in.size;
851    context->out.free = context->in.free;
852    context->in.data = NULL;
853    context->in.used = 0;
854    context->in.free = 0;
855    context->in.size = 0;
856}
857/* }}} */
858
859/* {{{ static void php_output_context_dtor(php_output_context *context)
860 * Destroy the contents of an output context */
861static inline void php_output_context_dtor(php_output_context *context)
862{
863    if (context->in.free && context->in.data) {
864        efree(context->in.data);
865        context->in.data = NULL;
866    }
867    if (context->out.free && context->out.data) {
868        efree(context->out.data);
869        context->out.data = NULL;
870    }
871}
872/* }}} */
873
874/* {{{ static php_output_handler *php_output_handler_init(zval *name, size_t chunk_size, int flags TSRMLS_DC)
875 * Allocates and initializes a php_output_handler structure */
876static inline php_output_handler *php_output_handler_init(const char *name, size_t name_len, size_t chunk_size, int flags TSRMLS_DC)
877{
878    php_output_handler *handler;
879
880    handler = ecalloc(1, sizeof(php_output_handler));
881    handler->name = estrndup(name, name_len);
882    handler->name_len = name_len;
883    handler->size = chunk_size;
884    handler->flags = flags;
885    handler->buffer.size = PHP_OUTPUT_HANDLER_INITBUF_SIZE(chunk_size);
886    handler->buffer.data = emalloc(handler->buffer.size);
887
888    return handler;
889}
890/* }}} */
891
892/* {{{ static int php_output_handler_appen(php_output_handler *handler, const php_output_buffer *buf TSRMLS_DC)
893 * Appends input to the output handlers buffer and indicates whether the buffer does not have to be processed by the output handler */
894static inline int php_output_handler_append(php_output_handler *handler, const php_output_buffer *buf TSRMLS_DC)
895{
896    if (buf->used) {
897        OG(flags) |= PHP_OUTPUT_WRITTEN;
898        /* store it away */
899        if ((handler->buffer.size - handler->buffer.used) <= buf->used) {
900            size_t grow_int = PHP_OUTPUT_HANDLER_INITBUF_SIZE(handler->size);
901            size_t grow_buf = PHP_OUTPUT_HANDLER_INITBUF_SIZE(buf->used - (handler->buffer.size - handler->buffer.used));
902            size_t grow_max = MAX(grow_int, grow_buf);
903
904            handler->buffer.data = erealloc(handler->buffer.data, handler->buffer.size + grow_max);
905            handler->buffer.size += grow_max;
906        }
907        memcpy(handler->buffer.data + handler->buffer.used, buf->data, buf->used);
908        handler->buffer.used += buf->used;
909
910        /* chunked buffering */
911        if (handler->size && (handler->buffer.used >= handler->size)) {
912            /* store away errors and/or any intermediate output */
913            return OG(running) ? 1 : 0;
914        }
915    }
916    return 1;
917}
918/* }}} */
919
920/* {{{ static php_output_handler_status_t php_output_handler_op(php_output_handler *handler, php_output_context *context)
921 * Output handler operation dispatcher, applying context op to the php_output_handler handler */
922static inline php_output_handler_status_t php_output_handler_op(php_output_handler *handler, php_output_context *context)
923{
924    php_output_handler_status_t status;
925    int original_op = context->op;
926    PHP_OUTPUT_TSRMLS(context);
927
928#if PHP_OUTPUT_DEBUG
929    fprintf(stderr, ">>> op(%d, "
930                    "handler=%p, "
931                    "name=%s, "
932                    "flags=%d, "
933                    "buffer.data=%s, "
934                    "buffer.used=%zu, "
935                    "buffer.size=%zu, "
936                    "in.data=%s, "
937                    "in.used=%zu)\n",
938            context->op,
939            handler,
940            handler->name,
941            handler->flags,
942            handler->buffer.used?handler->buffer.data:"",
943            handler->buffer.used,
944            handler->buffer.size,
945            context->in.used?context->in.data:"",
946            context->in.used
947    );
948#endif
949
950    if (php_output_lock_error(context->op TSRMLS_CC)) {
951        /* fatal error */
952        return PHP_OUTPUT_HANDLER_FAILURE;
953    }
954
955    /* storable? */
956    if (php_output_handler_append(handler, &context->in TSRMLS_CC) && !context->op) {
957        context->op = original_op;
958        return PHP_OUTPUT_HANDLER_NO_DATA;
959    } else {
960        /* need to start? */
961        if (!(handler->flags & PHP_OUTPUT_HANDLER_STARTED)) {
962            context->op |= PHP_OUTPUT_HANDLER_START;
963        }
964
965        OG(running) = handler;
966        if (handler->flags & PHP_OUTPUT_HANDLER_USER) {
967            zval *retval = NULL, *ob_data, *ob_mode;
968
969            MAKE_STD_ZVAL(ob_data);
970            ZVAL_STRINGL(ob_data, handler->buffer.data, handler->buffer.used, 1);
971            MAKE_STD_ZVAL(ob_mode);
972            ZVAL_LONG(ob_mode, (long) context->op);
973            zend_fcall_info_argn(&handler->func.user->fci TSRMLS_CC, 2, &ob_data, &ob_mode);
974
975#define PHP_OUTPUT_USER_SUCCESS(retval) (retval && !(Z_TYPE_P(retval) == IS_BOOL && Z_BVAL_P(retval)==0))
976            if (SUCCESS == zend_fcall_info_call(&handler->func.user->fci, &handler->func.user->fcc, &retval, NULL TSRMLS_CC) && PHP_OUTPUT_USER_SUCCESS(retval)) {
977                /* user handler may have returned TRUE */
978                status = PHP_OUTPUT_HANDLER_NO_DATA;
979                if (Z_TYPE_P(retval) != IS_BOOL) {
980                    convert_to_string_ex(&retval);
981                    if (Z_STRLEN_P(retval)) {
982                        context->out.data = estrndup(Z_STRVAL_P(retval), Z_STRLEN_P(retval));
983                        context->out.used = Z_STRLEN_P(retval);
984                        context->out.free = 1;
985                        status = PHP_OUTPUT_HANDLER_SUCCESS;
986                    }
987                }
988            } else {
989                /* call failed, pass internal buffer along */
990                status = PHP_OUTPUT_HANDLER_FAILURE;
991            }
992
993            zend_fcall_info_argn(&handler->func.user->fci TSRMLS_CC, 0);
994            zval_ptr_dtor(&ob_data);
995            zval_ptr_dtor(&ob_mode);
996            if (retval) {
997                zval_ptr_dtor(&retval);
998            }
999
1000        } else {
1001
1002            php_output_context_feed(context, handler->buffer.data, handler->buffer.size, handler->buffer.used, 0);
1003
1004            if (SUCCESS == handler->func.internal(&handler->opaq, context)) {
1005                if (context->out.used) {
1006                    status = PHP_OUTPUT_HANDLER_SUCCESS;
1007                } else {
1008                    status = PHP_OUTPUT_HANDLER_NO_DATA;
1009                }
1010            } else {
1011                status = PHP_OUTPUT_HANDLER_FAILURE;
1012            }
1013        }
1014        handler->flags |= PHP_OUTPUT_HANDLER_STARTED;
1015        OG(running) = NULL;
1016    }
1017
1018    switch (status) {
1019        case PHP_OUTPUT_HANDLER_FAILURE:
1020            /* disable this handler */
1021            handler->flags |= PHP_OUTPUT_HANDLER_DISABLED;
1022            /* discard any output */
1023            if (context->out.data && context->out.free) {
1024                efree(context->out.data);
1025            }
1026            /* returns handlers buffer */
1027            context->out.data = handler->buffer.data;
1028            context->out.used = handler->buffer.used;
1029            context->out.free = 1;
1030            handler->buffer.data = NULL;
1031            handler->buffer.used = 0;
1032            handler->buffer.size = 0;
1033            break;
1034        case PHP_OUTPUT_HANDLER_NO_DATA:
1035            /* handler ate all */
1036            php_output_context_reset(context);
1037            /* no break */
1038        case PHP_OUTPUT_HANDLER_SUCCESS:
1039            /* no more buffered data */
1040            handler->buffer.used = 0;
1041            handler->flags |= PHP_OUTPUT_HANDLER_PROCESSED;
1042            break;
1043    }
1044
1045    context->op = original_op;
1046    return status;
1047}
1048/* }}} */
1049
1050
1051/* {{{ static void php_output_op(int op, const char *str, size_t len TSRMLS_DC)
1052 * Output op dispatcher, passes input and output handlers output through the output handler stack until it gets written to the SAPI */
1053static inline void php_output_op(int op, const char *str, size_t len TSRMLS_DC)
1054{
1055    php_output_context context;
1056    php_output_handler **active;
1057    int obh_cnt;
1058
1059    if (php_output_lock_error(op TSRMLS_CC)) {
1060        return;
1061    }
1062
1063    php_output_context_init(&context, op TSRMLS_CC);
1064
1065    /*
1066     * broken up for better performance:
1067     *  - apply op to the one active handler; note that OG(active) might be popped off the stack on a flush
1068     *  - or apply op to the handler stack
1069     */
1070    if (OG(active) && (obh_cnt = zend_stack_count(&OG(handlers)))) {
1071        context.in.data = (char *) str;
1072        context.in.used = len;
1073
1074        if (obh_cnt > 1) {
1075            zend_stack_apply_with_argument(&OG(handlers), ZEND_STACK_APPLY_TOPDOWN, php_output_stack_apply_op, &context);
1076        } else if ((SUCCESS == zend_stack_top(&OG(handlers), (void *) &active)) && (!((*active)->flags & PHP_OUTPUT_HANDLER_DISABLED))) {
1077            php_output_handler_op(*active, &context);
1078        } else {
1079            php_output_context_pass(&context);
1080        }
1081    } else {
1082        context.out.data = (char *) str;
1083        context.out.used = len;
1084    }
1085
1086    if (context.out.data && context.out.used) {
1087        php_output_header(TSRMLS_C);
1088
1089        if (!(OG(flags) & PHP_OUTPUT_DISABLED)) {
1090#if PHP_OUTPUT_DEBUG
1091            fprintf(stderr, "::: sapi_write('%s', %zu)\n", context.out.data, context.out.used);
1092#endif
1093            sapi_module.ub_write(context.out.data, context.out.used TSRMLS_CC);
1094
1095            if (OG(flags) & PHP_OUTPUT_IMPLICITFLUSH) {
1096                sapi_flush(TSRMLS_C);
1097            }
1098
1099            OG(flags) |= PHP_OUTPUT_SENT;
1100        }
1101    }
1102    php_output_context_dtor(&context);
1103}
1104/* }}} */
1105
1106/* {{{ static int php_output_stack_apply_op(void *h, void *c)
1107 * Operation callback for the stack apply function */
1108static int php_output_stack_apply_op(void *h, void *c)
1109{
1110    int was_disabled;
1111    php_output_handler_status_t status;
1112    php_output_handler *handler = *(php_output_handler **) h;
1113    php_output_context *context = (php_output_context *) c;
1114
1115    if ((was_disabled = (handler->flags & PHP_OUTPUT_HANDLER_DISABLED))) {
1116        status = PHP_OUTPUT_HANDLER_FAILURE;
1117    } else {
1118        status = php_output_handler_op(handler, context);
1119    }
1120
1121    /*
1122     * handler ate all => break
1123     * handler returned data or failed resp. is disabled => continue
1124     */
1125    switch (status) {
1126        case PHP_OUTPUT_HANDLER_NO_DATA:
1127            return 1;
1128
1129        case PHP_OUTPUT_HANDLER_SUCCESS:
1130            /* swap contexts buffers, unless this is the last handler in the stack */
1131            if (handler->level) {
1132                php_output_context_swap(context);
1133            }
1134            return 0;
1135
1136        case PHP_OUTPUT_HANDLER_FAILURE:
1137        default:
1138            if (was_disabled) {
1139                /* pass input along, if it's the last handler in the stack */
1140                if (!handler->level) {
1141                    php_output_context_pass(context);
1142                }
1143            } else {
1144                /* swap buffers, unless this is the last handler */
1145                if (handler->level) {
1146                    php_output_context_swap(context);
1147                }
1148            }
1149            return 0;
1150    }
1151}
1152/* }}} */
1153
1154/* {{{ static int php_output_stack_apply_clean(void *h, void *c)
1155 * Clean callback for the stack apply function */
1156static int php_output_stack_apply_clean(void *h, void *c)
1157{
1158    php_output_handler *handler = *(php_output_handler **) h;
1159    php_output_context *context = (php_output_context *) c;
1160
1161    handler->buffer.used = 0;
1162    php_output_handler_op(handler, context);
1163    php_output_context_reset(context);
1164    return 0;
1165}
1166/* }}} */
1167
1168/* {{{ static int php_output_stack_apply_list(void *h, void *z)
1169 * List callback for the stack apply function */
1170static int php_output_stack_apply_list(void *h, void *z)
1171{
1172    php_output_handler *handler = *(php_output_handler **) h;
1173    zval *array = (zval *) z;
1174
1175    add_next_index_stringl(array, handler->name, handler->name_len, 1);
1176    return 0;
1177}
1178/* }}} */
1179
1180/* {{{ static int php_output_stack_apply_status(void *h, void *z)
1181 * Status callback for the stack apply function */
1182static int php_output_stack_apply_status(void *h, void *z)
1183{
1184    php_output_handler *handler = *(php_output_handler **) h;
1185    zval *array = (zval *) z;
1186
1187    add_next_index_zval(array, php_output_handler_status(handler, NULL));
1188
1189    return 0;
1190}
1191
1192/* {{{ static zval *php_output_handler_status(php_output_handler *handler, zval *entry)
1193 * Returns an array with the status of the output handler */
1194static inline zval *php_output_handler_status(php_output_handler *handler, zval *entry)
1195{
1196    if (!entry) {
1197        MAKE_STD_ZVAL(entry);
1198        array_init(entry);
1199    }
1200
1201    add_assoc_stringl(entry, "name", handler->name, handler->name_len, 1);
1202    add_assoc_long(entry, "type", (long) (handler->flags & 0xf));
1203    add_assoc_long(entry, "flags", (long) handler->flags);
1204    add_assoc_long(entry, "level", (long) handler->level);
1205    add_assoc_long(entry, "chunk_size", (long) handler->size);
1206    add_assoc_long(entry, "buffer_size", (long) handler->buffer.size);
1207    add_assoc_long(entry, "buffer_used", (long) handler->buffer.used);
1208
1209    return entry;
1210}
1211/* }}} */
1212
1213/* {{{ static int php_output_stack_pop(int flags TSRMLS_DC)
1214 * Pops an output handler off the stack */
1215static inline int php_output_stack_pop(int flags TSRMLS_DC)
1216{
1217    php_output_context context;
1218    php_output_handler **current, *orphan = OG(active);
1219
1220    if (!orphan) {
1221        if (!(flags & PHP_OUTPUT_POP_SILENT)) {
1222            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");
1223        }
1224        return 0;
1225    } else if (!(flags & PHP_OUTPUT_POP_FORCE) && !(orphan->flags & PHP_OUTPUT_HANDLER_REMOVABLE)) {
1226        if (!(flags & PHP_OUTPUT_POP_SILENT)) {
1227            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);
1228        }
1229        return 0;
1230    } else {
1231        php_output_context_init(&context, PHP_OUTPUT_HANDLER_FINAL TSRMLS_CC);
1232
1233        /* don't run the output handler if it's disabled */
1234        if (!(orphan->flags & PHP_OUTPUT_HANDLER_DISABLED)) {
1235            /* didn't it start yet? */
1236            if (!(orphan->flags & PHP_OUTPUT_HANDLER_STARTED)) {
1237                context.op |= PHP_OUTPUT_HANDLER_START;
1238            }
1239            /* signal that we're cleaning up */
1240            if (flags & PHP_OUTPUT_POP_DISCARD) {
1241                context.op |= PHP_OUTPUT_HANDLER_CLEAN;
1242            }
1243            php_output_handler_op(orphan, &context);
1244        }
1245
1246        /* pop it off the stack */
1247        zend_stack_del_top(&OG(handlers));
1248        if (SUCCESS == zend_stack_top(&OG(handlers), (void *) &current)) {
1249            OG(active) = *current;
1250        } else {
1251            OG(active) = NULL;
1252        }
1253
1254        /* pass output along */
1255        if (context.out.data && context.out.used && !(flags & PHP_OUTPUT_POP_DISCARD)) {
1256            php_output_write(context.out.data, context.out.used TSRMLS_CC);
1257        }
1258
1259        /* destroy the handler (after write!) */
1260        php_output_handler_free(&orphan TSRMLS_CC);
1261        php_output_context_dtor(&context);
1262
1263        return 1;
1264    }
1265}
1266/* }}} */
1267
1268/* {{{ static SUCCESS|FAILURE php_output_handler_compat_func(void *ctx, php_output_context *)
1269 * php_output_handler_context_func_t for php_output_handler_func_t output handlers */
1270static int php_output_handler_compat_func(void **handler_context, php_output_context *output_context)
1271{
1272    php_output_handler_func_t func = *(php_output_handler_func_t *) handler_context;
1273    PHP_OUTPUT_TSRMLS(output_context);
1274
1275    if (func) {
1276        char *out_str = NULL;
1277        uint out_len = 0;
1278
1279        func(output_context->in.data, output_context->in.used, &out_str, &out_len, output_context->op TSRMLS_CC);
1280
1281        if (out_str) {
1282            output_context->out.data = out_str;
1283            output_context->out.used = out_len;
1284            output_context->out.free = 1;
1285        } else {
1286            php_output_context_pass(output_context);
1287        }
1288
1289        return SUCCESS;
1290    }
1291    return FAILURE;
1292}
1293/* }}} */
1294
1295/* {{{ static SUCCESS|FAILURE php_output_handler_default_func(void *ctx, php_output_context *)
1296 * Default output handler */
1297static int php_output_handler_default_func(void **handler_context, php_output_context *output_context)
1298{
1299    php_output_context_pass(output_context);
1300    return SUCCESS;
1301}
1302/* }}} */
1303
1304/* {{{ static SUCCESS|FAILURE php_output_handler_devnull_func(void *ctx, php_output_context *)
1305 * Null output handler */
1306static int php_output_handler_devnull_func(void **handler_context, php_output_context *output_context)
1307{
1308    return SUCCESS;
1309}
1310/* }}} */
1311
1312/*
1313 * USERLAND (nearly 1:1 of old output.c)
1314 */
1315
1316/* {{{ proto bool ob_start([string|array user_function [, int chunk_size [, int flags]]])
1317   Turn on Output Buffering (specifying an optional output handler). */
1318PHP_FUNCTION(ob_start)
1319{
1320    zval *output_handler = NULL;
1321    long chunk_size = 0;
1322    long flags = PHP_OUTPUT_HANDLER_STDFLAGS;
1323
1324    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|z/ll", &output_handler, &chunk_size, &flags) == FAILURE) {
1325        return;
1326    }
1327
1328    if (chunk_size < 0) {
1329        chunk_size = 0;
1330    }
1331
1332    if (php_output_start_user(output_handler, chunk_size, flags TSRMLS_CC) == FAILURE) {
1333        php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to create buffer");
1334        RETURN_FALSE;
1335    }
1336    RETURN_TRUE;
1337}
1338/* }}} */
1339
1340/* {{{ proto bool ob_flush(void)
1341   Flush (send) contents of the output buffer. The last buffer content is sent to next buffer */
1342PHP_FUNCTION(ob_flush)
1343{
1344    if (zend_parse_parameters_none() == FAILURE) {
1345        return;
1346    }
1347
1348    if (!OG(active)) {
1349        php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to flush buffer. No buffer to flush");
1350        RETURN_FALSE;
1351    }
1352
1353    if (SUCCESS != php_output_flush(TSRMLS_C)) {
1354        php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to flush buffer of %s (%d)", OG(active)->name, OG(active)->level);
1355        RETURN_FALSE;
1356    }
1357    RETURN_TRUE;
1358}
1359/* }}} */
1360
1361/* {{{ proto bool ob_clean(void)
1362   Clean (delete) the current output buffer */
1363PHP_FUNCTION(ob_clean)
1364{
1365    if (zend_parse_parameters_none() == FAILURE) {
1366        return;
1367    }
1368
1369    if (!OG(active)) {
1370        php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer. No buffer to delete");
1371        RETURN_FALSE;
1372    }
1373
1374    if (SUCCESS != php_output_clean(TSRMLS_C)) {
1375        php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer of %s (%d)", OG(active)->name, OG(active)->level);
1376        RETURN_FALSE;
1377    }
1378    RETURN_TRUE;
1379}
1380/* }}} */
1381
1382/* {{{ proto bool ob_end_flush(void)
1383   Flush (send) the output buffer, and delete current output buffer */
1384PHP_FUNCTION(ob_end_flush)
1385{
1386    if (zend_parse_parameters_none() == FAILURE) {
1387        return;
1388    }
1389
1390    if (!OG(active)) {
1391        php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete and flush buffer. No buffer to delete or flush");
1392        RETURN_FALSE;
1393    }
1394
1395    RETURN_BOOL(SUCCESS == php_output_end(TSRMLS_C));
1396}
1397/* }}} */
1398
1399/* {{{ proto bool ob_end_clean(void)
1400   Clean the output buffer, and delete current output buffer */
1401PHP_FUNCTION(ob_end_clean)
1402{
1403    if (zend_parse_parameters_none() == FAILURE) {
1404        return;
1405    }
1406
1407    if (!OG(active)) {
1408        php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer. No buffer to delete");
1409        RETURN_FALSE;
1410    }
1411
1412    RETURN_BOOL(SUCCESS == php_output_discard(TSRMLS_C));
1413}
1414/* }}} */
1415
1416/* {{{ proto bool ob_get_flush(void)
1417   Get current buffer contents, flush (send) the output buffer, and delete current output buffer */
1418PHP_FUNCTION(ob_get_flush)
1419{
1420    if (zend_parse_parameters_none() == FAILURE) {
1421        return;
1422    }
1423
1424    if (php_output_get_contents(return_value TSRMLS_CC) == FAILURE) {
1425        php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete and flush buffer. No buffer to delete or flush");
1426        RETURN_FALSE;
1427    }
1428
1429    if (SUCCESS != php_output_end(TSRMLS_C)) {
1430        php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer of %s (%d)", OG(active)->name, OG(active)->level);
1431    }
1432}
1433/* }}} */
1434
1435/* {{{ proto bool ob_get_clean(void)
1436   Get current buffer contents and delete current output buffer */
1437PHP_FUNCTION(ob_get_clean)
1438{
1439    if (zend_parse_parameters_none() == FAILURE) {
1440        return;
1441    }
1442
1443    if(!OG(active)) {
1444        RETURN_FALSE;
1445    }
1446
1447    if (php_output_get_contents(return_value TSRMLS_CC) == FAILURE) {
1448        php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer. No buffer to delete");
1449        RETURN_FALSE;
1450    }
1451
1452    if (SUCCESS != php_output_discard(TSRMLS_C)) {
1453        php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer of %s (%d)", OG(active)->name, OG(active)->level);
1454    }
1455}
1456/* }}} */
1457
1458/* {{{ proto string ob_get_contents(void)
1459   Return the contents of the output buffer */
1460PHP_FUNCTION(ob_get_contents)
1461{
1462    if (zend_parse_parameters_none() == FAILURE) {
1463        return;
1464    }
1465
1466    if (php_output_get_contents(return_value TSRMLS_CC) == FAILURE) {
1467        RETURN_FALSE;
1468    }
1469}
1470/* }}} */
1471
1472/* {{{ proto int ob_get_level(void)
1473   Return the nesting level of the output buffer */
1474PHP_FUNCTION(ob_get_level)
1475{
1476    if (zend_parse_parameters_none() == FAILURE) {
1477        return;
1478    }
1479
1480    RETURN_LONG(php_output_get_level(TSRMLS_C));
1481}
1482/* }}} */
1483
1484/* {{{ proto int ob_get_length(void)
1485   Return the length of the output buffer */
1486PHP_FUNCTION(ob_get_length)
1487{
1488    if (zend_parse_parameters_none() == FAILURE) {
1489        return;
1490    }
1491
1492    if (php_output_get_length(return_value TSRMLS_CC) == FAILURE) {
1493        RETURN_FALSE;
1494    }
1495}
1496/* }}} */
1497
1498/* {{{ proto false|array ob_list_handlers()
1499   List all output_buffers in an array */
1500PHP_FUNCTION(ob_list_handlers)
1501{
1502    if (zend_parse_parameters_none() == FAILURE) {
1503        return;
1504    }
1505
1506    array_init(return_value);
1507
1508    if (!OG(active)) {
1509        return;
1510    }
1511
1512    zend_stack_apply_with_argument(&OG(handlers), ZEND_STACK_APPLY_BOTTOMUP, php_output_stack_apply_list, return_value);
1513}
1514/* }}} */
1515
1516/* {{{ proto false|array ob_get_status([bool full_status])
1517   Return the status of the active or all output buffers */
1518PHP_FUNCTION(ob_get_status)
1519{
1520    zend_bool full_status = 0;
1521
1522    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &full_status) == FAILURE) {
1523        return;
1524    }
1525
1526    array_init(return_value);
1527
1528    if (!OG(active)) {
1529        return;
1530    }
1531
1532    if (full_status) {
1533        zend_stack_apply_with_argument(&OG(handlers), ZEND_STACK_APPLY_BOTTOMUP, php_output_stack_apply_status, return_value);
1534    } else {
1535        php_output_handler_status(OG(active), return_value);
1536    }
1537}
1538/* }}} */
1539
1540/* {{{ proto void ob_implicit_flush([int flag])
1541   Turn implicit flush on/off and is equivalent to calling flush() after every output call */
1542PHP_FUNCTION(ob_implicit_flush)
1543{
1544    long flag = 1;
1545
1546    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flag) == FAILURE) {
1547        return;
1548    }
1549
1550    php_output_set_implicit_flush(flag TSRMLS_CC);
1551}
1552/* }}} */
1553
1554/* {{{ proto bool output_reset_rewrite_vars(void)
1555   Reset(clear) URL rewriter values */
1556PHP_FUNCTION(output_reset_rewrite_vars)
1557{
1558    if (php_url_scanner_reset_vars(TSRMLS_C) == SUCCESS) {
1559        RETURN_TRUE;
1560    } else {
1561        RETURN_FALSE;
1562    }
1563}
1564/* }}} */
1565
1566/* {{{ proto bool output_add_rewrite_var(string name, string value)
1567   Add URL rewriter values */
1568PHP_FUNCTION(output_add_rewrite_var)
1569{
1570    char *name, *value;
1571    int name_len, value_len;
1572
1573    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &name, &name_len, &value, &value_len) == FAILURE) {
1574        return;
1575    }
1576
1577    if (php_url_scanner_add_var(name, name_len, value, value_len, 1 TSRMLS_CC) == SUCCESS) {
1578        RETURN_TRUE;
1579    } else {
1580        RETURN_FALSE;
1581    }
1582}
1583/* }}} */
1584
1585/*
1586 * Local variables:
1587 * tab-width: 4
1588 * c-basic-offset: 4
1589 * End:
1590 * vim600: sw=4 ts=4 fdm=marker
1591 * vim<600: sw=4 ts=4
1592 */
1593