mirror of
https://github.com/php/php-src.git
synced 2026-10-28 17:56:50 +08:00
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
885 lines
24 KiB
C
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;
|
|
}
|