Coverage Report

Created: 2026-06-13 07:01

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