Coverage Report

Created: 2026-06-02 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/main/streams/stream_errors.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: Jakub Zelenka <bukka@php.net>                               |
14
   +----------------------------------------------------------------------+
15
 */
16
17
#define ZEND_ENUM_StreamErrorCode_USE_NAME_TABLE
18
#include "php.h"
19
#include "php_globals.h"
20
#include "php_streams.h"
21
#include "php_stream_errors.h"
22
#include "zend_enum.h"
23
#include "zend_exceptions.h"
24
#include "ext/standard/file.h"
25
#include "stream_errors_arginfo.h"
26
27
/* Class entries */
28
static zend_class_entry *php_ce_stream_error_code;
29
static zend_class_entry *php_ce_stream_error_mode;
30
static zend_class_entry *php_ce_stream_error_store;
31
static zend_class_entry *php_ce_stream_error;
32
static zend_class_entry *php_ce_stream_exception;
33
34
/* Forward declarations */
35
static void php_stream_error_entry_free(php_stream_error_entry *entry);
36
37
/* Helper to create a single StreamError object from an entry */
38
static void php_stream_error_create_object(zval *zv, php_stream_error_entry *entry)
39
0
{
40
0
  object_init_ex(zv, php_ce_stream_error);
41
42
0
  const char *case_name = NULL;
43
0
  if (entry->code > 0 && entry->code <= ZEND_ENUM_StreamErrorCode_CASE_COUNT) {
44
0
    case_name = zend_enum_StreamErrorCode_case_names[entry->code];
45
0
  }
46
0
  if (!case_name) {
47
0
    case_name = "Generic";
48
0
  }
49
50
0
  zend_object *enum_obj = zend_enum_get_case_cstr(php_ce_stream_error_code, case_name);
51
0
  ZEND_ASSERT(enum_obj != NULL);
52
53
0
  zval code_enum;
54
0
  ZVAL_OBJ_COPY(&code_enum, enum_obj);
55
56
0
  zend_update_property(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("code"), &code_enum);
57
0
  zval_ptr_dtor(&code_enum);
58
59
0
  zend_update_property_str(
60
0
      php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("message"), entry->message);
61
62
0
  zend_update_property_string(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("wrapperName"),
63
0
      entry->wrapper_name ? entry->wrapper_name : "");
64
65
0
  zend_update_property_long(
66
0
      php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("severity"), entry->severity);
67
68
0
  zend_update_property_bool(
69
0
      php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("terminating"), entry->terminating);
70
71
0
  if (entry->param) {
72
0
    zend_update_property_string(
73
0
        php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("param"), entry->param);
74
0
  } else {
75
0
    zend_update_property_null(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("param"));
76
0
  }
77
0
}
78
79
/* Create array of StreamError objects from error chain */
80
PHPAPI void php_stream_error_create_array(zval *zv, php_stream_error_entry *first)
81
0
{
82
0
  array_init(zv);
83
84
0
  php_stream_error_entry *entry = first;
85
0
  while (entry) {
86
0
    zval error_obj;
87
0
    php_stream_error_create_object(&error_obj, entry);
88
0
    zend_hash_next_index_insert_new(Z_ARRVAL_P(zv), &error_obj);
89
0
    entry = entry->next;
90
0
  }
91
0
}
92
93
/* Context option helpers */
94
95
static int php_stream_auto_decide_error_store_mode(int error_mode)
96
0
{
97
0
  switch (error_mode) {
98
0
    case PHP_STREAM_ERROR_MODE_ERROR:
99
0
      return PHP_STREAM_ERROR_STORE_NONE;
100
0
    case PHP_STREAM_ERROR_MODE_EXCEPTION:
101
0
      return PHP_STREAM_ERROR_STORE_NON_TERM;
102
0
    case PHP_STREAM_ERROR_MODE_SILENT:
103
0
      return PHP_STREAM_ERROR_STORE_ALL;
104
0
    default:
105
0
      return PHP_STREAM_ERROR_STORE_NONE;
106
0
  }
107
0
}
108
109
static int php_stream_get_error_mode(php_stream_context *context)
110
0
{
111
0
  if (!context) {
112
0
    return PHP_STREAM_ERROR_MODE_ERROR;
113
0
  }
114
115
0
  zval *option = php_stream_context_get_option(context, "stream", "error_mode");
116
0
  if (!option) {
117
0
    return PHP_STREAM_ERROR_MODE_ERROR;
118
0
  }
119
120
0
  if (Z_TYPE_P(option) != IS_OBJECT
121
0
      || !instanceof_function(Z_OBJCE_P(option), php_ce_stream_error_mode)) {
122
0
    zend_type_error("stream context option 'error_mode' must be of type StreamErrorMode");
123
0
    return PHP_STREAM_ERROR_MODE_ERROR;
124
0
  }
125
126
0
  switch ((zend_enum_StreamErrorMode) zend_enum_fetch_case_id(Z_OBJ_P(option))) {
127
0
    case ZEND_ENUM_StreamErrorMode_Error:
128
0
      return PHP_STREAM_ERROR_MODE_ERROR;
129
0
    case ZEND_ENUM_StreamErrorMode_Exception:
130
0
      return PHP_STREAM_ERROR_MODE_EXCEPTION;
131
0
    case ZEND_ENUM_StreamErrorMode_Silent:
132
0
      return PHP_STREAM_ERROR_MODE_SILENT;
133
0
  }
134
135
0
  return PHP_STREAM_ERROR_MODE_ERROR;
136
0
}
137
138
static int php_stream_get_error_store_mode(php_stream_context *context, int error_mode)
139
0
{
140
0
  if (!context) {
141
0
    return php_stream_auto_decide_error_store_mode(error_mode);
142
0
  }
143
144
0
  zval *option = php_stream_context_get_option(context, "stream", "error_store");
145
0
  if (!option) {
146
0
    return php_stream_auto_decide_error_store_mode(error_mode);
147
0
  }
148
149
0
  if (Z_TYPE_P(option) != IS_OBJECT
150
0
      || !instanceof_function(Z_OBJCE_P(option), php_ce_stream_error_store)) {
151
0
    zend_type_error("stream context option 'error_store' must be of type StreamErrorStore");
152
0
    return php_stream_auto_decide_error_store_mode(error_mode);
153
0
  }
154
155
0
  switch ((zend_enum_StreamErrorStore) zend_enum_fetch_case_id(Z_OBJ_P(option))) {
156
0
    case ZEND_ENUM_StreamErrorStore_Auto:
157
0
      return php_stream_auto_decide_error_store_mode(error_mode);
158
0
    case ZEND_ENUM_StreamErrorStore_None:
159
0
      return PHP_STREAM_ERROR_STORE_NONE;
160
0
    case ZEND_ENUM_StreamErrorStore_NonTerminating:
161
0
      return PHP_STREAM_ERROR_STORE_NON_TERM;
162
0
    case ZEND_ENUM_StreamErrorStore_Terminating:
163
0
      return PHP_STREAM_ERROR_STORE_TERMINAL;
164
0
    case ZEND_ENUM_StreamErrorStore_All:
165
0
      return PHP_STREAM_ERROR_STORE_ALL;
166
0
  }
167
168
0
  return php_stream_auto_decide_error_store_mode(error_mode);
169
0
}
170
171
/* Helper functions */
172
173
static bool php_stream_has_terminating_error(php_stream_error_operation *op)
174
0
{
175
0
  php_stream_error_entry *entry = op->first_error;
176
0
  while (entry) {
177
0
    if (entry->terminating) {
178
0
      return true;
179
0
    }
180
0
    entry = entry->next;
181
0
  }
182
0
  return false;
183
0
}
184
185
static inline php_stream_error_operation *php_stream_get_operation_at_depth(uint32_t depth)
186
0
{
187
0
  php_stream_error_state *state = &FG(stream_error_state);
188
189
0
  if (depth < PHP_STREAM_ERROR_OPERATION_POOL_SIZE) {
190
0
    return &state->operation_pool[depth];
191
0
  } else {
192
0
    uint32_t overflow_index = depth - PHP_STREAM_ERROR_OPERATION_POOL_SIZE;
193
0
    ZEND_ASSERT(overflow_index < state->overflow_capacity);
194
0
    return &state->overflow_operations[overflow_index];
195
0
  }
196
0
}
197
198
static inline php_stream_error_operation *php_stream_get_parent_operation(void)
199
0
{
200
0
  php_stream_error_state *state = &FG(stream_error_state);
201
202
0
  if (state->operation_depth <= 1) {
203
0
    return NULL;
204
0
  }
205
206
0
  return php_stream_get_operation_at_depth(state->operation_depth - 2);
207
0
}
208
209
/* Clean up functions */
210
211
static void php_stream_error_entry_free(php_stream_error_entry *entry)
212
0
{
213
0
  while (entry) {
214
0
    php_stream_error_entry *next = entry->next;
215
0
    zend_string_release(entry->message);
216
0
    efree(entry->wrapper_name);
217
0
    efree(entry->param);
218
0
    efree(entry);
219
0
    entry = next;
220
0
  }
221
0
}
222
223
PHPAPI void php_stream_error_state_cleanup(void)
224
1.99k
{
225
1.99k
  php_stream_error_state *state = &FG(stream_error_state);
226
227
1.99k
  while (state->current_operation) {
228
0
    php_stream_error_operation *op = state->current_operation;
229
0
    state->operation_depth--;
230
0
    state->current_operation = php_stream_get_parent_operation();
231
232
0
    php_stream_error_entry_free(op->first_error);
233
234
0
    op->first_error = NULL;
235
0
    op->last_error = NULL;
236
0
    op->error_count = 0;
237
0
  }
238
239
1.99k
  php_stream_stored_error *stored = state->stored_errors;
240
1.99k
  while (stored) {
241
0
    php_stream_stored_error *next = stored->next;
242
0
    php_stream_error_entry_free(stored->first_error);
243
0
    efree(stored);
244
0
    stored = next;
245
0
  }
246
247
1.99k
  state->stored_errors = NULL;
248
1.99k
  state->stored_count = 0;
249
1.99k
  state->operation_depth = 0;
250
251
1.99k
  if (state->overflow_operations) {
252
0
    efree(state->overflow_operations);
253
0
    state->overflow_operations = NULL;
254
0
    state->overflow_capacity = 0;
255
0
  }
256
1.99k
}
257
258
PHPAPI void php_stream_error_get_last(zval *return_value)
259
0
{
260
0
  php_stream_error_state *state = &FG(stream_error_state);
261
262
0
  if (!state->stored_errors) {
263
0
    ZVAL_EMPTY_ARRAY(return_value);
264
0
    return;
265
0
  }
266
267
0
  php_stream_error_create_array(return_value, state->stored_errors->first_error);
268
0
}
269
270
PHPAPI void php_stream_error_clear_stored(void)
271
0
{
272
0
  php_stream_error_state *state = &FG(stream_error_state);
273
274
0
  php_stream_stored_error *stored = state->stored_errors;
275
0
  while (stored) {
276
0
    php_stream_stored_error *next = stored->next;
277
0
    php_stream_error_entry_free(stored->first_error);
278
0
    efree(stored);
279
0
    stored = next;
280
0
  }
281
282
0
  state->stored_errors = NULL;
283
0
  state->stored_count = 0;
284
0
}
285
286
/* Error operation stack management */
287
288
PHPAPI php_stream_error_operation *php_stream_error_operation_begin(void)
289
0
{
290
0
  php_stream_error_state *state = &FG(stream_error_state);
291
292
0
  if (state->operation_depth >= PHP_STREAM_ERROR_MAX_DEPTH) {
293
0
    php_error_docref(NULL, E_WARNING,
294
0
        "Stream error operation depth exceeded (%u), possible infinite recursion",
295
0
        state->operation_depth);
296
0
    return NULL;
297
0
  }
298
299
0
  php_stream_error_operation *op;
300
301
0
  if (state->operation_depth < PHP_STREAM_ERROR_OPERATION_POOL_SIZE) {
302
0
    op = &state->operation_pool[state->operation_depth];
303
0
  } else {
304
0
    uint32_t overflow_index = state->operation_depth - PHP_STREAM_ERROR_OPERATION_POOL_SIZE;
305
306
0
    if (overflow_index >= state->overflow_capacity) {
307
0
      uint32_t new_capacity
308
0
          = state->overflow_capacity == 0 ? 8 : state->overflow_capacity * 2;
309
0
      php_stream_error_operation *new_overflow = erealloc(
310
0
          state->overflow_operations, sizeof(php_stream_error_operation) * new_capacity);
311
0
      state->overflow_operations = new_overflow;
312
0
      state->overflow_capacity = new_capacity;
313
0
    }
314
315
0
    op = &state->overflow_operations[overflow_index];
316
0
  }
317
318
0
  op->first_error = NULL;
319
0
  op->last_error = NULL;
320
0
  op->error_count = 0;
321
322
0
  state->current_operation = op;
323
0
  state->operation_depth++;
324
325
0
  return op;
326
0
}
327
328
static void php_stream_error_add(zend_enum_StreamErrorCode code, const char *wrapper_name,
329
    zend_string *message, const char *docref, char *param, int severity, bool terminating)
330
0
{
331
0
  php_stream_error_operation *op = FG(stream_error_state).current_operation;
332
0
  ZEND_ASSERT(op != NULL);
333
334
0
  php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry));
335
0
  entry->message = message;
336
0
  entry->code = code;
337
0
  entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL;
338
0
  entry->param = param;
339
0
  entry->docref = docref ? estrdup(docref) : NULL;
340
0
  entry->severity = severity;
341
0
  entry->terminating = terminating;
342
0
  entry->next = NULL;
343
344
0
  if (op->last_error) {
345
0
    op->last_error->next = entry;
346
0
  } else {
347
0
    op->first_error = entry;
348
0
  }
349
0
  op->last_error = entry;
350
0
  op->error_count++;
351
0
}
352
353
/* Error reporting */
354
355
static void php_stream_call_error_handler(zval *handler, zval *errors_array)
356
0
{
357
0
  zend_fcall_info_cache fcc;
358
0
  char *is_callable_error = NULL;
359
360
0
  if (!zend_is_callable_ex(handler, NULL, 0, NULL, &fcc, &is_callable_error)) {
361
0
    if (is_callable_error) {
362
0
      zend_type_error("stream error handler must be a valid callback, %s", is_callable_error);
363
0
      efree(is_callable_error);
364
0
    }
365
0
    return;
366
0
  }
367
368
0
  zend_call_known_fcc(&fcc, NULL, 1, errors_array, NULL);
369
0
}
370
371
static void php_stream_throw_exception_with_errors(php_stream_error_operation *op)
372
0
{
373
0
  if (!op->first_error) {
374
0
    return;
375
0
  }
376
377
0
  zval ex;
378
0
  object_init_ex(&ex, php_ce_stream_exception);
379
380
  /* Set message from first error */
381
0
  zend_update_property_string(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("message"),
382
0
      ZSTR_VAL(op->first_error->message));
383
384
  /* Set code from first error */
385
0
  zend_update_property_long(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("code"),
386
0
      (zend_long) op->first_error->code);
387
388
  /* Build errors array and set it */
389
0
  zval errors_array;
390
0
  php_stream_error_create_array(&errors_array, op->first_error);
391
0
  zend_update_property(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("errors"), &errors_array);
392
0
  zval_ptr_dtor(&errors_array);
393
394
0
  zend_throw_exception_object(&ex);
395
0
}
396
397
static void php_stream_report_errors(php_stream_context *context, php_stream_error_operation *op,
398
    int error_mode, bool is_terminating)
399
0
{
400
0
  switch (error_mode) {
401
0
    case PHP_STREAM_ERROR_MODE_ERROR: {
402
0
      php_stream_error_entry *entry = op->first_error;
403
0
      while (entry) {
404
0
        if (entry->param) {
405
0
          php_error_docref1(entry->docref, entry->param, entry->severity, "%s",
406
0
              ZSTR_VAL(entry->message));
407
0
        } else {
408
0
          php_error_docref(
409
0
              entry->docref, entry->severity, "%s", ZSTR_VAL(entry->message));
410
0
        }
411
0
        entry = entry->next;
412
0
      }
413
0
      break;
414
0
    }
415
416
0
    case PHP_STREAM_ERROR_MODE_EXCEPTION: {
417
0
      if (is_terminating) {
418
0
        php_stream_throw_exception_with_errors(op);
419
0
      }
420
0
      break;
421
0
    }
422
423
0
    case PHP_STREAM_ERROR_MODE_SILENT:
424
0
      break;
425
0
  }
426
427
  /* Call user error handler if set */
428
0
  zval *handler
429
0
      = context ? php_stream_context_get_option(context, "stream", "error_handler") : NULL;
430
431
0
  if (handler) {
432
0
    zval errors_array;
433
0
    php_stream_error_create_array(&errors_array, op->first_error);
434
435
0
    php_stream_call_error_handler(handler, &errors_array);
436
437
0
    zval_ptr_dtor(&errors_array);
438
0
  }
439
0
}
440
441
/* Error storage */
442
443
PHPAPI void php_stream_error_operation_end(php_stream_context *context)
444
0
{
445
0
  php_stream_error_state *state = &FG(stream_error_state);
446
0
  php_stream_error_operation *op = state->current_operation;
447
448
0
  if (!op) {
449
0
    return;
450
0
  }
451
452
0
  state->operation_depth--;
453
0
  state->current_operation = php_stream_get_parent_operation();
454
455
0
  if (op->error_count > 0) {
456
0
    if (context == NULL) {
457
0
      context = FG(default_context);
458
0
    }
459
460
0
    int error_mode = php_stream_get_error_mode(context);
461
0
    int store_mode = php_stream_get_error_store_mode(context, error_mode);
462
463
0
    bool is_terminating = php_stream_has_terminating_error(op);
464
465
0
    php_stream_report_errors(context, op, error_mode, is_terminating);
466
467
0
    if (store_mode == PHP_STREAM_ERROR_STORE_NONE) {
468
0
      php_stream_error_entry_free(op->first_error);
469
0
      op->first_error = NULL;
470
0
    } else {
471
0
      php_stream_error_entry *entry = op->first_error;
472
0
      php_stream_error_entry *prev = NULL;
473
0
      php_stream_error_entry *to_store_first = NULL;
474
0
      php_stream_error_entry *to_store_last = NULL;
475
0
      uint32_t to_store_count = 0;
476
0
      php_stream_error_entry *remaining_first = NULL;
477
478
0
      while (entry) {
479
0
        php_stream_error_entry *next = entry->next;
480
0
        bool should_store = false;
481
482
0
        if (store_mode == PHP_STREAM_ERROR_STORE_ALL) {
483
0
          should_store = true;
484
0
        } else if (store_mode == PHP_STREAM_ERROR_STORE_NON_TERM && !entry->terminating) {
485
0
          should_store = true;
486
0
        } else if (store_mode == PHP_STREAM_ERROR_STORE_TERMINAL && entry->terminating) {
487
0
          should_store = true;
488
0
        }
489
490
0
        if (should_store) {
491
0
          entry->next = NULL;
492
0
          if (to_store_last) {
493
0
            to_store_last->next = entry;
494
0
          } else {
495
0
            to_store_first = entry;
496
0
          }
497
0
          to_store_last = entry;
498
0
          to_store_count++;
499
0
        } else {
500
0
          entry->next = NULL;
501
0
          if (prev) {
502
0
            prev->next = entry;
503
0
          } else {
504
0
            remaining_first = entry;
505
0
          }
506
0
          prev = entry;
507
0
        }
508
509
0
        entry = next;
510
0
      }
511
512
0
      if (to_store_first) {
513
0
        php_stream_stored_error *stored = emalloc(sizeof(php_stream_stored_error));
514
0
        stored->first_error = to_store_first;
515
0
        stored->error_count = to_store_count;
516
0
        stored->next = state->stored_errors;
517
518
0
        state->stored_errors = stored;
519
0
        state->stored_count++;
520
0
      }
521
522
0
      if (remaining_first) {
523
0
        php_stream_error_entry_free(remaining_first);
524
0
      }
525
526
0
      op->first_error = NULL;
527
0
    }
528
0
  }
529
530
0
  op->first_error = NULL;
531
0
  op->last_error = NULL;
532
0
  op->error_count = 0;
533
0
}
534
535
PHPAPI void php_stream_error_operation_end_for_stream(php_stream *stream)
536
0
{
537
0
  php_stream_error_state *state = &FG(stream_error_state);
538
0
  php_stream_error_operation *op = state->current_operation;
539
540
0
  if (!op) {
541
0
    return;
542
0
  }
543
544
0
  if (op->error_count == 0) {
545
0
    state->operation_depth--;
546
0
    state->current_operation = php_stream_get_parent_operation();
547
548
0
    op->first_error = NULL;
549
0
    op->last_error = NULL;
550
0
    return;
551
0
  }
552
553
0
  php_stream_context *context = PHP_STREAM_CONTEXT(stream);
554
0
  php_stream_error_operation_end(context);
555
0
}
556
557
PHPAPI void php_stream_error_operation_abort(void)
558
0
{
559
0
  php_stream_error_state *state = &FG(stream_error_state);
560
0
  php_stream_error_operation *op = state->current_operation;
561
562
0
  if (!op) {
563
0
    return;
564
0
  }
565
566
0
  state->operation_depth--;
567
0
  state->current_operation = php_stream_get_parent_operation();
568
569
0
  php_stream_error_entry_free(op->first_error);
570
0
  op->first_error = NULL;
571
0
  op->last_error = NULL;
572
0
  op->error_count = 0;
573
0
}
574
575
/* Wrapper error reporting */
576
577
static void php_stream_wrapper_error_internal(const char *wrapper_name, php_stream_context *context,
578
    const char *docref, int options, int severity, bool terminating,
579
    zend_enum_StreamErrorCode code, char *param, zend_string *message)
580
0
{
581
0
  bool implicit_operation = (FG(stream_error_state).current_operation == NULL);
582
0
  if (implicit_operation) {
583
0
    php_stream_error_operation_begin();
584
0
  }
585
586
0
  php_stream_error_add(code, wrapper_name, message, docref, param, severity, terminating);
587
588
0
  if (implicit_operation) {
589
0
    php_stream_error_operation_end(context);
590
0
  }
591
0
}
592
593
PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name,
594
    php_stream_context *context, const char *docref, int options, int severity,
595
    bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...)
596
0
{
597
0
  if (!(options & REPORT_ERRORS)) {
598
0
    return;
599
0
  }
600
601
0
  va_list args;
602
0
  va_start(args, fmt);
603
0
  zend_string *message = vstrpprintf(0, fmt, args);
604
0
  va_end(args);
605
606
0
  php_stream_wrapper_error_internal(
607
0
      wrapper_name, context, docref, options, severity, terminating, code, NULL, message);
608
0
}
609
610
PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_context *context,
611
    const char *docref, int options, int severity, bool terminating,
612
    zend_enum_StreamErrorCode code, const char *fmt, ...)
613
0
{
614
0
  if (!(options & REPORT_ERRORS)) {
615
0
    return;
616
0
  }
617
618
0
  va_list args;
619
0
  va_start(args, fmt);
620
0
  zend_string *message = vstrpprintf(0, fmt, args);
621
0
  va_end(args);
622
623
0
  const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
624
625
0
  php_stream_wrapper_error_internal(
626
0
      wrapper_name, context, docref, options, severity, terminating, code, NULL, message);
627
0
}
628
629
PHPAPI void php_stream_wrapper_error_param(php_stream_wrapper *wrapper, php_stream_context *context,
630
    const char *docref, int options, int severity, bool terminating,
631
    zend_enum_StreamErrorCode code, const char *param, const char *fmt, ...)
632
0
{
633
0
  if (!(options & REPORT_ERRORS)) {
634
0
    return;
635
0
  }
636
637
0
  va_list args;
638
0
  va_start(args, fmt);
639
0
  zend_string *message = vstrpprintf(0, fmt, args);
640
0
  va_end(args);
641
642
0
  const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
643
0
  char *param_copy = param ? estrdup(param) : NULL;
644
645
0
  php_stream_wrapper_error_internal(wrapper_name, context, docref, options, severity, terminating,
646
0
      code, param_copy, message);
647
0
}
648
649
PHPAPI void php_stream_wrapper_error_param2(php_stream_wrapper *wrapper,
650
    php_stream_context *context, const char *docref, int options, int severity,
651
    bool terminating, zend_enum_StreamErrorCode code, const char *param1, const char *param2,
652
    const char *fmt, ...)
653
0
{
654
0
  if (!(options & REPORT_ERRORS)) {
655
0
    return;
656
0
  }
657
658
0
  char *combined_param;
659
0
  spprintf(&combined_param, 0, "%s,%s", param1, param2);
660
661
0
  va_list args;
662
0
  va_start(args, fmt);
663
0
  zend_string *message = vstrpprintf(0, fmt, args);
664
0
  va_end(args);
665
666
0
  const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
667
668
0
  php_stream_wrapper_error_internal(wrapper_name, context, docref, options, severity, terminating,
669
0
      code, combined_param, message);
670
0
}
671
672
/* Stream error reporting */
673
674
PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severity,
675
    bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...)
676
0
{
677
0
  va_list args;
678
0
  va_start(args, fmt);
679
680
0
  zend_string *message = vstrpprintf(0, fmt, args);
681
0
  va_end(args);
682
683
0
  const char *wrapper_name = stream->wrapper ? stream->wrapper->wops->label : "stream";
684
685
0
  php_stream_context *context = PHP_STREAM_CONTEXT(stream);
686
687
0
  php_stream_wrapper_error_internal(wrapper_name, context, docref, REPORT_ERRORS, severity,
688
0
      terminating, code, NULL, message);
689
0
}
690
691
/* Legacy wrapper error logging */
692
693
static void php_stream_error_entry_dtor_legacy(void *error)
694
0
{
695
0
  php_stream_error_entry *entry = *(php_stream_error_entry **) error;
696
0
  zend_string_release(entry->message);
697
0
  efree(entry->wrapper_name);
698
0
  efree(entry->param);
699
0
  efree(entry->docref);
700
0
  efree(entry);
701
0
}
702
703
static void php_stream_error_list_dtor(zval *item)
704
0
{
705
0
  zend_llist *list = (zend_llist *) Z_PTR_P(item);
706
0
  zend_llist_destroy(list);
707
0
  efree(list);
708
0
}
709
710
static void php_stream_wrapper_log_store_error(zend_string *message, zend_enum_StreamErrorCode code,
711
    const char *wrapper_name, const char *param, int severity, bool terminating)
712
0
{
713
0
  char *param_copy = param ? estrdup(param) : NULL;
714
715
0
  php_stream_error_entry *entry = ecalloc(1, sizeof(php_stream_error_entry));
716
0
  entry->message = message;
717
0
  entry->code = code;
718
0
  entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL;
719
0
  entry->param = param_copy;
720
0
  entry->severity = severity;
721
0
  entry->terminating = terminating;
722
723
0
  if (!FG(wrapper_logged_errors)) {
724
0
    ALLOC_HASHTABLE(FG(wrapper_logged_errors));
725
0
    zend_hash_init(FG(wrapper_logged_errors), 8, NULL, php_stream_error_list_dtor, 0);
726
0
  }
727
728
0
  zend_llist *list
729
0
      = zend_hash_str_find_ptr(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
730
731
0
  if (!list) {
732
0
    zend_llist new_list;
733
0
    zend_llist_init(
734
0
        &new_list, sizeof(php_stream_error_entry *), php_stream_error_entry_dtor_legacy, 0);
735
0
    list = zend_hash_str_update_mem(FG(wrapper_logged_errors), wrapper_name,
736
0
        strlen(wrapper_name), &new_list, sizeof(new_list));
737
0
  }
738
739
0
  zend_llist_add_element(list, &entry);
740
0
}
741
742
static void php_stream_wrapper_log_error_internal(const php_stream_wrapper *wrapper,
743
    php_stream_context *context, int options, int severity, bool terminating,
744
    zend_enum_StreamErrorCode code, char *param, const char *fmt, va_list args)
745
0
{
746
0
  zend_string *message = vstrpprintf(0, fmt, args);
747
0
  const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
748
749
0
  if (options & REPORT_ERRORS) {
750
0
    php_stream_wrapper_error_internal(
751
0
        wrapper_name, context, NULL, options, severity, terminating, code, param, message);
752
0
  } else {
753
0
    php_stream_wrapper_log_store_error(
754
0
        message, code, wrapper_name, param, severity, terminating);
755
0
  }
756
0
}
757
758
PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper,
759
    php_stream_context *context, int options, int severity, bool terminating,
760
    zend_enum_StreamErrorCode code, const char *fmt, ...)
761
0
{
762
0
  va_list args;
763
0
  va_start(args, fmt);
764
0
  php_stream_wrapper_log_error_internal(
765
0
      wrapper, context, options, severity, terminating, code, NULL, fmt, args);
766
0
  va_end(args);
767
0
}
768
769
PHPAPI void php_stream_wrapper_log_error_param(const php_stream_wrapper *wrapper,
770
    php_stream_context *context, int options, int severity, bool terminating,
771
    zend_enum_StreamErrorCode code, const char *param, const char *fmt, ...)
772
0
{
773
0
  va_list args;
774
0
  va_start(args, fmt);
775
0
  char *param_copy = param ? estrdup(param) : NULL;
776
0
  php_stream_wrapper_log_error_internal(
777
0
      wrapper, context, options, severity, terminating, code, param_copy, fmt, args);
778
0
  va_end(args);
779
0
}
780
781
static zend_llist *php_stream_get_wrapper_errors_list(const char *wrapper_name)
782
0
{
783
0
  if (!FG(wrapper_logged_errors)) {
784
0
    return NULL;
785
0
  }
786
0
  return (zend_llist *) zend_hash_str_find_ptr(
787
0
      FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
788
0
}
789
790
PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name,
791
    php_stream_context *context, zend_enum_StreamErrorCode code, const char *path,
792
    const char *caption)
793
0
{
794
0
  char *msg;
795
0
  char errstr[256];
796
0
  int free_msg = 0;
797
798
0
  if (EG(exception)) {
799
0
    return;
800
0
  }
801
802
0
  char *tmp = estrdup(path);
803
0
  if (strcmp(wrapper_name, PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME)) {
804
0
    zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name);
805
0
    if (err_list) {
806
0
      size_t l = 0;
807
0
      int brlen;
808
0
      int i;
809
0
      int count = (int) zend_llist_count(err_list);
810
0
      const char *br;
811
0
      php_stream_error_entry **err_entry_p;
812
0
      zend_llist_position pos;
813
814
0
      if (PG(html_errors)) {
815
0
        brlen = 7;
816
0
        br = "<br />\n";
817
0
      } else {
818
0
        brlen = 1;
819
0
        br = "\n";
820
0
      }
821
822
0
      for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p;
823
0
          err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) {
824
0
        l += ZSTR_LEN((*err_entry_p)->message);
825
0
        if (i < count - 1) {
826
0
          l += brlen;
827
0
        }
828
0
      }
829
0
      msg = emalloc(l + 1);
830
0
      msg[0] = '\0';
831
0
      for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p;
832
0
          err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) {
833
0
        strcat(msg, ZSTR_VAL((*err_entry_p)->message));
834
0
        if (i < count - 1) {
835
0
          strcat(msg, br);
836
0
        }
837
0
      }
838
839
0
      free_msg = 1;
840
0
    } else {
841
0
      if (!strcmp(wrapper_name, php_plain_files_wrapper.wops->label)) {
842
0
        msg = php_socket_strerror_s(errno, errstr, sizeof(errstr));
843
0
      } else {
844
0
        msg = "operation failed";
845
0
      }
846
0
    }
847
0
  } else {
848
0
    msg = "no suitable wrapper could be found";
849
0
  }
850
851
0
  php_strip_url_passwd(tmp);
852
853
0
  zend_string *message = strpprintf(0, "%s: %s", caption, msg);
854
855
0
  php_stream_wrapper_error_internal(wrapper_name, context, NULL, REPORT_ERRORS, E_WARNING, true,
856
0
      code, tmp, message);
857
858
0
  if (free_msg) {
859
0
    efree(msg);
860
0
  }
861
0
}
862
863
PHPAPI void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper,
864
    php_stream_context *context, zend_enum_StreamErrorCode code, const char *path,
865
    const char *caption)
866
0
{
867
0
  if (wrapper) {
868
0
    const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
869
0
    php_stream_display_wrapper_name_errors(wrapper_name, context, code, path, caption);
870
0
  }
871
0
}
872
873
PHPAPI void php_stream_tidy_wrapper_name_error_log(const char *wrapper_name)
874
0
{
875
0
  if (FG(wrapper_logged_errors)) {
876
0
    zend_hash_str_del(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
877
0
  }
878
0
}
879
880
PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper)
881
0
{
882
0
  if (wrapper) {
883
0
    const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
884
0
    php_stream_tidy_wrapper_name_error_log(wrapper_name);
885
0
  }
886
0
}
887
888
/* StreamException methods */
889
890
PHP_METHOD(StreamException, getErrors)
891
0
{
892
0
  ZEND_PARSE_PARAMETERS_NONE();
893
894
0
  zval *errors = zend_read_property(
895
0
      php_ce_stream_exception, Z_OBJ_P(ZEND_THIS), ZEND_STRL("errors"), 1, NULL);
896
897
0
  RETURN_COPY(errors);
898
0
}
899
900
/* Module init */
901
902
PHP_MINIT_FUNCTION(stream_errors)
903
2
{
904
2
  php_ce_stream_error_code = register_class_StreamErrorCode();
905
2
  php_ce_stream_error_mode = register_class_StreamErrorMode();
906
2
  php_ce_stream_error_store = register_class_StreamErrorStore();
907
908
2
  php_ce_stream_error = register_class_StreamError();
909
2
  php_ce_stream_exception = register_class_StreamException(zend_ce_exception);
910
911
2
  return SUCCESS;
912
2
}
913
914
PHP_MSHUTDOWN_FUNCTION(stream_errors)
915
0
{
916
0
  return SUCCESS;
917
0
}