diff --git a/config/config.php b/config/config.php index bdda1a02..a252a556 100755 --- a/config/config.php +++ b/config/config.php @@ -7,6 +7,7 @@ define('RASPI_CONFIG_PROVIDERS', 'config/vpn-providers.json'); define('RASPI_ADMIN_DETAILS', RASPI_CONFIG.'/raspap.auth'); define('RASPI_WIFI_AP_INTERFACE', 'wlan0'); define('RASPI_CACHE_PATH', sys_get_temp_dir() . '/raspap'); +define('RASPI_ERROR_LOG', sys_get_temp_dir() . '/raspap_error.log'); define('RASPI_DEBUG_LOG', 'raspap_debug.log'); define('RASPI_LOG_SIZE_LIMIT', 64); diff --git a/includes/defaults.php b/includes/defaults.php index d7b2972e..317b838b 100755 --- a/includes/defaults.php +++ b/includes/defaults.php @@ -12,6 +12,7 @@ $defaults = [ 'RASPI_ADMIN_DETAILS' => RASPI_CONFIG.'/raspap.auth', 'RASPI_WIFI_AP_INTERFACE' => 'wlan0', 'RASPI_CACHE_PATH' => sys_get_temp_dir() . '/raspap', + 'RASPI_ERROR_LOG' => sys_get_temp_dir() . '/raspap_error.log', 'RASPI_DEBUG_LOG' => 'raspap_debug.log', 'RASPI_LOG_SIZE_LIMIT' => 64, diff --git a/includes/exceptions.php b/includes/exceptions.php new file mode 100755 index 00000000..c52c04ed --- /dev/null +++ b/includes/exceptions.php @@ -0,0 +1,6 @@ + + diff --git a/index.php b/index.php index 9b755744..1137c3a3 100755 --- a/index.php +++ b/index.php @@ -26,6 +26,7 @@ require 'includes/csrf.php'; ensureCSRFSessionToken(); +require_once 'includes/exceptions.php'; require_once 'includes/config.php'; require_once 'includes/autoload.php'; require_once 'includes/defaults.php'; diff --git a/locale/en_US/LC_MESSAGES/messages.mo b/locale/en_US/LC_MESSAGES/messages.mo index 0b0865ab..d0cd5812 100644 Binary files a/locale/en_US/LC_MESSAGES/messages.mo and b/locale/en_US/LC_MESSAGES/messages.mo differ diff --git a/locale/en_US/LC_MESSAGES/messages.po b/locale/en_US/LC_MESSAGES/messages.po index 7053ad78..3c403635 100644 --- a/locale/en_US/LC_MESSAGES/messages.po +++ b/locale/en_US/LC_MESSAGES/messages.po @@ -1520,3 +1520,11 @@ msgstr "Update complete" msgid "An error occurred. Check the log at /tmp/raspap_install.log" msgstr "An error occurred. Check the log at /tmp/raspap_install.log" +#: includes/exceptions.php + +msgid "RaspAP Exception" +msgstr "RaspAP Exception" + +msgid "An exception occurred" +msgstr "An exception occurred" + diff --git a/src/RaspAP/Exceptions/ExceptionHandler.php b/src/RaspAP/Exceptions/ExceptionHandler.php new file mode 100755 index 00000000..d9cf74e9 --- /dev/null +++ b/src/RaspAP/Exceptions/ExceptionHandler.php @@ -0,0 +1,69 @@ + + * @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE + * @see + */ + +declare(strict_types=1); + +namespace RaspAP\Exceptions; + +use RaspAP\Exceptions\HtmlErrorRenderer; + +class ExceptionHandler +{ + + public function __construct() + { + $this->setExceptionHandler(); + $this->setShutdownHandler(); + } + + public static function handleException($exception) + { + $errorMessage = ( + '[' . date('Y-m-d H:i:s') . '] ' . + $exception->getMessage() . ' in ' . + $exception->getFile() . ' on line ' . + $exception->getLine() . PHP_EOL + ); + // Log the exception + error_log($errorMessage, 3, RASPI_ERROR_LOG); + + $renderer = new HtmlErrorRenderer(); + $renderer->render($exception); + } + + public static function handleShutdown() + { + $error = error_get_last(); + if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) { + $errorMessage = ( + '[' . date('Y-m-d H:i:s') . '] ' . + $error['message'] . ' in ' . + $error['file'] . ' on line ' . + $error['line'] . PHP_EOL + ); + error_log($errorMessage, 3, RASPI_ERROR_LOG); + + $renderer = new HtmlErrorRenderer(); + $renderer->render($exception); + } + } + + protected function setExceptionHandler() + { + set_exception_handler(array($this, 'handleException')); + } + + protected function setShutdownHandler() + { + register_shutdown_function(array($this, 'handleShutdown')); + } +} + diff --git a/src/RaspAP/Exceptions/HtmlErrorRenderer.php b/src/RaspAP/Exceptions/HtmlErrorRenderer.php new file mode 100755 index 00000000..058a2478 --- /dev/null +++ b/src/RaspAP/Exceptions/HtmlErrorRenderer.php @@ -0,0 +1,106 @@ + + * @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE + * @see + */ + +declare(strict_types=1); + +namespace RaspAP\Exceptions; + +class HtmlErrorRenderer +{ + + public function __construct() + { + $this->charset = 'UTF-8'; + $this->projectDir = $_SERVER['DOCUMENT_ROOT']; + $this->template = '/templates/exception.php'; + $this->debug = true; + } + + public function render($exception) + { + $message = $exception->getMessage(); + $code = $exception->getCode(); + $file = $exception->getFile(); + $line = $exception->getLine(); + $trace = $this->getExceptionTraceAsString($exception); + + header('Content-Type: text/html; charset='.$this->charset); + if ($this->debug) { + header('X-Debug-Exception: '. rawurlencode($message)); + header('X-Debug-Exception-File: '. rawurlencode($file).':'.$line); + } + $__template_data = compact( + "message", + "code", + "file", + "line", + "trace" + ); + if (is_array($__template_data)) { + extract($__template_data); + } + $file = $this->projectDir . $this->template; + ob_start(); + include $file; + echo ob_get_clean(); + } + + /** + * Improved exception trace + * @param object $e : exception + * @param array $seen : passed to recursive calls to accumulate trace lines already seen + * @return array of strings, one entry per trace line + * @see https://github.com/php/php-src/blob/master/Zend/zend_exceptions.c + */ + public function getExceptionTraceAsString($e, $seen = null) { + + $starter = $seen ? 'Thrown by: ' : ''; + $result = array(); + if (!$seen) $seen = array(); + $trace = $e->getTrace(); + $prev = $e->getPrevious(); + $result[] = sprintf('%s%s: %s', $starter, get_class($e), $e->getMessage()); + $file = $e->getFile(); + $line = $e->getLine(); + + while (true) { + $current = "$file:$line"; + if (is_array($seen) && in_array($current, $seen)) { + $result[] = sprintf(' ... %d more', count($trace)+1); + break; + } + $result[] = sprintf(' at %s%s%s(%s%s%s)', + count($trace) && array_key_exists('class', $trace[0]) ? str_replace('\\', '.', $trace[0]['class']) : '', + count($trace) && array_key_exists('class', $trace[0]) && array_key_exists('function', $trace[0]) ? '.' : '', + count($trace) && array_key_exists('function', $trace[0]) ? str_replace('\\', '.', $trace[0]['function']) : '(main)', + $line === null ? $file : basename($file), + $line === null ? '' : ':', + $line === null ? '' : $line); + + if (is_array($seen)) { + $seen[] = "$file:$line"; + } + if (!count($trace)) { + break; + } + $file = array_key_exists('file', $trace[0]) ? $trace[0]['file'] : 'Unknown Source'; + $line = array_key_exists('file', $trace[0]) && array_key_exists('line', $trace[0]) && $trace[0]['line'] ? $trace[0]['line'] : null; + array_shift($trace); + } + $result = join(PHP_EOL, $result); + if ($prev) { + $result .= PHP_EOL . getExceptionTraceAsString($prev, $seen); + } + return $result; + } + +} + diff --git a/templates/exception.php b/templates/exception.php new file mode 100755 index 00000000..5d5c8c49 --- /dev/null +++ b/templates/exception.php @@ -0,0 +1,30 @@ + + + + + + <?php echo _("RaspAP Exception"); ?> + + + + + +
+
+
+
+
+

+
+
+

+
Stack trace:
+
+
+
+
+
+
+ + +