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