diff --git a/actions/DetectAction.php b/actions/DetectAction.php index cc5a7975..dc6cdc61 100644 --- a/actions/DetectAction.php +++ b/actions/DetectAction.php @@ -47,7 +47,7 @@ class DetectAction implements ActionInterface $bridgeParams['format'] = $format; header('Location: ?action=display&' . http_build_query($bridgeParams), true, 301); - die(); + exit; } returnClientError('No bridge found for given URL: ' . $targetURL); diff --git a/actions/DisplayAction.php b/actions/DisplayAction.php index 6561ad8a..efcddb69 100644 --- a/actions/DisplayAction.php +++ b/actions/DisplayAction.php @@ -63,7 +63,7 @@ class DisplayAction implements ActionInterface unset($this->userData['_cache_timeout']); $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($this->userData); header('Location: ' . $uri, true, 301); - die(); + exit; } $cache_timeout = filter_var($this->userData['_cache_timeout'], FILTER_VALIDATE_INT); @@ -126,7 +126,7 @@ class DisplayAction implements ActionInterface if ($mtime <= $stime) { // Cached data is older or same header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304); - die(); + exit; } } @@ -193,7 +193,9 @@ class DisplayAction implements ActionInterface $items[] = $item; } elseif (Configuration::getConfig('error', 'output') === 'http') { header('Content-Type: text/html', true, $this->getReturnCode($e)); - die(buildTransformException($e, $bridge)); + $response = buildTransformException($e, $bridge); + print $response; + exit; } } } @@ -224,7 +226,9 @@ class DisplayAction implements ActionInterface } catch (\Throwable $e) { error_log($e); header('Content-Type: text/html', true, $e->getCode()); - die(buildTransformException($e, $bridge)); + $response = buildTransformException($e, $bridge); + print $response; + exit; } } } diff --git a/caches/SQLiteCache.php b/caches/SQLiteCache.php index e8d020a5..6a2273e9 100644 --- a/caches/SQLiteCache.php +++ b/caches/SQLiteCache.php @@ -13,7 +13,8 @@ class SQLiteCache implements CacheInterface public function __construct() { if (!extension_loaded('sqlite3')) { - die('"sqlite3" extension not loaded. Please check "php.ini"'); + print render('error.html.php', ['message' => '"sqlite3" extension not loaded. Please check "php.ini"']); + exit; } if (!is_writable(PATH_CACHE)) { @@ -25,12 +26,16 @@ class SQLiteCache implements CacheInterface $file = Configuration::getConfig(get_called_class(), 'file'); if (empty($file)) { - die('Configuration for ' . get_called_class() . ' missing. Please check your ' . FILE_CONFIG); + $message = sprintf('Configuration for %s missing. Please check your %s', get_called_class(), FILE_CONFIG); + print render('error.html.php', ['message' => $message]); + exit; } if (dirname($file) == '.') { $file = PATH_CACHE . $file; } elseif (!is_dir(dirname($file))) { - die('Invalid configuration for ' . get_called_class() . '. Please check your ' . FILE_CONFIG); + $message = sprintf('Invalid configuration for %s. Please check your %s', get_called_class(), FILE_CONFIG); + print render('error.html.php', ['message' => $message]); + exit; } if (!is_file($file)) { diff --git a/index.php b/index.php index b8dee496..2fc5771f 100644 --- a/index.php +++ b/index.php @@ -26,13 +26,7 @@ try { } } catch (\Throwable $e) { error_log($e); - - $code = $e->getCode(); - if ($code !== -1) { - header('Content-Type: text/plain', true, $code); - } - - $message = sprintf("Uncaught Exception %s: '%s'\n", get_class($e), $e->getMessage()); - - print $message; + print render('error.html.php', [ + 'message' => sprintf("Uncaught Exception %s: '%s'\n", get_class($e), $e->getMessage()), + ]); } diff --git a/lib/Authentication.php b/lib/Authentication.php index 1ae26edf..172836b2 100644 --- a/lib/Authentication.php +++ b/lib/Authentication.php @@ -58,7 +58,9 @@ class Authentication if (Configuration::getConfig('authentication', 'enable') === true) { if (!Authentication::verifyPrompt()) { header('WWW-Authenticate: Basic realm="RSS-Bridge"', true, 401); - die('Please authenticate in order to access this instance !'); + $message = 'Please authenticate in order to access this instance !'; + print $message; + exit; } } } diff --git a/lib/Configuration.php b/lib/Configuration.php index 2680ce3e..000b8bd5 100644 --- a/lib/Configuration.php +++ b/lib/Configuration.php @@ -317,7 +317,10 @@ final class Configuration */ private static function reportError($message) { - header('Content-Type: text/plain', true, 500); - die('Configuration error' . PHP_EOL . $message); + http_response_code(500); + print render('error.html.php', [ + 'message' => "Configuration error: $message", + ]); + exit; } } diff --git a/lib/html.php b/lib/html.php index 324b7dc2..fa448a31 100644 --- a/lib/html.php +++ b/lib/html.php @@ -12,6 +12,44 @@ * @link https://github.com/rss-bridge/rss-bridge */ +function render(string $template, array $context = []): string +{ + $context['page'] = render_template($template, $context); + return render_template('base.html.php', $context); +} + +function render_template(string $template, array $context = []): string +{ + if (isset($context['template'])) { + throw new \Exception("Don't use `template` as a context key"); + } + extract($context); + ob_start(); + try { + require __DIR__ . '/../templates/' . $template; + } catch (\Throwable $e) { + ob_end_clean(); + throw $e; + } + return ob_get_clean(); +} + +/** + * Escape for html context + */ +function e(string $s): string +{ + return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); +} + +/** + * Explicitly don't escape + */ +function raw(string $s): string +{ + return $s; +} + /** * Removes unwanted tags from a given HTML text. * diff --git a/templates/base.html.php b/templates/base.html.php new file mode 100644 index 00000000..9e0cc7ca --- /dev/null +++ b/templates/base.html.php @@ -0,0 +1,17 @@ + + + + + + + <?= e($title ?? 'RSS-Bridge') ?> + + + + +
+ +
+ + + diff --git a/templates/error.html.php b/templates/error.html.php new file mode 100644 index 00000000..4664b51d --- /dev/null +++ b/templates/error.html.php @@ -0,0 +1,9 @@ +
+

Something went wrong

+
+ +

+ +

+ +