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
721
{
97
721
  switch (error_mode) {
98
721
    case PHP_STREAM_ERROR_MODE_ERROR:
99
721
      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
721
  }
107
721
}
108
109
static int php_stream_get_error_mode(php_stream_context *context)
110
721
{
111
721
  if (!context) {
112
720
    return PHP_STREAM_ERROR_MODE_ERROR;
113
720
  }
114
115
1
  zval *option = php_stream_context_get_option(context, "stream", "error_mode");
116
1
  if (!option) {
117
1
    return PHP_STREAM_ERROR_MODE_ERROR;
118
1
  }
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
721
{
140
721
  if (!context) {
141
720
    return php_stream_auto_decide_error_store_mode(error_mode);
142
720
  }
143
144
1
  zval *option = php_stream_context_get_option(context, "stream", "error_store");
145
1
  if (!option) {
146
1
    return php_stream_auto_decide_error_store_mode(error_mode);
147
1
  }
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
721
{
175
721
  php_stream_error_entry *entry = op->first_error;
176
721
  while (entry) {
177
721
    if (entry->terminating) {
178
721
      return true;
179
721
    }
180
0
    entry = entry->next;
181
0
  }
182
0
  return false;
183
721
}
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
722
{
200
722
  php_stream_error_state *state = &FG(stream_error_state);
201
202
722
  if (state->operation_depth <= 1) {
203
722
    return NULL;
204
722
  }
205
206
0
  return php_stream_get_operation_at_depth(state->operation_depth - 2);
207
722
}
208
209
/* Clean up functions */
210
211
static void php_stream_error_entry_free(php_stream_error_entry *entry)
212
721
{
213
1.44k
  while (entry) {
214
721
    php_stream_error_entry *next = entry->next;
215
721
    zend_string_release(entry->message);
216
721
    efree(entry->wrapper_name);
217
721
    efree(entry->param);
218
721
    efree(entry);
219
721
    entry = next;
220
721
  }
221
721
}
222
223
PHPAPI void php_stream_error_state_cleanup(void)
224
33.5k
{
225
33.5k
  php_stream_error_state *state = &FG(stream_error_state);
226
227
33.5k
  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
33.5k
  php_stream_stored_error *stored = state->stored_errors;
240
33.5k
  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
33.5k
  state->stored_errors = NULL;
248
33.5k
  state->stored_count = 0;
249
33.5k
  state->operation_depth = 0;
250
251
33.5k
  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
33.5k
}
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
722
{
290
722
  php_stream_error_state *state = &FG(stream_error_state);
291
292
722
  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
722
  php_stream_error_operation *op;
300
301
722
  if (state->operation_depth < PHP_STREAM_ERROR_OPERATION_POOL_SIZE) {
302
722
    op = &state->operation_pool[state->operation_depth];
303
722
  } 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
722
  op->first_error = NULL;
319
722
  op->last_error = NULL;
320
722
  op->error_count = 0;
321
322
722
  state->current_operation = op;
323
722
  state->operation_depth++;
324
325
722
  return op;
326
722
}
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
721
{
331
721
  php_stream_error_operation *op = FG(stream_error_state).current_operation;
332
721
  ZEND_ASSERT(op != NULL);
333
334
721
  php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry));
335
721
  entry->message = message;
336
721
  entry->code = code;
337
721
  entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL;
338
721
  entry->param = param;
339
721
  entry->docref = docref ? estrdup(docref) : NULL;
340
721
  entry->severity = severity;
341
721
  entry->terminating = terminating;
342
721
  entry->next = NULL;
343
344
721
  if (op->last_error) {
345
0
    op->last_error->next = entry;
346
721
  } else {
347
721
    op->first_error = entry;
348
721
  }
349
721
  op->last_error = entry;
350
721
  op->error_count++;
351
721
}
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
721
{
400
721
  switch (error_mode) {
401
721
    case PHP_STREAM_ERROR_MODE_ERROR: {
402
721
      php_stream_error_entry *entry = op->first_error;
403
1.44k
      while (entry) {
404
721
        if (entry->param) {
405
719
          php_error_docref1(entry->docref, entry->param, entry->severity, "%s",
406
719
              ZSTR_VAL(entry->message));
407
719
        } else {
408
2
          php_error_docref(
409
2
              entry->docref, entry->severity, "%s", ZSTR_VAL(entry->message));
410
2
        }
411
721
        entry = entry->next;
412
721
      }
413
721
      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
721
  }
426
427
  /* Call user error handler if set */
428
721
  zval *handler
429
721
      = context ? php_stream_context_get_option(context, "stream", "error_handler") : NULL;
430
431
721
  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
721
}
440
441
/* Error storage */
442
443
PHPAPI void php_stream_error_operation_end(php_stream_context *context)
444
722
{
445
722
  php_stream_error_state *state = &FG(stream_error_state);
446
722
  php_stream_error_operation *op = state->current_operation;
447
448
722
  if (!op) {
449
0
    return;
450
0
  }
451
452
722
  state->operation_depth--;
453
722
  state->current_operation = php_stream_get_parent_operation();
454
455
722
  if (op->error_count > 0) {
456
721
    if (context == NULL) {
457
720
      context = FG(default_context);
458
720
    }
459
460
721
    int error_mode = php_stream_get_error_mode(context);
461
721
    int store_mode = php_stream_get_error_store_mode(context, error_mode);
462
463
721
    bool is_terminating = php_stream_has_terminating_error(op);
464
465
721
    php_stream_report_errors(context, op, error_mode, is_terminating);
466
467
721
    if (store_mode == PHP_STREAM_ERROR_STORE_NONE) {
468
721
      php_stream_error_entry_free(op->first_error);
469
721
      op->first_error = NULL;
470
721
    } 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
721
  }
529
530
722
  op->first_error = NULL;
531
722
  op->last_error = NULL;
532
722
  op->error_count = 0;
533
722
}
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
721
{
581
721
  bool implicit_operation = (FG(stream_error_state).current_operation == NULL);
582
721
  if (implicit_operation) {
583
721
    php_stream_error_operation_begin();
584
721
  }
585
586
721
  php_stream_error_add(code, wrapper_name, message, docref, param, severity, terminating);
587
588
721
  if (implicit_operation) {
589
721
    php_stream_error_operation_end(context);
590
721
  }
591
721
}
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
3
{
614
3
  if (!(options & REPORT_ERRORS)) {
615
1
    return;
616
1
  }
617
618
2
  va_list args;
619
2
  va_start(args, fmt);
620
2
  zend_string *message = vstrpprintf(0, fmt, args);
621
2
  va_end(args);
622
623
2
  const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
624
625
2
  php_stream_wrapper_error_internal(
626
2
      wrapper_name, context, docref, options, severity, terminating, code, NULL, message);
627
2
}
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
662
{
695
662
  php_stream_error_entry *entry = *(php_stream_error_entry **) error;
696
662
  zend_string_release(entry->message);
697
662
  efree(entry->wrapper_name);
698
662
  efree(entry->param);
699
662
  efree(entry->docref);
700
662
  efree(entry);
701
662
}
702
703
static void php_stream_error_list_dtor(zval *item)
704
662
{
705
662
  zend_llist *list = (zend_llist *) Z_PTR_P(item);
706
662
  zend_llist_destroy(list);
707
662
  efree(list);
708
662
}
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
662
{
713
662
  char *param_copy = param ? estrdup(param) : NULL;
714
715
662
  php_stream_error_entry *entry = ecalloc(1, sizeof(php_stream_error_entry));
716
662
  entry->message = message;
717
662
  entry->code = code;
718
662
  entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL;
719
662
  entry->param = param_copy;
720
662
  entry->severity = severity;
721
662
  entry->terminating = terminating;
722
723
662
  if (!FG(wrapper_logged_errors)) {
724
2
    ALLOC_HASHTABLE(FG(wrapper_logged_errors));
725
2
    zend_hash_init(FG(wrapper_logged_errors), 8, NULL, php_stream_error_list_dtor, 0);
726
2
  }
727
728
662
  zend_llist *list
729
662
      = zend_hash_str_find_ptr(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
730
731
662
  if (!list) {
732
662
    zend_llist new_list;
733
662
    zend_llist_init(
734
662
        &new_list, sizeof(php_stream_error_entry *), php_stream_error_entry_dtor_legacy, 0);
735
662
    list = zend_hash_str_update_mem(FG(wrapper_logged_errors), wrapper_name,
736
662
        strlen(wrapper_name), &new_list, sizeof(new_list));
737
662
  }
738
739
662
  zend_llist_add_element(list, &entry);
740
662
}
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
662
{
746
662
  zend_string *message = vstrpprintf(0, fmt, args);
747
662
  const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
748
749
662
  if (options & REPORT_ERRORS) {
750
0
    php_stream_wrapper_error_internal(
751
0
        wrapper_name, context, NULL, options, severity, terminating, code, param, message);
752
662
  } else {
753
662
    php_stream_wrapper_log_store_error(
754
662
        message, code, wrapper_name, param, severity, terminating);
755
662
  }
756
662
}
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
662
{
762
662
  va_list args;
763
662
  va_start(args, fmt);
764
662
  php_stream_wrapper_log_error_internal(
765
662
      wrapper, context, options, severity, terminating, code, NULL, fmt, args);
766
662
  va_end(args);
767
662
}
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
719
{
783
719
  if (!FG(wrapper_logged_errors)) {
784
57
    return NULL;
785
57
  }
786
662
  return (zend_llist *) zend_hash_str_find_ptr(
787
662
      FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
788
719
}
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
721
{
794
721
  char *msg;
795
721
  char errstr[256];
796
721
  int free_msg = 0;
797
798
721
  if (EG(exception)) {
799
2
    return;
800
2
  }
801
802
719
  char *tmp = estrdup(path);
803
719
  if (strcmp(wrapper_name, PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME)) {
804
719
    zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name);
805
719
    if (err_list) {
806
662
      size_t l = 0;
807
662
      int brlen;
808
662
      int i;
809
662
      int count = (int) zend_llist_count(err_list);
810
662
      const char *br;
811
662
      php_stream_error_entry **err_entry_p;
812
662
      zend_llist_position pos;
813
814
662
      if (PG(html_errors)) {
815
0
        brlen = 7;
816
0
        br = "<br />\n";
817
662
      } else {
818
662
        brlen = 1;
819
662
        br = "\n";
820
662
      }
821
822
1.32k
      for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p;
823
662
          err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) {
824
662
        l += ZSTR_LEN((*err_entry_p)->message);
825
662
        if (i < count - 1) {
826
0
          l += brlen;
827
0
        }
828
662
      }
829
662
      msg = emalloc(l + 1);
830
662
      msg[0] = '\0';
831
1.32k
      for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p;
832
662
          err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) {
833
662
        strcat(msg, ZSTR_VAL((*err_entry_p)->message));
834
662
        if (i < count - 1) {
835
0
          strcat(msg, br);
836
0
        }
837
662
      }
838
839
662
      free_msg = 1;
840
662
    } else {
841
57
      if (!strcmp(wrapper_name, php_plain_files_wrapper.wops->label)) {
842
56
        msg = php_socket_strerror_s(errno, errstr, sizeof(errstr));
843
56
      } else {
844
1
        msg = "operation failed";
845
1
      }
846
57
    }
847
719
  } else {
848
0
    msg = "no suitable wrapper could be found";
849
0
  }
850
851
719
  php_strip_url_passwd(tmp);
852
853
719
  zend_string *message = strpprintf(0, "%s: %s", caption, msg);
854
855
719
  php_stream_wrapper_error_internal(wrapper_name, context, NULL, REPORT_ERRORS, E_WARNING, true,
856
719
      code, tmp, message);
857
858
719
  if (free_msg) {
859
662
    efree(msg);
860
662
  }
861
719
}
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
3
{
867
3
  if (wrapper) {
868
3
    const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
869
3
    php_stream_display_wrapper_name_errors(wrapper_name, context, code, path, caption);
870
3
  }
871
3
}
872
873
PHPAPI void php_stream_tidy_wrapper_name_error_log(const char *wrapper_name)
874
1.39k
{
875
1.39k
  if (FG(wrapper_logged_errors)) {
876
662
    zend_hash_str_del(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
877
662
  }
878
1.39k
}
879
880
PHPAPI void php_stream_tidy_wrapper_error_log(php_stream_wrapper *wrapper)
881
3
{
882
3
  if (wrapper) {
883
3
    const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
884
3
    php_stream_tidy_wrapper_name_error_log(wrapper_name);
885
3
  }
886
3
}
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
}