LCOV - code coverage report
Current view: top level - src - wmerrors.c (source / functions) Hit Total Coverage
Test: mediawiki/php/wmerrors test coverage report Lines: 112 240 46.7 %
Date: 2023-07-06 23:12:57 Functions: 14 19 73.7 %

          Line data    Source code
       1             : 
       2             : #ifdef HAVE_CONFIG_H
       3             : #include "config.h"
       4             : #endif
       5             : 
       6             : #include <stdlib.h>
       7             : 
       8             : #include "php.h"
       9             : #include "php_ini.h"
      10             : #include "php_main.h"
      11             : #include "php_wmerrors.h"
      12             : #include "ext/standard/php_standard.h"
      13             : #include "SAPI.h" /* for sapi_module */
      14             : #include "ext/date/php_date.h" /* for php_format_date */
      15             : #include "ext/standard/php_smart_string.h" /* for smart_string */
      16             : #include "Zend/zend_builtin_functions.h" /* for zend_fetch_debug_backtrace */
      17             : #include "Zend/zend_exceptions.h" /* for zend_ce_exception */
      18             : 
      19             : #if PHP_VERSION_ID >= 80100
      20             : #define wmerrors_error_filename zend_string
      21             : #else
      22             : #define wmerrors_error_filename const char
      23             : #endif
      24             : 
      25             : #if PHP_VERSION_ID >= 80000
      26             : #define wmerrors_message zend_string *message
      27             : #define wmerrors_message_args message
      28             : #define WMERRORS_DONT_BAIL E_DONT_BAIL
      29             : #else
      30             : #define wmerrors_message const char *format, va_list args
      31             : #define wmerrors_message_args format, args
      32             : #define WMERRORS_DONT_BAIL 0
      33             : #endif
      34             : 
      35             : static int wmerrors_post_deactivate();
      36             : static void wmerrors_cb(int type, wmerrors_error_filename *error_filename, const uint32_t error_lineno, wmerrors_message);
      37             : static void wmerrors_show_message(int type, wmerrors_error_filename *error_filename, const uint32_t error_lineno, wmerrors_message);
      38             : static void wmerrors_get_concise_backtrace(smart_string *s);
      39             : static void wmerrors_write_full_backtrace(smart_string *s);
      40             : static void wmerrors_write_request_info(smart_string *s);
      41             : static void wmerrors_execute_file(int type, wmerrors_error_filename *error_filename, const uint32_t error_lineno, wmerrors_message);
      42             : 
      43             : ZEND_DECLARE_MODULE_GLOBALS(wmerrors)
      44             : 
      45             : PHP_FUNCTION(wmerrors_malloc_test);
      46             : 
      47             : ZEND_BEGIN_ARG_INFO(wmerrors_malloc_test_arginfo, 0)
      48             : ZEND_END_ARG_INFO()
      49             : 
      50             : zend_function_entry wmerrors_functions[] = {
      51             :     PHP_FE(wmerrors_malloc_test, wmerrors_malloc_test_arginfo)
      52             :     {NULL, NULL, NULL}
      53             : };
      54             : 
      55             : 
      56             : zend_module_entry wmerrors_module_entry = {
      57             :     STANDARD_MODULE_HEADER,
      58             :     "wmerrors",
      59             :     wmerrors_functions,
      60             :     PHP_MINIT(wmerrors),
      61             :     PHP_MSHUTDOWN(wmerrors),
      62             :     PHP_RINIT(wmerrors),
      63             :     PHP_RSHUTDOWN(wmerrors),
      64             :     PHP_MINFO(wmerrors),
      65             :     PHP_WMERRORS_VERSION,
      66             :     NO_MODULE_GLOBALS,
      67             :     wmerrors_post_deactivate,
      68             :     STANDARD_MODULE_PROPERTIES_EX
      69             : };
      70             : 
      71             : 
      72             : #ifdef COMPILE_DL_WMERRORS
      73           4 : ZEND_GET_MODULE(wmerrors)
      74             : #endif
      75             : 
      76             : PHP_INI_BEGIN()
      77             :     STD_PHP_INI_BOOLEAN("wmerrors.enabled", "0", PHP_INI_ALL, OnUpdateBool, enabled, zend_wmerrors_globals, wmerrors_globals )
      78             :     STD_PHP_INI_ENTRY("wmerrors.message_file", "", PHP_INI_ALL, OnUpdateString, message_file, zend_wmerrors_globals, wmerrors_globals)
      79             :     STD_PHP_INI_ENTRY("wmerrors.error_script_file", "", PHP_INI_ALL, OnUpdateString, error_script_file, zend_wmerrors_globals, wmerrors_globals)
      80             :     STD_PHP_INI_ENTRY("wmerrors.log_file", "", PHP_INI_ALL, OnUpdateString, log_file, zend_wmerrors_globals, wmerrors_globals)
      81             :     STD_PHP_INI_BOOLEAN("wmerrors.log_backtrace", "0", PHP_INI_ALL, OnUpdateBool, log_backtrace, zend_wmerrors_globals, wmerrors_globals)
      82             :     STD_PHP_INI_ENTRY("wmerrors.log_line_prefix", "", PHP_INI_ALL, OnUpdateString, log_line_prefix, zend_wmerrors_globals, wmerrors_globals)
      83             :     STD_PHP_INI_BOOLEAN("wmerrors.ignore_logging_errors", "0", PHP_INI_ALL, OnUpdateBool, ignore_logging_errors, zend_wmerrors_globals, wmerrors_globals)
      84             :     STD_PHP_INI_BOOLEAN("wmerrors.backtrace_in_php_error_message", "0", PHP_INI_ALL, OnUpdateBool, backtrace_in_php_error_message, zend_wmerrors_globals, wmerrors_globals)
      85             : PHP_INI_END()
      86             : 
      87             : void (*old_error_cb)(int type, wmerrors_error_filename *error_filename, const uint32_t error_lineno, wmerrors_message);
      88             : 
      89           4 : static void php_wmerrors_init_globals(zend_wmerrors_globals *wmerrors_globals)
      90             : {
      91           4 :     memset(wmerrors_globals, 0, sizeof(zend_wmerrors_globals));
      92           4 : }
      93             : 
      94           4 : PHP_MINIT_FUNCTION(wmerrors)
      95             : {
      96           4 :     ZEND_INIT_MODULE_GLOBALS(wmerrors, php_wmerrors_init_globals, NULL);
      97           4 :     REGISTER_INI_ENTRIES();
      98           4 :     old_error_cb = zend_error_cb;
      99           4 :     zend_error_cb = wmerrors_cb;
     100           4 :     return SUCCESS;
     101             : }
     102             : 
     103             : 
     104           4 : PHP_MSHUTDOWN_FUNCTION(wmerrors)
     105             : {
     106           4 :     UNREGISTER_INI_ENTRIES();
     107           4 :     zend_error_cb = old_error_cb;
     108           4 :     return SUCCESS;
     109             : }
     110             : 
     111             : 
     112             : 
     113           4 : PHP_RINIT_FUNCTION(wmerrors)
     114             : {
     115           4 :     WMERRORS_G(recursion_guard) = 0;
     116           4 :     return SUCCESS;
     117             : }
     118             : 
     119             : 
     120             : 
     121           4 : PHP_RSHUTDOWN_FUNCTION(wmerrors)
     122             : {
     123           4 :     return SUCCESS;
     124             : }
     125             : 
     126           4 : int wmerrors_post_deactivate()
     127             : {
     128           4 :     return SUCCESS;
     129             : }
     130             : 
     131           0 : PHP_MINFO_FUNCTION(wmerrors)
     132             : {
     133           0 :     php_info_print_table_start();
     134           0 :     php_info_print_table_header(2, "wmerrors support", "enabled");
     135           0 :     php_info_print_table_row(2, "wmerrors version", PHP_WMERRORS_VERSION);
     136           0 :     php_info_print_table_end();
     137           0 :     DISPLAY_INI_ENTRIES();
     138           0 : }
     139             : 
     140             : static const char* wmerrors_error_type_to_string(int type);
     141             : static void wmerrors_log_error(int type, wmerrors_error_filename *error_filename, const uint32_t error_lineno, wmerrors_message);
     142             : 
     143           1 : static void wmerrors_cb(int type, wmerrors_error_filename *error_filename, const uint32_t error_lineno, wmerrors_message)
     144             : {
     145           1 :     smart_string new_filename = { NULL };
     146           1 :     int orig_type = type;
     147           1 :     type &= ~WMERRORS_DONT_BAIL;
     148             : 
     149             :     /* Do not call the custom error handling if:
     150             :      * it's not enabled,
     151             :      * OR the error is not one of E_{,CORE_,COMPILE_,USER_,RECOVERABLE_}ERROR,
     152             :      * OR the error is an E_RECOVERABLE_ERROR and is being thrown as an exception,
     153             :      * OR it's triggering itself (recursion guard)
     154             :      */
     155           1 :     if ( !WMERRORS_G(enabled)
     156           1 :             || (type == E_RECOVERABLE_ERROR && EG(error_handling) == EH_THROW && !EG(exception))
     157           1 :             || (type != E_ERROR && type != E_CORE_ERROR && type != E_COMPILE_ERROR
     158           0 :                   && type != E_USER_ERROR && type != E_RECOVERABLE_ERROR)
     159           1 :             || WMERRORS_G(recursion_guard))
     160             :     {
     161             :         /* recursion_guard != 1 means this is an error in writing to the log file.
     162             :          * Ignore it if configured to do so.
     163             :          */
     164           0 :         if (WMERRORS_G(recursion_guard) == 1 || !WMERRORS_G(ignore_logging_errors))
     165           0 :             old_error_cb(orig_type, error_filename, error_lineno, wmerrors_message_args);
     166           0 :         return;
     167             :     }
     168           1 :     WMERRORS_G(recursion_guard) = 1;
     169             :     /* No more OOM errors for now thanks */
     170           1 :     zend_set_memory_limit((size_t)-1);
     171             : 
     172             :     /* Do not show the html error to console */
     173           1 :     if ( WMERRORS_G(enabled) && strncmp(sapi_module.name, "cli", 3) ) {
     174             :         /* Show the message */
     175           0 :         if (WMERRORS_G(error_script_file) && WMERRORS_G(error_script_file)[0] != '\0') {
     176           0 :             wmerrors_execute_file(type, error_filename, error_lineno, wmerrors_message_args);
     177           0 :         } else if (WMERRORS_G(message_file) && WMERRORS_G(message_file)[0] != '\0') {
     178           0 :             wmerrors_show_message(type, error_filename, error_lineno, wmerrors_message_args);
     179             :         }
     180             :     }
     181             : 
     182           1 :     if ( WMERRORS_G(enabled) ) {
     183             :         /* Log the error */
     184           1 :         wmerrors_log_error(type, error_filename, error_lineno, wmerrors_message_args);
     185             :     }
     186             : 
     187             :     /* Put a concise backtrace in the normal output */
     188           1 :     if (WMERRORS_G(backtrace_in_php_error_message)) {
     189           1 :         wmerrors_get_concise_backtrace(&new_filename);
     190             :     }
     191             : 
     192           1 :     smart_string_appendl(
     193             :         &new_filename,
     194             : #if PHP_VERSION_ID >= 80100
     195             :         ZSTR_VAL(error_filename),
     196             :         ZSTR_LEN(error_filename)
     197             : #else
     198             :         error_filename,
     199             :         strlen(error_filename)
     200             : #endif
     201             :     );
     202             :     smart_string_0(&new_filename);
     203             : 
     204           1 :     WMERRORS_G(recursion_guard) = 0;
     205           1 :     zend_set_memory_limit(PG(memory_limit));
     206             : 
     207             :     /* Pass through */
     208           2 :     old_error_cb(
     209             :         orig_type,
     210             : #if PHP_VERSION_ID >= 80100
     211             :         zend_string_init(new_filename.c, new_filename.len, 0),
     212             : #else
     213           1 :         new_filename.c,
     214             : #endif
     215             :         error_lineno,
     216             :         wmerrors_message_args
     217             :     );
     218             :     /* Note: old_error_cb() may not return, in which case there will be no
     219             :      * explicit free of new_filename */
     220             :     smart_string_free(&new_filename);
     221             : }
     222             : 
     223             : /* Obtain a concisely formatted backtrace */
     224           1 : static void wmerrors_get_concise_backtrace(smart_string *s) {
     225           1 :     zval trace = {}, *entry, *file, *line;
     226             :     HashPosition pos;
     227             :     zend_string *basename;
     228             : 
     229           1 :     zend_fetch_debug_backtrace(&trace, 0, 0, 1000);
     230             : 
     231           1 :     if (Z_TYPE(trace) != IS_ARRAY) {
     232             :         /* Not supposed to happen */
     233             :         zval_dtor(&trace);
     234           0 :         return;
     235             :     }
     236             : 
     237           1 :     zend_hash_internal_pointer_reset_ex(Z_ARRVAL(trace), &pos);
     238           1 :     ZEND_HASH_FOREACH_VAL(Z_ARRVAL(trace), entry) {
     239           0 :         if (!entry || Z_TYPE_P(entry) != IS_ARRAY) {
     240             :             /* Not supposed to happen */
     241             :             smart_string_appendl(s, "?!? ", sizeof("?!? "));
     242           0 :             continue;
     243             :         }
     244             : 
     245           0 :         file = zend_hash_str_find(Z_ARRVAL_P(entry), ZEND_STRL("file"));
     246           0 :         line = zend_hash_str_find(Z_ARRVAL_P(entry), ZEND_STRL("line"));
     247             : 
     248           0 :         if (!file || Z_TYPE_P(file) != IS_STRING || !line || Z_TYPE_P(line) != IS_LONG) {
     249             :             /* Not supposed to happen */
     250             :             smart_string_appendl(s, "?!?!? ", sizeof("?!?!? "));
     251           0 :             continue;
     252             :         }
     253           0 :         basename = php_basename(Z_STRVAL_P(file), Z_STRLEN_P(file), NULL, 0);
     254           0 :         smart_string_appendl(s, ZSTR_VAL(basename), ZSTR_LEN(basename));
     255             :         smart_string_appendc(s, ':');
     256           0 :         smart_string_append_long(s, Z_LVAL_P(line));
     257             :         smart_string_appendc(s, ' ');
     258             : 
     259             :         zend_string_free(basename);
     260             :     } ZEND_HASH_FOREACH_END();
     261             :     zval_dtor(&trace);
     262             : }
     263             : 
     264           1 : static php_stream * wmerrors_open_log_file(const char* stream_name) {
     265             :     php_stream * stream;
     266           1 :     int err; zend_string *errstr = NULL;
     267             :     struct timeval tv;
     268           1 :     int flags = 0;
     269             : 
     270           1 :     if (!WMERRORS_G(ignore_logging_errors))
     271           1 :         flags |= REPORT_ERRORS;
     272             : 
     273           1 :     if ( strncmp( stream_name, "tcp://", 6 ) && strncmp( stream_name, "udp://", 6 ) ) {
     274             :         /* Is it a wrapper? */
     275           1 :         stream = php_stream_open_wrapper((char*)stream_name, "ab", flags, NULL);
     276             :     } else {
     277             :         /* Maybe it's a transport? */
     278           0 :         double timeout = FG(default_socket_timeout);
     279             :         unsigned long conv;
     280           0 :         conv = (unsigned long) (timeout * 1000000.0);
     281           0 :         tv.tv_sec = conv / 1000000;
     282           0 :         tv.tv_usec = conv % 1000000;
     283             : 
     284           0 :         stream = php_stream_xport_create(stream_name, strlen(stream_name), flags,
     285             :             STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT, NULL, &tv, NULL, &errstr, &err);
     286             :     }
     287             : 
     288           1 :     if (errstr) {
     289           0 :         zend_string_release(errstr);
     290             :     }
     291             : 
     292             :     /* Set the chunk size to something fairly large, to avoid fragmentation */
     293           1 :     if (stream) {
     294           1 :         php_stream_set_option(stream, PHP_STREAM_OPTION_SET_CHUNK_SIZE, 65507, NULL);
     295             :     }
     296           1 :     return stream;
     297             : }
     298             : 
     299           1 : static void wmerrors_log_error(int type, wmerrors_error_filename *error_filename, const uint32_t error_lineno, wmerrors_message) {
     300             :     char *first_line;
     301             :     int first_line_len;
     302             :     char error_time_str[256];
     303             :     time_t simpleTime;
     304             :     struct tm brokenTime;
     305             :     php_stream *logfile_stream;
     306           1 :     smart_string our_message = {NULL};
     307           1 :     smart_string prefixed_message = {NULL};
     308             : 
     309           1 :     if ( !WMERRORS_G(log_file) || *WMERRORS_G(log_file) == '\0') {
     310             :         /* No log file configured */
     311           0 :         return;
     312             :     }
     313             : 
     314             : #if PHP_VERSION_ID < 80000
     315             :     char *input_message;
     316             :     int input_message_len;
     317             :     va_list my_args;
     318             : 
     319             :     /* Don't destroy the caller's va_list */
     320           1 :     va_copy(my_args, args);
     321             :     /* Write the input message */
     322           1 :     input_message_len = vspprintf(&input_message, 0, format, my_args);
     323           1 :     va_end(my_args);
     324             : #else
     325             :     int input_message_len = ZSTR_LEN(message) > INT_MAX ? INT_MAX : ZSTR_LEN(message);
     326             : #endif
     327             : 
     328             :     /* Try opening the logging file */
     329             :     /* Set recursion_guard==2 whenever we're doing something to the log file */
     330           1 :     WMERRORS_G(recursion_guard) = 2;
     331           1 :     logfile_stream = wmerrors_open_log_file(WMERRORS_G(log_file));
     332           1 :     WMERRORS_G(recursion_guard) = 1;
     333           1 :     if ( !logfile_stream ) {
     334           0 :         return;
     335             :     }
     336             : 
     337             :     /* Get a date string */
     338           1 :     simpleTime = time(NULL);
     339           1 :     localtime_r(&simpleTime, &brokenTime);
     340           1 :     strftime(error_time_str, sizeof(error_time_str), "%Y-%m-%d %H:%M:%S", &brokenTime);
     341             : 
     342             :     /* Make the initial log line */
     343           1 :     first_line_len = spprintf(&first_line, 0, "[%s] %s: %.*s at %s on line %u%s",
     344             :             error_time_str, wmerrors_error_type_to_string(type),
     345             : #if PHP_VERSION_ID >= 80000
     346             :             input_message_len, ZSTR_VAL(message),
     347             : #else
     348             :             input_message_len, input_message,
     349             : #endif
     350             : #if PHP_VERSION_ID >= 80100
     351             :             ZSTR_VAL(error_filename),
     352             : #else
     353             :             error_filename,
     354             : #endif
     355             :             error_lineno, PHP_EOL);
     356           1 :     smart_string_appendl(&our_message, first_line, first_line_len);
     357             : #if PHP_VERSION_ID < 80000
     358           1 :     efree(input_message);
     359             : #endif
     360           1 :     efree(first_line);
     361             : 
     362             :     /* Write the request info */
     363           1 :     wmerrors_write_request_info(&our_message);
     364             : 
     365             :     /* Write a backtrace */
     366           1 :     if ( WMERRORS_G(log_backtrace) ) {
     367             :         smart_string_appends(&our_message, "Backtrace:");
     368             :         smart_string_appends(&our_message, PHP_EOL);
     369           1 :         wmerrors_write_full_backtrace(&our_message);
     370             :     }
     371             : 
     372             :     /* Add the log line prefix if requested */
     373           1 :     if (our_message.c && WMERRORS_G(log_line_prefix) && WMERRORS_G(log_line_prefix)[0]) {
     374           0 :         char * line_start = our_message.c;
     375           0 :         char * message_end = our_message.c + our_message.len;
     376             :         char * line_end;
     377           0 :         while (line_start < message_end) {
     378           0 :             smart_string_appends(&prefixed_message, WMERRORS_G(log_line_prefix));
     379           0 :             line_end = memchr(line_start, '\n', message_end - line_start);
     380           0 :             if (!line_end) {
     381           0 :                 line_end = message_end - 1;
     382             :             }
     383           0 :             smart_string_appendl(&prefixed_message, line_start, line_end - line_start + 1);
     384           0 :             line_start = line_end + 1;
     385             :         }
     386             :         smart_string_free(&our_message);
     387             :     } else {
     388           1 :         prefixed_message = our_message;
     389             :     }
     390             : 
     391           1 :     WMERRORS_G(recursion_guard) = 2;
     392           1 :     if (prefixed_message.c) {
     393           1 :         php_stream_write(logfile_stream, prefixed_message.c, prefixed_message.len);
     394             :     }
     395           1 :     php_stream_close(logfile_stream);
     396           1 :     WMERRORS_G(recursion_guard) = 1;
     397             :     smart_string_free(&prefixed_message);
     398             : }
     399             : 
     400             : 
     401             : /**
     402             :  * Write a backtrace to a string
     403             :  */
     404           1 : static void wmerrors_write_full_backtrace(smart_string * s) {
     405             :     zval trace;
     406             :     zval backtrace_fname;
     407             :     int status;
     408             :     zval exception;
     409             : 
     410             :     /* Create an Exception object */
     411           1 :     object_init_ex(&exception, zend_ce_exception);
     412             : 
     413             :     /* Call Exception::getTraceAsString() */
     414           1 :     ZVAL_STRING(&backtrace_fname, "getTraceAsString");
     415           1 :     status = call_user_function(NULL, &exception, &backtrace_fname,
     416             :         &trace, 0, NULL);
     417             : 
     418             :     zval_dtor(&backtrace_fname);
     419           1 :     zval_ptr_dtor(&exception);
     420           1 :     if (status != SUCCESS) {
     421           0 :         return;
     422             :     }
     423             : 
     424             :     /* Write it */
     425           1 :     convert_to_string(&trace);
     426           1 :     smart_string_appendl(s, Z_STRVAL(trace), Z_STRLEN(trace));
     427             :     smart_string_appends(s, PHP_EOL);
     428             : 
     429             :     zval_dtor(&trace);
     430             : }
     431             : 
     432             : /**
     433             :  * Write the current URL to a string
     434             :  */
     435           1 : static void wmerrors_write_request_info(smart_string * s) {
     436             :     HashTable * server_ht;
     437             :     zend_string *hostname;
     438             : 
     439           1 :     if (zend_is_auto_global_str("_SERVER", sizeof("_SERVER") - 1)) {
     440           1 :         server_ht = Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]);
     441             :     } else {
     442           0 :         server_ht = NULL;
     443             :     }
     444             : 
     445             :     /* Server */
     446           1 :     hostname = php_get_uname('n');
     447             :     smart_string_appends(s, "Server: ");
     448           1 :     smart_string_appends(s, ZSTR_VAL(hostname));
     449             :     smart_string_appends(s, PHP_EOL);
     450             :     zend_string_release(hostname);
     451             : 
     452             :     /* Method */
     453           1 :     if (SG(request_info).request_method) {
     454             :         smart_string_appends(s, "Method: ");
     455           0 :         smart_string_appends(s, SG(request_info).request_method);
     456             :         smart_string_appends(s, PHP_EOL);
     457             :     }
     458             : 
     459             :     /* URL */
     460             :     smart_string_appends(s, "URL: ");
     461             : 
     462           1 :     zval *info = server_ht ? zend_hash_str_find(server_ht, ZEND_STRL("HTTPS")) : NULL;
     463           1 :     if (info) {
     464             :         smart_string_appends(s, "https://");
     465             :     } else {
     466             :         smart_string_appends(s, "http://");
     467             :     }
     468             : 
     469           1 :     info = server_ht ? zend_hash_str_find(server_ht, ZEND_STRL("HTTP_HOST")) : NULL;
     470           1 :     if (info) {
     471           0 :         smart_string_appendl(s, Z_STRVAL_P(info), Z_STRLEN_P(info));
     472             :     } else {
     473             :         smart_string_appends(s, "[unknown-host]");
     474             :     }
     475             : 
     476           1 :     if (SG(request_info).request_uri) {
     477           0 :         smart_string_appends(s, SG(request_info).request_uri);
     478             :     }
     479           1 :     if (SG(request_info).query_string && SG(request_info).query_string[0]) {
     480             :         smart_string_appendc(s, '?');
     481           0 :         smart_string_appends(s, SG(request_info).query_string);
     482             :     }
     483             :     smart_string_appends(s, PHP_EOL);
     484             : 
     485             :     /* Cookie */
     486           1 :     if (SG(request_info).cookie_data) {
     487             :         smart_string_appends(s, "Cookie: ");
     488           0 :         smart_string_appends(s, SG(request_info).cookie_data);
     489             :         smart_string_appends(s, PHP_EOL);
     490             :     }
     491           1 : }
     492             : 
     493           0 : static zend_string *wmerrors_escape_html_entities(const char *old, size_t oldlen)
     494             : {
     495           0 :     return php_escape_html_entities((unsigned char*)old, oldlen, 0, ENT_COMPAT, NULL);
     496             : }
     497             : 
     498           0 : static void wmerrors_show_message(int type, wmerrors_error_filename *error_filename, const uint32_t error_lineno, wmerrors_message)
     499             : {
     500             :     php_stream *stream;
     501             :     zend_string *our_message;
     502           0 :     long maxlen = PHP_STREAM_COPY_ALL;
     503           0 :     smart_string expanded = { NULL };
     504             : 
     505             :     /* Open the message file */
     506           0 :     stream = php_stream_open_wrapper(WMERRORS_G(message_file), "rb",
     507             :             REPORT_ERRORS, NULL);
     508           0 :     if (!stream) {
     509           0 :         return;
     510             :     }
     511             : 
     512             : #if PHP_VERSION_ID < 80000
     513             :     va_list my_args;
     514             :     /* Don't destroy the caller's va_list */
     515           0 :     va_copy(my_args, args);
     516             : #endif
     517             : 
     518             :     /* Read the contents */
     519           0 :     our_message = php_stream_copy_to_mem(stream, maxlen, 0);
     520           0 :     php_stream_close(stream);
     521             : 
     522             :     /* Replace some tokens */
     523           0 :     for (char *p = ZSTR_VAL(our_message); p < ZSTR_VAL(our_message) + ZSTR_LEN(our_message); p++) {
     524           0 :         if (*p == '$') {
     525           0 :             if (!strncmp(p, "$file", sizeof("$file")-1)) {
     526           0 :                 zend_string *str = wmerrors_escape_html_entities(
     527             : #if PHP_VERSION_ID >= 80100
     528             :                     ZSTR_VAL(error_filename), ZSTR_LEN(error_filename)
     529             : #else
     530             :                     error_filename, strlen(error_filename)
     531             : #endif
     532             :                 );
     533           0 :                 smart_string_appendl(&expanded, ZSTR_VAL(str), ZSTR_LEN(str));
     534             :                 zend_string_release(str);
     535           0 :                 p += sizeof("file") - 1;
     536           0 :             } else if (!strncmp(p, "$line", sizeof("$line")-1)) {
     537           0 :                 smart_string_append_unsigned(&expanded, (zend_ulong)error_lineno);
     538           0 :                 p += sizeof("line") - 1;
     539           0 :             } else if (!strncmp(p, "$message", sizeof("$message")-1)) {
     540             : #if PHP_VERSION_ID >= 80000
     541             :                 zend_string *str = wmerrors_escape_html_entities(ZSTR_VAL(message), ZSTR_LEN(message));
     542             :                 smart_string_appendl(&expanded, ZSTR_VAL(str), ZSTR_LEN(str));
     543             : #else
     544             :                 /* Don't destroy args */
     545             :                 char *buf;
     546           0 :                 size_t len = vspprintf(&buf, 0, format, my_args);
     547           0 :                 zend_string *str = wmerrors_escape_html_entities(buf, len);
     548           0 :                 smart_string_appendl(&expanded, ZSTR_VAL(str), ZSTR_LEN(str));
     549           0 :                 efree(buf);
     550             : #endif
     551             :                 zend_string_release(str);
     552           0 :                 p += sizeof("message") - 1;
     553             :             } else {
     554             :                 smart_string_appendc(&expanded, '$');
     555             :             }
     556             :         } else {
     557           0 :             smart_string_appendc(&expanded, *p);
     558             :         }
     559             :     }
     560             : 
     561             :     /* Set headers */
     562           0 :     if (!SG(headers_sent)) {
     563           0 :         sapi_header_line ctr = {0};
     564             : 
     565           0 :         ctr.line = "HTTP/1.0 500 Internal Server Error";
     566           0 :         ctr.line_len = strlen(ctr.line);
     567           0 :         sapi_header_op(SAPI_HEADER_REPLACE, &ctr);
     568             :     }
     569             : 
     570             :     /* Write the message out */
     571           0 :     if (expanded.c) {
     572           0 :         php_write(expanded.c, expanded.len);
     573             :     }
     574             : 
     575             : 
     576             :     /* Clean up */
     577             :     smart_string_free(&expanded);
     578             : #if PHP_VERSION_ID < 80000
     579           0 :     va_end(my_args);
     580             : #endif
     581             :     zend_string_release(our_message);
     582             : }
     583             : 
     584           1 : static const char* wmerrors_error_type_to_string(int type) {
     585             :     /** Copied from php_error_cb() */
     586           1 :     switch (type) {
     587           1 :         case E_ERROR:
     588             :         case E_CORE_ERROR:
     589             :         case E_COMPILE_ERROR:
     590             :         case E_USER_ERROR:
     591           1 :             return "Fatal error";
     592           0 :         case E_RECOVERABLE_ERROR:
     593           0 :             return "Recoverable fatal error";
     594           0 :         case E_WARNING:
     595             :         case E_CORE_WARNING:
     596             :         case E_COMPILE_WARNING:
     597             :         case E_USER_WARNING:
     598           0 :             return "Warning";
     599             :             break;
     600           0 :         case E_PARSE:
     601           0 :             return "Parse error";
     602           0 :         case E_NOTICE:
     603             :         case E_USER_NOTICE:
     604           0 :             return "Notice";
     605           0 :         case E_STRICT:
     606           0 :             return "Strict Standards";
     607           0 :         case E_DEPRECATED:
     608             :         case E_USER_DEPRECATED:
     609           0 :             return "Deprecated";
     610           0 :         default:
     611           0 :             return "Unknown error";
     612             :     }
     613             : }
     614             : 
     615           0 : PHP_FUNCTION(wmerrors_malloc_test) {
     616             :     for (;;) {
     617           0 :         free(malloc(100));
     618             :     }
     619             : }
     620             : 
     621           0 : static void wmerrors_execute_file(int type, wmerrors_error_filename *error_filename, const uint32_t error_lineno, wmerrors_message) {
     622             :     /* Copy the error message into PG(...), as in php_error_cb(), so that the
     623             :      * invoked script can get the error details from error_get_last(). */
     624             : #if PHP_VERSION_ID < 80000
     625             :     char *buffer;
     626             :     size_t buffer_len;
     627             :     va_list my_args;
     628             : 
     629             :     /* Don't destroy the caller's va_list */
     630           0 :     va_copy(my_args, args);
     631           0 :     buffer_len = vspprintf(&buffer, PG(log_errors_max_len), format, my_args);
     632           0 :     va_end(my_args);
     633             : #endif
     634             : 
     635           0 :     if (PG(last_error_message)) {
     636           0 :         free(PG(last_error_message));
     637           0 :         PG(last_error_message) = NULL;
     638             :     }
     639           0 :     if (PG(last_error_file)) {
     640           0 :         free(PG(last_error_file));
     641           0 :         PG(last_error_file) = NULL;
     642             :     }
     643           0 :     if (!error_filename) {
     644             : #if PHP_VERSION_ID >= 80100
     645             :         error_filename = ZSTR_KNOWN(ZEND_STR_UNKNOWN_CAPITALIZED);
     646             : #else
     647           0 :         error_filename = "Unknown";
     648             : #endif
     649             :     }
     650           0 :     PG(last_error_type) = type;
     651             : #if PHP_VERSION_ID >= 80000
     652             :     PG(last_error_message) = zend_string_copy(message);
     653             : #else
     654           0 :     PG(last_error_message) = strndup(buffer, buffer_len);
     655             : #endif
     656             : #if PHP_VERSION_ID >= 80100
     657             :     PG(last_error_file) = zend_string_copy(error_filename);
     658             : #else
     659           0 :     PG(last_error_file) = strdup(error_filename);
     660             : #endif
     661           0 :     PG(last_error_lineno) = error_lineno;
     662             : 
     663             : #if PHP_VERSION_ID < 80000
     664           0 :     efree(buffer);
     665             : #endif
     666             : 
     667             :     /* Open the file and execute it as PHP.
     668             :      *
     669             :      * This part follows spl_autoload(), which is a rare example of an extension
     670             :      * invoking a PHP file.
     671             :      *
     672             :      * A comment in the PHP source states that it is unsafe to run userspace code
     673             :      * while handling an error. There are fewer reasons for that to be true now
     674             :      * than when that comment was written. The main remaining concern is that
     675             :      * the error handler may be invoked from within any emalloc() call. The global
     676             :      * state may be invalid at this time, since extensions generally do not expect
     677             :      * that emalloc() may execute userspace code.
     678             :      *
     679             :      * The PHP core will call userspace code on OOM only after zend_bailout() is
     680             :      * called, which resets the stack. That would probably be safer, but would
     681             :      * remove the ability for the called code to inspect the backtrace.
     682             :      */
     683             :     int ret;
     684             :     zval result;
     685             :     zend_file_handle file_handle;
     686             :     zend_op_array *new_op_array;
     687             : 
     688             : #if PHP_VERSION_ID >= 80100
     689             :     zend_stream_init_filename(&file_handle, WMERRORS_G(error_script_file));
     690             :     ret = php_stream_open_for_zend_ex(&file_handle, STREAM_OPEN_FOR_INCLUDE);
     691             : #else
     692           0 :     ret = php_stream_open_for_zend_ex(WMERRORS_G(error_script_file), &file_handle, STREAM_OPEN_FOR_INCLUDE);
     693             : #endif
     694           0 :     if (ret == SUCCESS) {
     695           0 :         new_op_array = zend_compile_file(&file_handle, ZEND_INCLUDE);
     696           0 :         if (new_op_array) {
     697           0 :             ZVAL_UNDEF(&result);
     698           0 :             zend_execute(new_op_array, &result);
     699           0 :             zend_destroy_file_handle(&file_handle);
     700           0 :             destroy_op_array(new_op_array);
     701           0 :             efree(new_op_array);
     702           0 :             zval_ptr_dtor(&result);
     703             :         }
     704             :     }
     705           0 : }

Generated by: LCOV version 1.13