Coverage Report

Created: 2023-12-13 20:07

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