Coverage Report

Created: 2025-07-23 06:33

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