Coverage Report

Created: 2025-12-14 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/main/output.c
Line
Count
Source
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
2
{
79
2
  memset(G, 0, sizeof(*G));
80
2
}
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
3.68M
{
104
3.68M
  if (!SG(headers_sent)) {
105
6.10k
    if (!OG(output_start_filename)) {
106
6.10k
      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
6.10k
      } else if (zend_is_executing()) {
110
0
        OG(output_start_filename) = zend_get_executed_filename_ex();
111
0
        OG(output_start_lineno) = zend_get_executed_lineno();
112
0
      }
113
6.10k
      if (OG(output_start_filename)) {
114
0
        zend_string_addref(OG(output_start_filename));
115
0
      }
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
6.10k
    }
121
6.10k
    if (!php_header()) {
122
0
      OG(flags) |= PHP_OUTPUT_DISABLED;
123
0
    }
124
6.10k
  }
125
3.68M
}
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
2
{
138
2
  ZEND_INIT_MODULE_GLOBALS(output, php_output_init_globals, NULL);
139
2
  zend_hash_init(&php_output_handler_aliases, 8, NULL, NULL, 1);
140
2
  zend_hash_init(&php_output_handler_conflicts, 8, NULL, NULL, 1);
141
2
  zend_hash_init(&php_output_handler_reverse_conflicts, 8, NULL, reverse_conflict_dtor, 1);
142
2
  php_output_direct = php_output_stdout;
143
2
}
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
6.10k
{
161
#ifdef ZTS
162
  memset(TSRMG_BULK_STATIC(output_globals_id, zend_output_globals*), 0, sizeof(zend_output_globals));
163
#else
164
6.10k
  memset(&output_globals, 0, sizeof(zend_output_globals));
165
6.10k
#endif
166
167
6.10k
  zend_stack_init(&OG(handlers), sizeof(php_output_handler *));
168
6.10k
  OG(flags) |= PHP_OUTPUT_ACTIVATED;
169
170
6.10k
  return SUCCESS;
171
6.10k
}
172
/* }}} */
173
174
/* {{{ void php_output_deactivate(void)
175
 * Destroy the output handler stack */
176
PHPAPI void php_output_deactivate(void)
177
6.10k
{
178
6.10k
  php_output_handler **handler = NULL;
179
180
6.10k
  if ((OG(flags) & PHP_OUTPUT_ACTIVATED)) {
181
6.10k
    php_output_header();
182
183
6.10k
    OG(flags) ^= PHP_OUTPUT_ACTIVATED;
184
6.10k
    OG(active) = NULL;
185
6.10k
    OG(running) = NULL;
186
187
    /* release all output handlers */
188
6.10k
    if (OG(handlers).elements) {
189
0
      while ((handler = zend_stack_top(&OG(handlers)))) {
190
0
        php_output_handler_free(handler);
191
0
        zend_stack_del_top(&OG(handlers));
192
0
      }
193
0
    }
194
6.10k
    zend_stack_destroy(&OG(handlers));
195
6.10k
  }
196
197
6.10k
  if (OG(output_start_filename)) {
198
0
    zend_string_release(OG(output_start_filename));
199
0
    OG(output_start_filename) = NULL;
200
0
  }
201
6.10k
}
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
3.67M
{
239
3.67M
  if (OG(flags) & PHP_OUTPUT_ACTIVATED) {
240
3.67M
    php_output_op(PHP_OUTPUT_HANDLER_WRITE, str, len);
241
3.67M
    return len;
242
3.67M
  }
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
0
{
313
0
  if (php_output_stack_pop(PHP_OUTPUT_POP_TRY)) {
314
0
    return SUCCESS;
315
0
  }
316
0
  return FAILURE;
317
0
}
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
6.10k
{
324
6.10k
  while (OG(active) && php_output_stack_pop(PHP_OUTPUT_POP_FORCE));
325
6.10k
}
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
0
{
332
0
  if (php_output_stack_pop(PHP_OUTPUT_POP_DISCARD|PHP_OUTPUT_POP_TRY)) {
333
0
    return SUCCESS;
334
0
  }
335
0
  return FAILURE;
336
0
}
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
0
{
343
0
  while (OG(active)) {
344
0
    php_output_stack_pop(PHP_OUTPUT_POP_DISCARD|PHP_OUTPUT_POP_FORCE);
345
0
  }
346
0
}
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
0
{
353
0
  return OG(active) ? zend_stack_count(&OG(handlers)) : 0;
354
0
}
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
0
{
361
0
  if (OG(active)) {
362
0
    if (OG(active)->buffer.used) {
363
0
      ZVAL_STRINGL(p, OG(active)->buffer.data, OG(active)->buffer.used);
364
0
    } else {
365
0
      ZVAL_EMPTY_STRING(p);
366
0
    }
367
0
    return SUCCESS;
368
0
  } else {
369
0
    ZVAL_NULL(p);
370
0
    return FAILURE;
371
0
  }
372
0
}
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
0
{
400
0
  php_output_handler *handler;
401
402
0
  handler = php_output_handler_create_internal(ZEND_STRL(php_output_default_handler_name), php_output_handler_default_func, 0, PHP_OUTPUT_HANDLER_STDFLAGS);
403
0
  if (SUCCESS == php_output_handler_start(handler)) {
404
0
    return SUCCESS;
405
0
  }
406
0
  php_output_handler_free(&handler);
407
0
  return FAILURE;
408
0
}
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
0
{
430
0
  php_output_handler *handler;
431
432
0
  if (output_handler) {
433
0
    handler = php_output_handler_create_user(output_handler, chunk_size, flags);
434
0
  } else {
435
0
    handler = php_output_handler_create_internal(ZEND_STRL(php_output_default_handler_name), php_output_handler_default_func, chunk_size, flags);
436
0
  }
437
0
  if (SUCCESS == php_output_handler_start(handler)) {
438
0
    return SUCCESS;
439
0
  }
440
0
  php_output_handler_free(&handler);
441
0
  return FAILURE;
442
0
}
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
0
{
465
0
  zend_string *handler_name = NULL;
466
0
  char *error = NULL;
467
0
  php_output_handler *handler = NULL;
468
0
  php_output_handler_alias_ctor_t alias = NULL;
469
0
  php_output_handler_user_func_t *user = NULL;
470
471
0
  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
0
    default:
482
0
      user = ecalloc(1, sizeof(php_output_handler_user_func_t));
483
0
      if (SUCCESS == zend_fcall_info_init(output_handler, 0, &user->fci, &user->fcc, &handler_name, &error)) {
484
0
        handler = php_output_handler_init(handler_name, chunk_size, PHP_OUTPUT_HANDLER_ABILITY_FLAGS(flags) | PHP_OUTPUT_HANDLER_USER);
485
0
        ZVAL_COPY(&user->zoh, output_handler);
486
0
        handler->func.user = user;
487
0
      } else {
488
0
        efree(user);
489
0
      }
490
0
      if (error) {
491
0
        php_error_docref("ref.outcontrol", E_WARNING, "%s", error);
492
0
        efree(error);
493
0
      }
494
0
      if (handler_name) {
495
0
        zend_string_release_ex(handler_name, 0);
496
0
      }
497
0
  }
498
499
0
  return handler;
500
0
}
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
0
{
507
0
  php_output_handler *handler;
508
0
  zend_string *str = zend_string_init(name, name_len, 0);
509
510
0
  handler = php_output_handler_init(str, chunk_size, PHP_OUTPUT_HANDLER_ABILITY_FLAGS(flags) | PHP_OUTPUT_HANDLER_INTERNAL);
511
0
  handler->func.internal = output_handler;
512
0
  zend_string_release_ex(str, 0);
513
514
0
  return handler;
515
0
}
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
0
{
534
0
  HashTable *rconflicts;
535
0
  php_output_handler_conflict_check_t conflict;
536
537
0
  if (php_output_lock_error(PHP_OUTPUT_HANDLER_START) || !handler) {
538
0
    return FAILURE;
539
0
  }
540
0
  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
0
  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
0
  handler->level = zend_stack_push(&OG(handlers), &handler);
554
0
  OG(active) = handler;
555
0
  return SUCCESS;
556
0
}
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
0
{
700
0
  if (handler->name) {
701
0
    zend_string_release_ex(handler->name, 0);
702
0
  }
703
0
  if (handler->buffer.data) {
704
0
    efree(handler->buffer.data);
705
0
  }
706
0
  if (handler->flags & PHP_OUTPUT_HANDLER_USER) {
707
0
    zval_ptr_dtor(&handler->func.user->zoh);
708
0
    efree(handler->func.user);
709
0
  }
710
0
  if (handler->dtor && handler->opaq) {
711
0
    handler->dtor(handler->opaq);
712
0
  }
713
0
  memset(handler, 0, sizeof(*handler));
714
0
}
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
0
{
721
0
  if (*h) {
722
0
    php_output_handler_dtor(*h);
723
0
    efree(*h);
724
0
    *h = NULL;
725
0
  }
726
0
}
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
6.10k
{
733
6.10k
  if (flush) {
734
6.10k
    OG(flags) |= PHP_OUTPUT_IMPLICITFLUSH;
735
6.10k
  } else {
736
0
    OG(flags) &= ~PHP_OUTPUT_IMPLICITFLUSH;
737
0
  }
738
6.10k
}
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
0
{
745
0
  return OG(output_start_filename) ? ZSTR_VAL(OG(output_start_filename)) : NULL;
746
0
}
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
0
{
753
0
  return OG(output_start_lineno);
754
0
}
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
3.67M
{
761
  /* if there's no ob active, ob has been stopped */
762
3.67M
  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
3.67M
  return false;
769
3.67M
}
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
3.67M
{
776
3.67M
  memset(context, 0, sizeof(php_output_context));
777
3.67M
  context->op = op;
778
3.67M
}
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
0
{
785
0
  int op = context->op;
786
0
  php_output_context_dtor(context);
787
0
  memset(context, 0, sizeof(php_output_context));
788
0
  context->op = op;
789
0
}
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
0
{
796
0
  if (context->in.free && context->in.data) {
797
0
    efree(context->in.data);
798
0
  }
799
0
  context->in.data = data;
800
0
  context->in.used = used;
801
0
  context->in.free = free;
802
0
  context->in.size = size;
803
0
}
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
0
{
828
0
  context->out.data = context->in.data;
829
0
  context->out.used = context->in.used;
830
0
  context->out.size = context->in.size;
831
0
  context->out.free = context->in.free;
832
0
  context->in.data = NULL;
833
0
  context->in.used = 0;
834
0
  context->in.free = 0;
835
0
  context->in.size = 0;
836
0
}
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
3.67M
{
843
3.67M
  if (context->in.free && context->in.data) {
844
0
    efree(context->in.data);
845
0
    context->in.data = NULL;
846
0
  }
847
3.67M
  if (context->out.free && context->out.data) {
848
0
    efree(context->out.data);
849
0
    context->out.data = NULL;
850
0
  }
851
3.67M
}
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
0
{
858
0
  php_output_handler *handler;
859
860
0
  handler = ecalloc(1, sizeof(php_output_handler));
861
0
  handler->name = zend_string_copy(name);
862
0
  handler->size = chunk_size;
863
0
  handler->flags = flags;
864
0
  handler->buffer.size = PHP_OUTPUT_HANDLER_INITBUF_SIZE(chunk_size);
865
0
  handler->buffer.data = emalloc(handler->buffer.size);
866
867
0
  return handler;
868
0
}
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
0
{
875
0
  if (buf->used) {
876
0
    OG(flags) |= PHP_OUTPUT_WRITTEN;
877
    /* store it away */
878
0
    if ((handler->buffer.size - handler->buffer.used) <= buf->used) {
879
0
      size_t grow_int = PHP_OUTPUT_HANDLER_INITBUF_SIZE(handler->size);
880
0
      size_t grow_buf = PHP_OUTPUT_HANDLER_INITBUF_SIZE(buf->used - (handler->buffer.size - handler->buffer.used));
881
0
      size_t grow_max = MAX(grow_int, grow_buf);
882
883
0
      handler->buffer.data = safe_erealloc(handler->buffer.data, 1, handler->buffer.size, grow_max);
884
0
      handler->buffer.size += grow_max;
885
0
    }
886
0
    memcpy(handler->buffer.data + handler->buffer.used, buf->data, buf->used);
887
0
    handler->buffer.used += buf->used;
888
889
    /* chunked buffering */
890
0
    if (handler->size && (handler->buffer.used >= handler->size)) {
891
      /* store away errors and/or any intermediate output */
892
0
      return OG(running) ? true : false;
893
0
    }
894
0
  }
895
0
  return true;
896
0
}
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
0
{
903
0
  php_output_handler_status_t status;
904
0
  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
0
  if (handler->flags & PHP_OUTPUT_HANDLER_DISABLED) {
929
0
    return PHP_OUTPUT_HANDLER_FAILURE;
930
0
  }
931
932
0
  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
0
  if (context->op == PHP_OUTPUT_HANDLER_WRITE && OG(active) && OG(running)) {
941
0
    handler->flags |= PHP_OUTPUT_HANDLER_PRODUCED_OUTPUT;
942
0
  }
943
944
0
  bool still_have_handler = true;
945
  /* storable? */
946
0
  if (php_output_handler_append(handler, &context->in) && !context->op) {
947
0
    context->op = original_op;
948
0
    return PHP_OUTPUT_HANDLER_NO_DATA;
949
0
  } else {
950
    /* need to start? */
951
0
    if (!(handler->flags & PHP_OUTPUT_HANDLER_STARTED)) {
952
0
      context->op |= PHP_OUTPUT_HANDLER_START;
953
0
    }
954
955
0
    OG(running) = handler;
956
0
    if (handler->flags & PHP_OUTPUT_HANDLER_USER) {
957
0
      zval ob_args[2];
958
0
      zval retval;
959
960
      /* ob_data */
961
0
      ZVAL_STRINGL(&ob_args[0], handler->buffer.data, handler->buffer.used);
962
      /* ob_mode */
963
0
      ZVAL_LONG(&ob_args[1], (zend_long) context->op);
964
965
      /* Set FCI info */
966
0
      handler->func.user->fci.param_count = 2;
967
0
      handler->func.user->fci.params = ob_args;
968
0
      handler->func.user->fci.retval = &retval;
969
970
0
      if (SUCCESS == zend_call_function(&handler->func.user->fci, &handler->func.user->fcc) && Z_TYPE(retval) != IS_UNDEF) {
971
0
        if (handler->flags & PHP_OUTPUT_HANDLER_PRODUCED_OUTPUT) {
972
          // Make sure that we don't get lost in the current output buffer
973
          // by disabling it
974
0
          handler->flags |= PHP_OUTPUT_HANDLER_DISABLED;
975
0
          if (handler->flags & PHP_OUTPUT_HANDLER_PRODUCED_OUTPUT) {
976
            // The handler might not always produce output
977
0
            handler->flags &= ~PHP_OUTPUT_HANDLER_PRODUCED_OUTPUT;
978
0
            php_error_docref(
979
0
              NULL,
980
0
              E_DEPRECATED,
981
0
              "Producing output from user output handler %s is deprecated",
982
0
              ZSTR_VAL(handler->name)
983
0
            );
984
0
          }
985
986
          // Check if the handler is still in the list of handlers to
987
          // determine if the PHP_OUTPUT_HANDLER_DISABLED flag can
988
          // be removed
989
0
          still_have_handler = false;
990
0
          int handler_count = php_output_get_level();
991
0
          if (handler_count) {
992
0
            php_output_handler **handlers = (php_output_handler **) zend_stack_base(&OG(handlers));
993
0
            for (int handler_num = 0; handler_num < handler_count; ++handler_num) {
994
0
              php_output_handler *curr_handler = handlers[handler_num];
995
0
              if (curr_handler == handler) {
996
0
                handler->flags &= (~PHP_OUTPUT_HANDLER_DISABLED);
997
0
                still_have_handler = true;
998
0
                break;
999
0
              }
1000
0
            }
1001
0
          }
1002
0
        }
1003
0
        if (Z_TYPE(retval) == IS_FALSE) {
1004
          /* call failed, pass internal buffer along */
1005
0
          status = PHP_OUTPUT_HANDLER_FAILURE;
1006
0
        } else {
1007
          /* user handler may have returned TRUE */
1008
0
          status = PHP_OUTPUT_HANDLER_NO_DATA;
1009
0
          if (Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) {
1010
0
            convert_to_string(&retval);
1011
0
            if (Z_STRLEN(retval)) {
1012
0
              context->out.data = estrndup(Z_STRVAL(retval), Z_STRLEN(retval));
1013
0
              context->out.used = Z_STRLEN(retval);
1014
0
              context->out.free = 1;
1015
0
              status = PHP_OUTPUT_HANDLER_SUCCESS;
1016
0
            }
1017
0
          }
1018
0
        }
1019
0
      } else {
1020
        /* call failed, pass internal buffer along */
1021
0
        status = PHP_OUTPUT_HANDLER_FAILURE;
1022
0
      }
1023
1024
      /* Free arguments and return value */
1025
0
      zval_ptr_dtor(&ob_args[0]);
1026
0
      zval_ptr_dtor(&ob_args[1]);
1027
0
      zval_ptr_dtor(&retval);
1028
1029
0
    } else {
1030
1031
0
      php_output_context_feed(context, handler->buffer.data, handler->buffer.size, handler->buffer.used, 0);
1032
1033
0
      if (SUCCESS == handler->func.internal(&handler->opaq, context)) {
1034
0
        if (context->out.used) {
1035
0
          status = PHP_OUTPUT_HANDLER_SUCCESS;
1036
0
        } else {
1037
0
          status = PHP_OUTPUT_HANDLER_NO_DATA;
1038
0
        }
1039
0
      } else {
1040
0
        status = PHP_OUTPUT_HANDLER_FAILURE;
1041
0
      }
1042
0
    }
1043
0
    if (still_have_handler) {
1044
0
      handler->flags |= PHP_OUTPUT_HANDLER_STARTED;
1045
0
    }
1046
0
    OG(running) = NULL;
1047
0
  }
1048
1049
0
  if (!still_have_handler) {
1050
    // Handler and context will have both already been freed
1051
0
    return status;
1052
0
  }
1053
1054
0
  switch (status) {
1055
0
    case PHP_OUTPUT_HANDLER_FAILURE:
1056
      /* disable this handler */
1057
0
      handler->flags |= PHP_OUTPUT_HANDLER_DISABLED;
1058
      /* discard any output */
1059
0
      if (context->out.data && context->out.free) {
1060
0
        efree(context->out.data);
1061
0
      }
1062
      /* returns handlers buffer */
1063
0
      context->out.data = handler->buffer.data;
1064
0
      context->out.used = handler->buffer.used;
1065
0
      context->out.free = 1;
1066
0
      handler->buffer.data = NULL;
1067
0
      handler->buffer.used = 0;
1068
0
      handler->buffer.size = 0;
1069
0
      break;
1070
0
    case PHP_OUTPUT_HANDLER_NO_DATA:
1071
      /* handler ate all */
1072
0
      php_output_context_reset(context);
1073
0
      ZEND_FALLTHROUGH;
1074
0
    case PHP_OUTPUT_HANDLER_SUCCESS:
1075
      /* no more buffered data */
1076
0
      handler->buffer.used = 0;
1077
0
      handler->flags |= PHP_OUTPUT_HANDLER_PROCESSED;
1078
0
      break;
1079
0
  }
1080
1081
0
  context->op = original_op;
1082
0
  return status;
1083
0
}
1084
/* }}} */
1085
1086
1087
/* {{{ static void php_output_op(int op, const char *str, size_t len)
1088
 * Output op dispatcher, passes input and output handlers output through the output handler stack until it gets written to the SAPI */
1089
static inline void php_output_op(int op, const char *str, size_t len)
1090
3.67M
{
1091
3.67M
  php_output_context context;
1092
3.67M
  php_output_handler **active;
1093
3.67M
  int obh_cnt;
1094
1095
3.67M
  if (php_output_lock_error(op)) {
1096
0
    return;
1097
0
  }
1098
1099
3.67M
  php_output_context_init(&context, op);
1100
1101
  /*
1102
   * broken up for better performance:
1103
   *  - apply op to the one active handler; note that OG(active) might be popped off the stack on a flush
1104
   *  - or apply op to the handler stack
1105
   */
1106
3.67M
  if (OG(active) && (obh_cnt = zend_stack_count(&OG(handlers)))) {
1107
0
    context.in.data = (char *) str;
1108
0
    context.in.used = len;
1109
1110
0
    if (obh_cnt > 1) {
1111
0
      zend_stack_apply_with_argument(&OG(handlers), ZEND_STACK_APPLY_TOPDOWN, php_output_stack_apply_op, &context);
1112
0
    } else if ((active = zend_stack_top(&OG(handlers))) && (!((*active)->flags & PHP_OUTPUT_HANDLER_DISABLED))) {
1113
0
      php_output_handler_op(*active, &context);
1114
0
    } else {
1115
0
      php_output_context_pass(&context);
1116
0
    }
1117
3.67M
  } else {
1118
3.67M
    context.out.data = (char *) str;
1119
3.67M
    context.out.used = len;
1120
3.67M
  }
1121
1122
3.67M
  if (context.out.data && context.out.used) {
1123
3.67M
    php_output_header();
1124
1125
3.67M
    if (!(OG(flags) & PHP_OUTPUT_DISABLED)) {
1126
#if PHP_OUTPUT_DEBUG
1127
      fprintf(stderr, "::: sapi_write('%s', %zu)\n", context.out.data, context.out.used);
1128
#endif
1129
3.67M
      sapi_module.ub_write(context.out.data, context.out.used);
1130
1131
3.67M
      if (OG(flags) & PHP_OUTPUT_IMPLICITFLUSH) {
1132
3.67M
        sapi_flush();
1133
3.67M
      }
1134
1135
3.67M
      OG(flags) |= PHP_OUTPUT_SENT;
1136
3.67M
    }
1137
3.67M
  }
1138
3.67M
  php_output_context_dtor(&context);
1139
3.67M
}
1140
/* }}} */
1141
1142
/* {{{ static int php_output_stack_apply_op(void *h, void *c)
1143
 * Operation callback for the stack apply function */
1144
static int php_output_stack_apply_op(void *h, void *c)
1145
0
{
1146
0
  int was_disabled;
1147
0
  php_output_handler_status_t status;
1148
0
  php_output_handler *handler = *(php_output_handler **) h;
1149
0
  php_output_context *context = (php_output_context *) c;
1150
1151
0
  if ((was_disabled = (handler->flags & PHP_OUTPUT_HANDLER_DISABLED))) {
1152
0
    status = PHP_OUTPUT_HANDLER_FAILURE;
1153
0
  } else {
1154
0
    status = php_output_handler_op(handler, context);
1155
0
  }
1156
1157
  /*
1158
   * handler ate all => break
1159
   * handler returned data or failed resp. is disabled => continue
1160
   */
1161
0
  switch (status) {
1162
0
    case PHP_OUTPUT_HANDLER_NO_DATA:
1163
0
      return 1;
1164
1165
0
    case PHP_OUTPUT_HANDLER_SUCCESS:
1166
      /* swap contexts buffers, unless this is the last handler in the stack */
1167
0
      if (handler->level) {
1168
0
        php_output_context_swap(context);
1169
0
      }
1170
0
      return 0;
1171
1172
0
    case PHP_OUTPUT_HANDLER_FAILURE:
1173
0
    default:
1174
0
      if (was_disabled) {
1175
        /* pass input along, if it's the last handler in the stack */
1176
0
        if (!handler->level) {
1177
0
          php_output_context_pass(context);
1178
0
        }
1179
0
      } else {
1180
        /* swap buffers, unless this is the last handler */
1181
0
        if (handler->level) {
1182
0
          php_output_context_swap(context);
1183
0
        }
1184
0
      }
1185
0
      return 0;
1186
0
  }
1187
0
}
1188
/* }}} */
1189
1190
/* {{{ static int php_output_stack_apply_clean(void *h, void *c)
1191
 * Clean callback for the stack apply function */
1192
static int php_output_stack_apply_clean(void *h, void *c)
1193
0
{
1194
0
  php_output_handler *handler = *(php_output_handler **) h;
1195
0
  php_output_context *context = (php_output_context *) c;
1196
1197
0
  handler->buffer.used = 0;
1198
0
  php_output_handler_op(handler, context);
1199
0
  php_output_context_reset(context);
1200
0
  return 0;
1201
0
}
1202
/* }}} */
1203
1204
/* {{{ static int php_output_stack_apply_list(void *h, void *z)
1205
 * List callback for the stack apply function */
1206
static int php_output_stack_apply_list(void *h, void *z)
1207
0
{
1208
0
  php_output_handler *handler = *(php_output_handler **) h;
1209
0
  zval *array = (zval *) z;
1210
1211
0
  add_next_index_str(array, zend_string_copy(handler->name));
1212
0
  return 0;
1213
0
}
1214
/* }}} */
1215
1216
/* {{{ static int php_output_stack_apply_status(void *h, void *z)
1217
 * Status callback for the stack apply function */
1218
static int php_output_stack_apply_status(void *h, void *z)
1219
0
{
1220
0
  php_output_handler *handler = *(php_output_handler **) h;
1221
0
  zval arr, *array = (zval *) z;
1222
1223
0
  add_next_index_zval(array, php_output_handler_status(handler, &arr));
1224
1225
0
  return 0;
1226
0
}
1227
1228
/* {{{ static zval *php_output_handler_status(php_output_handler *handler, zval *entry)
1229
 * Returns an array with the status of the output handler */
1230
static inline zval *php_output_handler_status(php_output_handler *handler, zval *entry)
1231
0
{
1232
0
  ZEND_ASSERT(entry != NULL);
1233
1234
0
  array_init(entry);
1235
0
  add_assoc_str(entry, "name", zend_string_copy(handler->name));
1236
0
  add_assoc_long(entry, "type", (zend_long) (handler->flags & 0xf));
1237
0
  add_assoc_long(entry, "flags", (zend_long) handler->flags);
1238
0
  add_assoc_long(entry, "level", (zend_long) handler->level);
1239
0
  add_assoc_long(entry, "chunk_size", (zend_long) handler->size);
1240
0
  add_assoc_long(entry, "buffer_size", (zend_long) handler->buffer.size);
1241
0
  add_assoc_long(entry, "buffer_used", (zend_long) handler->buffer.used);
1242
1243
0
  return entry;
1244
0
}
1245
/* }}} */
1246
1247
/* {{{ static int php_output_stack_pop(int flags)
1248
 * Pops an output handler off the stack */
1249
static int php_output_stack_pop(int flags)
1250
0
{
1251
0
  php_output_context context;
1252
0
  php_output_handler **current, *orphan = OG(active);
1253
1254
0
  if (!orphan) {
1255
0
    if (!(flags & PHP_OUTPUT_POP_SILENT)) {
1256
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");
1257
0
    }
1258
0
    return 0;
1259
0
  } else if (!(flags & PHP_OUTPUT_POP_FORCE) && !(orphan->flags & PHP_OUTPUT_HANDLER_REMOVABLE)) {
1260
0
    if (!(flags & PHP_OUTPUT_POP_SILENT)) {
1261
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);
1262
0
    }
1263
0
    return 0;
1264
0
  } else {
1265
0
    php_output_context_init(&context, PHP_OUTPUT_HANDLER_FINAL);
1266
1267
    /* don't run the output handler if it's disabled */
1268
0
    if (!(orphan->flags & PHP_OUTPUT_HANDLER_DISABLED)) {
1269
      /* didn't it start yet? */
1270
0
      if (!(orphan->flags & PHP_OUTPUT_HANDLER_STARTED)) {
1271
0
        context.op |= PHP_OUTPUT_HANDLER_START;
1272
0
      }
1273
      /* signal that we're cleaning up */
1274
0
      if (flags & PHP_OUTPUT_POP_DISCARD) {
1275
0
        context.op |= PHP_OUTPUT_HANDLER_CLEAN;
1276
0
      }
1277
0
      php_output_handler_op(orphan, &context);
1278
0
    }
1279
    // If it isn't still in the stack, cannot free it
1280
0
    bool still_have_handler = false;
1281
0
    int handler_count = php_output_get_level();
1282
0
    if (handler_count) {
1283
0
      php_output_handler **handlers = (php_output_handler **) zend_stack_base(&OG(handlers));
1284
0
      for (int handler_num = 0; handler_num < handler_count; ++handler_num) {
1285
0
        php_output_handler *curr_handler = handlers[handler_num];
1286
0
        if (curr_handler == orphan) {
1287
0
          still_have_handler = true;
1288
0
          break;
1289
0
        }
1290
0
      }
1291
0
    }
1292
1293
    /* pop it off the stack */
1294
0
    zend_stack_del_top(&OG(handlers));
1295
0
    if ((current = zend_stack_top(&OG(handlers)))) {
1296
0
      OG(active) = *current;
1297
0
    } else {
1298
0
      OG(active) = NULL;
1299
0
    }
1300
1301
    /* pass output along */
1302
0
    if (context.out.data && context.out.used && !(flags & PHP_OUTPUT_POP_DISCARD)) {
1303
0
      php_output_write(context.out.data, context.out.used);
1304
0
    }
1305
1306
    /* destroy the handler (after write!) */
1307
0
    if (still_have_handler) {
1308
0
      php_output_handler_free(&orphan);
1309
0
    }
1310
0
    php_output_context_dtor(&context);
1311
1312
0
    return 1;
1313
0
  }
1314
0
}
1315
/* }}} */
1316
1317
/* {{{ static SUCCESS|FAILURE php_output_handler_compat_func(void *ctx, php_output_context *)
1318
 * php_output_handler_context_func_t for php_output_handler_func_t output handlers */
1319
static zend_result php_output_handler_compat_func(void **handler_context, php_output_context *output_context)
1320
0
{
1321
0
  php_output_handler_func_t func = *(php_output_handler_func_t *) handler_context;
1322
1323
0
  if (func) {
1324
0
    char *out_str = NULL;
1325
0
    size_t out_len = 0;
1326
1327
0
    func(output_context->in.data, output_context->in.used, &out_str, &out_len, output_context->op);
1328
1329
0
    if (out_str) {
1330
0
      output_context->out.data = out_str;
1331
0
      output_context->out.used = out_len;
1332
0
      output_context->out.free = 1;
1333
0
    } else {
1334
0
      php_output_context_pass(output_context);
1335
0
    }
1336
1337
0
    return SUCCESS;
1338
0
  }
1339
0
  return FAILURE;
1340
0
}
1341
/* }}} */
1342
1343
/* {{{ static SUCCESS|FAILURE php_output_handler_default_func(void *ctx, php_output_context *)
1344
 * Default output handler */
1345
static zend_result php_output_handler_default_func(void **handler_context, php_output_context *output_context)
1346
0
{
1347
0
  php_output_context_pass(output_context);
1348
0
  return SUCCESS;
1349
0
}
1350
/* }}} */
1351
1352
/* {{{ static SUCCESS|FAILURE php_output_handler_devnull_func(void *ctx, php_output_context *)
1353
 * Null output handler */
1354
static zend_result php_output_handler_devnull_func(void **handler_context, php_output_context *output_context)
1355
0
{
1356
0
  return SUCCESS;
1357
0
}
1358
/* }}} */
1359
1360
/*
1361
 * USERLAND (nearly 1:1 of old output.c)
1362
 */
1363
1364
/* {{{ Turn on Output Buffering (specifying an optional output handler). */
1365
PHP_FUNCTION(ob_start)
1366
0
{
1367
0
  zval *output_handler = NULL;
1368
0
  zend_long chunk_size = 0;
1369
0
  zend_long flags = PHP_OUTPUT_HANDLER_STDFLAGS;
1370
1371
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "|zll", &output_handler, &chunk_size, &flags) == FAILURE) {
1372
0
    RETURN_THROWS();
1373
0
  }
1374
1375
0
  if (chunk_size < 0) {
1376
0
    chunk_size = 0;
1377
0
  }
1378
1379
0
  if (php_output_start_user(output_handler, chunk_size, flags) == FAILURE) {
1380
0
    php_error_docref("ref.outcontrol", E_NOTICE, "Failed to create buffer");
1381
0
    RETURN_FALSE;
1382
0
  }
1383
0
  RETURN_TRUE;
1384
0
}
1385
/* }}} */
1386
1387
/* {{{ Flush (send) contents of the output buffer. The last buffer content is sent to next buffer */
1388
PHP_FUNCTION(ob_flush)
1389
0
{
1390
0
  if (zend_parse_parameters_none() == FAILURE) {
1391
0
    RETURN_THROWS();
1392
0
  }
1393
1394
0
  if (!OG(active)) {
1395
0
    php_error_docref("ref.outcontrol", E_NOTICE, "Failed to flush buffer. No buffer to flush");
1396
0
    RETURN_FALSE;
1397
0
  }
1398
1399
0
  if (SUCCESS != php_output_flush()) {
1400
0
    php_error_docref("ref.outcontrol", E_NOTICE, "Failed to flush buffer of %s (%d)", ZSTR_VAL(OG(active)->name), OG(active)->level);
1401
0
    RETURN_FALSE;
1402
0
  }
1403
0
  RETURN_TRUE;
1404
0
}
1405
/* }}} */
1406
1407
/* {{{ Clean (delete) the current output buffer */
1408
PHP_FUNCTION(ob_clean)
1409
0
{
1410
0
  if (zend_parse_parameters_none() == FAILURE) {
1411
0
    RETURN_THROWS();
1412
0
  }
1413
1414
0
  if (!OG(active)) {
1415
0
    php_error_docref("ref.outcontrol", E_NOTICE, "Failed to delete buffer. No buffer to delete");
1416
0
    RETURN_FALSE;
1417
0
  }
1418
1419
0
  if (SUCCESS != php_output_clean()) {
1420
0
    php_error_docref("ref.outcontrol", E_NOTICE, "Failed to delete buffer of %s (%d)", ZSTR_VAL(OG(active)->name), OG(active)->level);
1421
0
    RETURN_FALSE;
1422
0
  }
1423
0
  RETURN_TRUE;
1424
0
}
1425
/* }}} */
1426
1427
/* {{{ Flush (send) the output buffer, and delete current output buffer */
1428
PHP_FUNCTION(ob_end_flush)
1429
0
{
1430
0
  if (zend_parse_parameters_none() == FAILURE) {
1431
0
    RETURN_THROWS();
1432
0
  }
1433
1434
0
  if (!OG(active)) {
1435
0
    php_error_docref("ref.outcontrol", E_NOTICE, "Failed to delete and flush buffer. No buffer to delete or flush");
1436
0
    RETURN_FALSE;
1437
0
  }
1438
1439
0
  RETURN_BOOL(SUCCESS == php_output_end());
1440
0
}
1441
/* }}} */
1442
1443
/* {{{ Clean the output buffer, and delete current output buffer */
1444
PHP_FUNCTION(ob_end_clean)
1445
0
{
1446
0
  if (zend_parse_parameters_none() == FAILURE) {
1447
0
    RETURN_THROWS();
1448
0
  }
1449
1450
0
  if (!OG(active)) {
1451
0
    php_error_docref("ref.outcontrol", E_NOTICE, "Failed to delete buffer. No buffer to delete");
1452
0
    RETURN_FALSE;
1453
0
  }
1454
1455
0
  RETURN_BOOL(SUCCESS == php_output_discard());
1456
0
}
1457
/* }}} */
1458
1459
/* {{{ Get current buffer contents, flush (send) the output buffer, and delete current output buffer */
1460
PHP_FUNCTION(ob_get_flush)
1461
0
{
1462
0
  if (zend_parse_parameters_none() == FAILURE) {
1463
0
    RETURN_THROWS();
1464
0
  }
1465
1466
0
  if (php_output_get_contents(return_value) == FAILURE) {
1467
0
    php_error_docref("ref.outcontrol", E_NOTICE, "Failed to delete and flush buffer. No buffer to delete or flush");
1468
0
    RETURN_FALSE;
1469
0
  }
1470
1471
0
  if (SUCCESS != php_output_end()) {
1472
0
    php_error_docref("ref.outcontrol", E_NOTICE, "Failed to delete buffer of %s (%d)", ZSTR_VAL(OG(active)->name), OG(active)->level);
1473
0
  }
1474
0
}
1475
/* }}} */
1476
1477
/* {{{ Get current buffer contents and delete current output buffer */
1478
PHP_FUNCTION(ob_get_clean)
1479
0
{
1480
0
  if (zend_parse_parameters_none() == FAILURE) {
1481
0
    RETURN_THROWS();
1482
0
  }
1483
1484
0
  if(!OG(active)) {
1485
0
    RETURN_FALSE;
1486
0
  }
1487
1488
0
  if (php_output_get_contents(return_value) == FAILURE) {
1489
0
    php_error_docref("ref.outcontrol", E_NOTICE, "Failed to delete buffer. No buffer to delete");
1490
0
    RETURN_FALSE;
1491
0
  }
1492
1493
0
  if (SUCCESS != php_output_discard()) {
1494
0
    php_error_docref("ref.outcontrol", E_NOTICE, "Failed to delete buffer of %s (%d)", ZSTR_VAL(OG(active)->name), OG(active)->level);
1495
0
  }
1496
0
}
1497
/* }}} */
1498
1499
/* {{{ Return the contents of the output buffer */
1500
PHP_FUNCTION(ob_get_contents)
1501
0
{
1502
0
  if (zend_parse_parameters_none() == FAILURE) {
1503
0
    RETURN_THROWS();
1504
0
  }
1505
1506
0
  if (php_output_get_contents(return_value) == FAILURE) {
1507
0
    RETURN_FALSE;
1508
0
  }
1509
0
}
1510
/* }}} */
1511
1512
/* {{{ Return the nesting level of the output buffer */
1513
PHP_FUNCTION(ob_get_level)
1514
0
{
1515
0
  if (zend_parse_parameters_none() == FAILURE) {
1516
0
    RETURN_THROWS();
1517
0
  }
1518
1519
0
  RETURN_LONG(php_output_get_level());
1520
0
}
1521
/* }}} */
1522
1523
/* {{{ Return the length of the output buffer */
1524
PHP_FUNCTION(ob_get_length)
1525
0
{
1526
0
  if (zend_parse_parameters_none() == FAILURE) {
1527
0
    RETURN_THROWS();
1528
0
  }
1529
1530
0
  if (php_output_get_length(return_value) == FAILURE) {
1531
0
    RETURN_FALSE;
1532
0
  }
1533
0
}
1534
/* }}} */
1535
1536
/* {{{ List all output_buffers in an array */
1537
PHP_FUNCTION(ob_list_handlers)
1538
0
{
1539
0
  if (zend_parse_parameters_none() == FAILURE) {
1540
0
    RETURN_THROWS();
1541
0
  }
1542
1543
0
  array_init(return_value);
1544
1545
0
  if (!OG(active)) {
1546
0
    return;
1547
0
  }
1548
1549
0
  zend_stack_apply_with_argument(&OG(handlers), ZEND_STACK_APPLY_BOTTOMUP, php_output_stack_apply_list, return_value);
1550
0
}
1551
/* }}} */
1552
1553
/* {{{ Return the status of the active or all output buffers */
1554
PHP_FUNCTION(ob_get_status)
1555
0
{
1556
0
  bool full_status = 0;
1557
1558
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &full_status) == FAILURE) {
1559
0
    RETURN_THROWS();
1560
0
  }
1561
1562
0
  if (!OG(active)) {
1563
0
    array_init(return_value);
1564
0
    return;
1565
0
  }
1566
1567
0
  if (full_status) {
1568
0
    array_init(return_value);
1569
0
    zend_stack_apply_with_argument(&OG(handlers), ZEND_STACK_APPLY_BOTTOMUP, php_output_stack_apply_status, return_value);
1570
0
  } else {
1571
0
    php_output_handler_status(OG(active), return_value);
1572
0
  }
1573
0
}
1574
/* }}} */
1575
1576
/* {{{ Turn implicit flush on/off and is equivalent to calling flush() after every output call */
1577
PHP_FUNCTION(ob_implicit_flush)
1578
0
{
1579
0
  zend_long flag = 1;
1580
1581
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &flag) == FAILURE) {
1582
0
    RETURN_THROWS();
1583
0
  }
1584
1585
0
  php_output_set_implicit_flush((int) flag);
1586
0
}
1587
/* }}} */
1588
1589
/* {{{ Reset(clear) URL rewriter values */
1590
PHP_FUNCTION(output_reset_rewrite_vars)
1591
0
{
1592
0
  if (zend_parse_parameters_none() == FAILURE) {
1593
0
    RETURN_THROWS();
1594
0
  }
1595
1596
0
  if (php_url_scanner_reset_vars() == SUCCESS) {
1597
0
    RETURN_TRUE;
1598
0
  } else {
1599
0
    RETURN_FALSE;
1600
0
  }
1601
0
}
1602
/* }}} */
1603
1604
/* {{{ Add URL rewriter values */
1605
PHP_FUNCTION(output_add_rewrite_var)
1606
0
{
1607
0
  char *name, *value;
1608
0
  size_t name_len, value_len;
1609
1610
0
  if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &name, &name_len, &value, &value_len) == FAILURE) {
1611
0
    RETURN_THROWS();
1612
0
  }
1613
1614
0
  if (php_url_scanner_add_var(name, name_len, value, value_len, 1) == SUCCESS) {
1615
0
    RETURN_TRUE;
1616
0
  } else {
1617
0
    RETURN_FALSE;
1618
0
  }
1619
0
}
1620
/* }}} */