Files
Lazizbek Ergashev 551967dd91 Fix GH-24491: Static method stream error handler replaces pending exception (#24520)
Since 2f7b01b86c, `zend_is_callable()` fills in an error string even when it fails because an exception is already pending, so `php_stream_call_error_handler()` threw a TypeError over the StreamException (or whatever the user error handler threw) for any `Class::method` handler. It now only throws when nothing is pending, the same way curl already does.

The same commit made `zend_parse_arg()` leak that error string when it bails out on a pending exception, so it's freed there now too.

Fixes GH-24491
2026-10-23 20:57:32 +01:00

885 lines
24 KiB
C

/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to the Modified BSD License that is |
| bundled with this package in the file LICENSE, and is available |
| through the World Wide Web at <https://www.php.net/license/>. |
| |
| SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Jakub Zelenka <bukka@php.net> |
+----------------------------------------------------------------------+
*/
#define ZEND_ENUM_StreamErrorCode_USE_NAME_TABLE
#include "php.h"
#include "php_globals.h"
#include "php_streams.h"
#include "php_stream_errors.h"
#include "zend_enum.h"
#include "zend_exceptions.h"
#include "ext/standard/file.h"
#include "stream_errors_arginfo.h"
/* Class entries */
static zend_class_entry *php_ce_stream_error_code;
static zend_class_entry *php_ce_stream_error_mode;
static zend_class_entry *php_ce_stream_error_store;
static zend_class_entry *php_ce_stream_error;
static zend_class_entry *php_ce_stream_exception;
/* Forward declarations */
static void php_stream_error_entry_free(php_stream_error_entry *entry);
/* Helper to create a single StreamError object from an entry */
static void php_stream_error_create_object(zval *zv, const php_stream_error_entry *entry)
{
object_init_ex(zv, php_ce_stream_error);
const char *case_name = NULL;
if (entry->code > 0 && entry->code <= ZEND_ENUM_StreamErrorCode_CASE_COUNT) {
case_name = zend_enum_StreamErrorCode_case_names[entry->code];
}
if (!case_name) {
case_name = "Generic";
}
/* TODO: migrate to zend_enum_get_case_by_id() */
zend_object *enum_obj = zend_enum_get_case_cstr(php_ce_stream_error_code, case_name);
ZEND_ASSERT(enum_obj != NULL);
zval code_enum;
ZVAL_OBJ_COPY(&code_enum, enum_obj);
zend_update_property(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("code"), &code_enum);
zval_ptr_dtor(&code_enum);
zend_update_property_str(
php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("message"), entry->message);
zend_update_property_string(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("wrapperName"),
entry->wrapper_name ? entry->wrapper_name : "");
zend_update_property_long(
php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("severity"), entry->severity);
zend_update_property_bool(
php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("terminating"), entry->terminating);
}
/* Create array of StreamError objects from error chain */
static void php_stream_error_create_array(zval *zv, const php_stream_error_entry *first)
{
array_init(zv);
const php_stream_error_entry *entry = first;
while (entry) {
zval error_obj;
php_stream_error_create_object(&error_obj, entry);
zend_hash_next_index_insert_new(Z_ARRVAL_P(zv), &error_obj);
entry = entry->next;
}
}
/* Context option helpers */
/* Error mode context options (internal C constants) */
C23_ENUM(php_stream_error_mode, uint8_t) {
PHP_STREAM_ERROR_MODE_ERROR = 0,
PHP_STREAM_ERROR_MODE_EXCEPTION = 1,
PHP_STREAM_ERROR_MODE_SILENT = 2
};
/* Error store context options (internal C constants) */
C23_ENUM(php_stream_error_store, uint8_t) {
PHP_STREAM_ERROR_STORE_AUTO = 0,
PHP_STREAM_ERROR_STORE_NONE = 1,
PHP_STREAM_ERROR_STORE_NON_TERM = 2,
PHP_STREAM_ERROR_STORE_TERMINAL = 3,
PHP_STREAM_ERROR_STORE_ALL = 4
};
static php_stream_error_store php_stream_auto_decide_error_store_mode(php_stream_error_mode error_mode)
{
switch (error_mode) {
case PHP_STREAM_ERROR_MODE_ERROR:
return PHP_STREAM_ERROR_STORE_NONE;
case PHP_STREAM_ERROR_MODE_EXCEPTION:
return PHP_STREAM_ERROR_STORE_NON_TERM;
case PHP_STREAM_ERROR_MODE_SILENT:
return PHP_STREAM_ERROR_STORE_ALL;
default:
return PHP_STREAM_ERROR_STORE_NONE;
}
}
static php_stream_error_mode php_stream_get_error_mode(const php_stream_context *context)
{
if (!context) {
return PHP_STREAM_ERROR_MODE_ERROR;
}
zval *option = php_stream_context_get_option(context, "stream", "error_mode");
if (!option) {
return PHP_STREAM_ERROR_MODE_ERROR;
}
if (Z_TYPE_P(option) != IS_OBJECT
|| !instanceof_function(Z_OBJCE_P(option), php_ce_stream_error_mode)) {
zend_type_error("stream context option 'error_mode' must be of type StreamErrorMode");
return PHP_STREAM_ERROR_MODE_ERROR;
}
switch ((zend_enum_StreamErrorMode) zend_enum_fetch_case_id(Z_OBJ_P(option))) {
case ZEND_ENUM_StreamErrorMode_Error:
return PHP_STREAM_ERROR_MODE_ERROR;
case ZEND_ENUM_StreamErrorMode_Exception:
return PHP_STREAM_ERROR_MODE_EXCEPTION;
case ZEND_ENUM_StreamErrorMode_Silent:
return PHP_STREAM_ERROR_MODE_SILENT;
}
return PHP_STREAM_ERROR_MODE_ERROR;
}
static php_stream_error_store php_stream_get_error_store_mode(
const php_stream_context *context, php_stream_error_mode error_mode)
{
if (!context) {
return php_stream_auto_decide_error_store_mode(error_mode);
}
zval *option = php_stream_context_get_option(context, "stream", "error_store");
if (!option) {
return php_stream_auto_decide_error_store_mode(error_mode);
}
if (Z_TYPE_P(option) != IS_OBJECT
|| !instanceof_function(Z_OBJCE_P(option), php_ce_stream_error_store)) {
zend_type_error("stream context option 'error_store' must be of type StreamErrorStore");
return php_stream_auto_decide_error_store_mode(error_mode);
}
switch ((zend_enum_StreamErrorStore) zend_enum_fetch_case_id(Z_OBJ_P(option))) {
case ZEND_ENUM_StreamErrorStore_Auto:
return php_stream_auto_decide_error_store_mode(error_mode);
case ZEND_ENUM_StreamErrorStore_None:
return PHP_STREAM_ERROR_STORE_NONE;
case ZEND_ENUM_StreamErrorStore_NonTerminating:
return PHP_STREAM_ERROR_STORE_NON_TERM;
case ZEND_ENUM_StreamErrorStore_Terminating:
return PHP_STREAM_ERROR_STORE_TERMINAL;
case ZEND_ENUM_StreamErrorStore_All:
return PHP_STREAM_ERROR_STORE_ALL;
}
return php_stream_auto_decide_error_store_mode(error_mode);
}
/* Helper functions */
static bool php_stream_has_terminating_error(const php_stream_error_entry *entry)
{
while (entry) {
if (entry->terminating) {
return true;
}
entry = entry->next;
}
return false;
}
static inline php_stream_error_operation *php_stream_get_operation_at_depth(uint32_t depth)
{
php_stream_error_state *state = &FG(stream_error_state);
if (depth < PHP_STREAM_ERROR_OPERATION_POOL_SIZE) {
return &state->operation_pool[depth];
} else {
uint32_t overflow_index = depth - PHP_STREAM_ERROR_OPERATION_POOL_SIZE;
ZEND_ASSERT(overflow_index < state->overflow_capacity);
return &state->overflow_operations[overflow_index];
}
}
static inline php_stream_error_operation *php_stream_get_parent_operation(void)
{
const php_stream_error_state *state = &FG(stream_error_state);
if (state->operation_depth <= state->operation_floor) {
return NULL;
}
return php_stream_get_operation_at_depth(state->operation_depth - 1);
}
/* Clean up functions */
static void php_stream_error_entry_free(php_stream_error_entry *entry)
{
while (entry) {
php_stream_error_entry *next = entry->next;
zend_string_release(entry->message);
efree(entry->wrapper_name);
efree(entry->docref);
efree(entry);
entry = next;
}
}
PHPAPI void php_stream_error_state_cleanup(void)
{
php_stream_error_state *state = &FG(stream_error_state);
state->operation_floor = 0;
state->refused_operations = 0;
while (state->current_operation) {
php_stream_error_operation *op = state->current_operation;
state->operation_depth--;
state->current_operation = php_stream_get_parent_operation();
php_stream_error_entry_free(op->first_error);
op->first_error = NULL;
op->last_error = NULL;
op->error_count = 0;
}
php_stream_stored_error *stored = state->stored_errors;
while (stored) {
php_stream_stored_error *next = stored->next;
php_stream_error_entry_free(stored->first_error);
efree(stored);
stored = next;
}
state->stored_errors = NULL;
state->stored_count = 0;
state->operation_depth = 0;
if (state->overflow_operations) {
efree(state->overflow_operations);
state->overflow_operations = NULL;
state->overflow_capacity = 0;
}
}
PHPAPI void php_stream_error_get_last(zval *return_value)
{
const php_stream_error_state *state = &FG(stream_error_state);
if (!state->stored_errors) {
ZVAL_EMPTY_ARRAY(return_value);
return;
}
php_stream_error_create_array(return_value, state->stored_errors->first_error);
}
PHPAPI void php_stream_error_clear_stored(void)
{
php_stream_error_state *state = &FG(stream_error_state);
php_stream_stored_error *stored = state->stored_errors;
while (stored) {
php_stream_stored_error *next = stored->next;
php_stream_error_entry_free(stored->first_error);
efree(stored);
stored = next;
}
state->stored_errors = NULL;
state->stored_count = 0;
}
/* Error operation stack management */
PHPAPI php_stream_error_operation *php_stream_error_operation_begin(void)
{
php_stream_error_state *state = &FG(stream_error_state);
if (state->operation_depth >= PHP_STREAM_ERROR_MAX_DEPTH) {
php_error_docref(NULL, E_WARNING,
"Stream error operation depth exceeded (%"PRIu32"), possible infinite recursion",
state->operation_depth);
state->refused_operations++;
return NULL;
}
php_stream_error_operation *op;
if (state->operation_depth < PHP_STREAM_ERROR_OPERATION_POOL_SIZE) {
op = &state->operation_pool[state->operation_depth];
} else {
uint32_t overflow_index = state->operation_depth - PHP_STREAM_ERROR_OPERATION_POOL_SIZE;
if (overflow_index >= state->overflow_capacity) {
uint32_t new_capacity
= state->overflow_capacity == 0 ? 8 : state->overflow_capacity * 2;
php_stream_error_operation *new_overflow = erealloc(
state->overflow_operations, sizeof(php_stream_error_operation) * new_capacity);
state->overflow_operations = new_overflow;
state->overflow_capacity = new_capacity;
}
op = &state->overflow_operations[overflow_index];
}
op->first_error = NULL;
op->last_error = NULL;
op->error_count = 0;
state->current_operation = op;
state->operation_depth++;
return op;
}
static void php_stream_error_add(zend_enum_StreamErrorCode code, const char *wrapper_name,
zend_string *message, const char *docref, int severity, bool terminating)
{
php_stream_error_operation *op = FG(stream_error_state).current_operation;
if (!op) {
zend_string_release(message);
return;
}
php_stream_error_entry *entry = emalloc(sizeof(php_stream_error_entry));
entry->message = message;
entry->code = code;
entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL;
entry->docref = docref ? estrdup(docref) : NULL;
entry->severity = severity;
entry->terminating = terminating;
entry->next = NULL;
if (op->last_error) {
op->last_error->next = entry;
} else {
op->first_error = entry;
}
op->last_error = entry;
op->error_count++;
}
/* Error reporting */
static void php_stream_call_error_handler(const zval *handler, zval *errors_array)
{
zend_fcall_info_cache fcc;
char *is_callable_error = NULL;
if (!zend_is_callable(handler, &fcc, &is_callable_error)) {
if (!EG(exception)) {
zend_type_error("stream error handler must be a valid callback, %s", is_callable_error);
}
efree(is_callable_error);
return;
}
zend_call_known_fcc(&fcc, NULL, 1, errors_array, NULL);
}
static void php_stream_throw_exception_with_errors(const php_stream_error_entry *first_error)
{
if (!first_error) {
return;
}
zval ex;
object_init_ex(&ex, php_ce_stream_exception);
/* Set message from first error */
zend_update_property_str(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("message"),
first_error->message);
/* Set code from first error */
zend_update_property_long(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("code"),
(zend_long) first_error->code);
/* Build errors array and set it */
zval errors_array;
php_stream_error_create_array(&errors_array, first_error);
zend_update_property(php_ce_stream_exception, Z_OBJ(ex), ZEND_STRL("errors"), &errors_array);
zval_ptr_dtor(&errors_array);
zend_throw_exception_object(&ex);
}
static void php_stream_report_errors(const php_stream_context *context, const php_stream_error_entry *first_error,
php_stream_error_mode error_mode, bool is_terminating)
{
switch (error_mode) {
case PHP_STREAM_ERROR_MODE_ERROR: {
const php_stream_error_entry *entry = first_error;
while (entry) {
php_error_docref(entry->docref, entry->severity, "%s", ZSTR_VAL(entry->message));
entry = entry->next;
}
break;
}
case PHP_STREAM_ERROR_MODE_EXCEPTION: {
if (is_terminating) {
php_stream_throw_exception_with_errors(first_error);
}
break;
}
case PHP_STREAM_ERROR_MODE_SILENT:
break;
}
/* Call user error handler if set */
const zval *handler
= context ? php_stream_context_get_option(context, "stream", "error_handler") : NULL;
if (handler) {
zval errors_array;
php_stream_error_create_array(&errors_array, first_error);
php_stream_call_error_handler(handler, &errors_array);
zval_ptr_dtor(&errors_array);
}
}
/* Error storage */
PHPAPI void php_stream_error_operation_end(const php_stream_context *context)
{
php_stream_error_state *state = &FG(stream_error_state);
php_stream_error_operation *op = state->current_operation;
if (state->refused_operations > 0) {
state->refused_operations--;
return;
}
if (!op) {
return;
}
php_stream_error_entry *first_error = op->first_error;
op->first_error = NULL;
op->last_error = NULL;
op->error_count = 0;
state->operation_depth--;
state->current_operation = php_stream_get_parent_operation();
if (!first_error) {
return;
}
if (context == NULL) {
context = FG(default_context);
}
php_stream_error_mode error_mode = php_stream_get_error_mode(context);
php_stream_error_store store_mode = php_stream_get_error_store_mode(context, error_mode);
bool is_terminating = php_stream_has_terminating_error(first_error);
if (context) {
GC_ADDREF(context->res);
}
uint32_t saved_floor = state->operation_floor;
state->operation_floor = state->operation_depth;
state->current_operation = NULL;
php_stream_report_errors(context, first_error, error_mode, is_terminating);
state->operation_floor = saved_floor;
state->current_operation = php_stream_get_parent_operation();
if (context) {
zend_list_delete(context->res);
}
if (store_mode == PHP_STREAM_ERROR_STORE_NONE) {
php_stream_error_entry_free(first_error);
return;
}
php_stream_error_entry *entry = first_error;
php_stream_error_entry *prev = NULL;
php_stream_error_entry *to_store_first = NULL;
php_stream_error_entry *to_store_last = NULL;
uint32_t to_store_count = 0;
php_stream_error_entry *remaining_first = NULL;
while (entry) {
php_stream_error_entry *next = entry->next;
bool should_store = false;
if (store_mode == PHP_STREAM_ERROR_STORE_ALL) {
should_store = true;
} else if (store_mode == PHP_STREAM_ERROR_STORE_NON_TERM && !entry->terminating) {
should_store = true;
} else if (store_mode == PHP_STREAM_ERROR_STORE_TERMINAL && entry->terminating) {
should_store = true;
}
if (should_store) {
entry->next = NULL;
if (to_store_last) {
to_store_last->next = entry;
} else {
to_store_first = entry;
}
to_store_last = entry;
to_store_count++;
} else {
entry->next = NULL;
if (prev) {
prev->next = entry;
} else {
remaining_first = entry;
}
prev = entry;
}
entry = next;
}
if (to_store_first) {
php_stream_stored_error *stored = emalloc(sizeof(php_stream_stored_error));
stored->first_error = to_store_first;
stored->error_count = to_store_count;
stored->next = state->stored_errors;
state->stored_errors = stored;
state->stored_count++;
}
if (remaining_first) {
php_stream_error_entry_free(remaining_first);
}
}
PHPAPI void php_stream_error_operation_end_for_stream(const php_stream *stream)
{
php_stream_error_state *state = &FG(stream_error_state);
php_stream_error_operation *op = state->current_operation;
if (state->refused_operations > 0) {
state->refused_operations--;
return;
}
if (!op) {
return;
}
if (op->error_count == 0) {
state->operation_depth--;
state->current_operation = php_stream_get_parent_operation();
op->first_error = NULL;
op->last_error = NULL;
return;
}
const php_stream_context *context = PHP_STREAM_CONTEXT(stream);
php_stream_error_operation_end(context);
}
PHPAPI void php_stream_error_operation_abort(void)
{
php_stream_error_state *state = &FG(stream_error_state);
php_stream_error_operation *op = state->current_operation;
if (state->refused_operations > 0) {
state->refused_operations--;
return;
}
if (!op) {
return;
}
state->operation_depth--;
state->current_operation = php_stream_get_parent_operation();
php_stream_error_entry_free(op->first_error);
op->first_error = NULL;
op->last_error = NULL;
op->error_count = 0;
}
/* Wrapper error reporting */
static void php_stream_wrapper_error_internal(const char *wrapper_name, const php_stream_context *context,
const char *docref, int severity, bool terminating,
zend_enum_StreamErrorCode code, zend_string *message)
{
bool implicit_operation = (FG(stream_error_state).current_operation == NULL);
if (implicit_operation) {
php_stream_error_operation_begin();
}
php_stream_error_add(code, wrapper_name, message, docref, severity, terminating);
if (implicit_operation) {
php_stream_error_operation_end(context);
}
}
PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name,
const php_stream_context *context, const char *docref, int options, int severity,
bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...)
{
if (!(options & REPORT_ERRORS)) {
return;
}
va_list args;
va_start(args, fmt);
zend_string *message = vstrpprintf(0, fmt, args);
va_end(args);
php_stream_wrapper_error_internal(
wrapper_name, context, docref, severity, terminating, code, message);
}
PHPAPI void php_stream_wrapper_error(
const php_stream_wrapper *wrapper,
const php_stream_context *context,
const char *docref, int options, int severity, bool terminating,
zend_enum_StreamErrorCode code, const char *fmt, ...)
{
if (!(options & REPORT_ERRORS)) {
return;
}
va_list args;
va_start(args, fmt);
zend_string *message = vstrpprintf(0, fmt, args);
va_end(args);
const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
php_stream_wrapper_error_internal(
wrapper_name, context, docref, severity, terminating, code, message);
}
/* Stream error reporting */
PHPAPI void php_stream_error(const php_stream *stream, const char *docref, int severity,
bool terminating, zend_enum_StreamErrorCode code, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
zend_string *message = vstrpprintf(0, fmt, args);
va_end(args);
const char *wrapper_name = stream->wrapper ? stream->wrapper->wops->label : "stream";
const php_stream_context *context = PHP_STREAM_CONTEXT(stream);
php_stream_wrapper_error_internal(wrapper_name, context, docref, severity,
terminating, code, message);
}
/* Legacy wrapper error logging */
static void php_stream_error_entry_dtor_legacy(void *error)
{
php_stream_error_entry *entry = *(php_stream_error_entry **) error;
zend_string_release(entry->message);
efree(entry->wrapper_name);
efree(entry->docref);
efree(entry);
}
static void php_stream_error_list_dtor(zval *item)
{
zend_llist *list = (zend_llist *) Z_PTR_P(item);
zend_llist_destroy(list);
efree(list);
}
static void php_stream_wrapper_log_store_error(zend_string *message, zend_enum_StreamErrorCode code,
const char *wrapper_name, int severity, bool terminating)
{
php_stream_error_entry *entry = ecalloc(1, sizeof(php_stream_error_entry));
entry->message = message;
entry->code = code;
entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL;
entry->severity = severity;
entry->terminating = terminating;
if (!FG(wrapper_logged_errors)) {
ALLOC_HASHTABLE(FG(wrapper_logged_errors));
zend_hash_init(FG(wrapper_logged_errors), 8, NULL, php_stream_error_list_dtor, 0);
}
zend_llist *list
= zend_hash_str_find_ptr(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
if (!list) {
zend_llist new_list;
zend_llist_init(
&new_list, sizeof(php_stream_error_entry *), php_stream_error_entry_dtor_legacy, 0);
list = zend_hash_str_update_mem(FG(wrapper_logged_errors), wrapper_name,
strlen(wrapper_name), &new_list, sizeof(new_list));
}
zend_llist_add_element(list, &entry);
}
PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper,
const php_stream_context *context, int options, int severity, bool terminating,
zend_enum_StreamErrorCode code, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
zend_string *message = vstrpprintf(0, fmt, args);
const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
if (options & REPORT_ERRORS) {
php_stream_wrapper_error_internal(
wrapper_name, context, NULL, severity, terminating, code, message);
} else {
php_stream_wrapper_log_store_error(
message, code, wrapper_name, severity, terminating);
}
va_end(args);
}
static zend_llist *php_stream_get_wrapper_errors_list(const char *wrapper_name)
{
if (!FG(wrapper_logged_errors)) {
return NULL;
}
return (zend_llist *) zend_hash_str_find_ptr(
FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
}
PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name,
const php_stream_context *context, zend_enum_StreamErrorCode code,
const char *caption)
{
char *msg;
char errstr[256];
bool free_msg = false;
if (EG(exception)) {
return;
}
if (strcmp(wrapper_name, PHP_STREAM_ERROR_WRAPPER_DEFAULT_NAME)) {
zend_llist *err_list = php_stream_get_wrapper_errors_list(wrapper_name);
if (err_list) {
size_t l = 0;
size_t brlen;
const char *br;
php_stream_error_entry **err_entry_p;
zend_llist_position pos;
if (PG(html_errors)) {
brlen = 7;
br = "<br />\n";
} else {
brlen = 1;
br = "\n";
}
size_t i = 0;
const size_t count = zend_llist_count(err_list);
for (i = 0, err_entry_p = zend_llist_get_first_ex(err_list, &pos); err_entry_p;
err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) {
l += ZSTR_LEN((*err_entry_p)->message);
if (i < count - 1) {
l += brlen;
}
}
msg = emalloc(l + 1);
msg[0] = '\0';
for (err_entry_p = zend_llist_get_first_ex(err_list, &pos), i = 0; err_entry_p;
err_entry_p = zend_llist_get_next_ex(err_list, &pos), i++) {
strcat(msg, ZSTR_VAL((*err_entry_p)->message));
if (i < count - 1) {
strcat(msg, br);
}
}
free_msg = true;
} else {
if (!strcmp(wrapper_name, php_plain_files_wrapper.wops->label)) {
msg = php_socket_strerror_s(errno, errstr, sizeof(errstr));
} else {
msg = "operation failed";
}
}
} else {
msg = "no suitable wrapper could be found";
}
zend_string *message = strpprintf(0, "%s: %s", caption, msg);
php_stream_wrapper_error_internal(wrapper_name, context, NULL, E_WARNING, true,
code, message);
if (free_msg) {
efree(msg);
}
}
PHPAPI void php_stream_display_wrapper_errors(const php_stream_wrapper *wrapper,
const php_stream_context *context, zend_enum_StreamErrorCode code,
const char *caption)
{
if (wrapper) {
const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
php_stream_display_wrapper_name_errors(wrapper_name, context, code, caption);
}
}
PHPAPI void php_stream_tidy_wrapper_name_error_log(const char *wrapper_name)
{
if (FG(wrapper_logged_errors)) {
zend_hash_str_del(FG(wrapper_logged_errors), wrapper_name, strlen(wrapper_name));
}
}
PHPAPI void php_stream_tidy_wrapper_error_log(const php_stream_wrapper *wrapper)
{
if (wrapper) {
const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper);
php_stream_tidy_wrapper_name_error_log(wrapper_name);
}
}
/* StreamException methods */
PHP_METHOD(StreamException, getErrors)
{
ZEND_PARSE_PARAMETERS_NONE();
const zval *errors = zend_read_property(
php_ce_stream_exception, Z_OBJ_P(ZEND_THIS), ZEND_STRL("errors"), 1, NULL);
RETURN_COPY(errors);
}
/* Module init */
PHP_MINIT_FUNCTION(stream_errors)
{
php_ce_stream_error_code = register_class_StreamErrorCode();
php_ce_stream_error_mode = register_class_StreamErrorMode();
php_ce_stream_error_store = register_class_StreamErrorStore();
php_ce_stream_error = register_class_StreamError();
php_ce_stream_exception = register_class_StreamException(zend_ce_exception);
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(stream_errors)
{
return SUCCESS;
}