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 : }
|