Coverage Report

Created: 2026-10-28 06:20

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