From 5eb8df7432a708284862e4b126e418265d36b4ab Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 2 May 2022 18:49:09 +0200 Subject: [inc/Util] Add types, move error printing functions to their own class --- inc/util.inc.php | 221 ++++++++++--------------------------------------------- 1 file changed, 38 insertions(+), 183 deletions(-) (limited to 'inc/util.inc.php') diff --git a/inc/util.inc.php b/inc/util.inc.php index c3e70f89..d95265f4 100644 --- a/inc/util.inc.php +++ b/inc/util.inc.php @@ -4,159 +4,18 @@ class Util { private static $redirectParams = array(); - /** - * Displays an error message and stops script execution. - * If CONFIG_DEBUG is true, it will also dump a stack trace - * and all globally defined variables. - * (As this might reveal sensitive data you should never enable it in production) - */ - public static function traceError($message) - { - if ((defined('API') && API) || (defined('AJAX') && AJAX) || php_sapi_name() === 'cli') { - error_log('API ERROR: ' . $message); - error_log(self::formatBacktracePlain(debug_backtrace())); - } - if (php_sapi_name() === 'cli') { - // Don't spam HTML when invoked via cli, above error_log should have gone to stdout/stderr - exit(1); - } - Header('HTTP/1.1 500 Internal Server Error'); - if (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'html') === false ) { - Header('Content-Type: text/plain; charset=utf-8'); - echo 'API ERROR: ', $message, "\n", self::formatBacktracePlain(debug_backtrace()); - exit(0); - } - Header('Content-Type: text/html; charset=utf-8'); - echo 'Fatal Error'; - echo '

Flagrant System error

'; - echo "

Message

$message
"; - if (strpos($message, 'Database') !== false) { - echo '
Try running database setup
'; - } - echo "

"; - if (defined('CONFIG_DEBUG') && CONFIG_DEBUG) { - global $SLX_ERRORS; - if (!empty($SLX_ERRORS)) { - echo '

PHP Errors

';
-				foreach ($SLX_ERRORS as $error) {
-					echo htmlspecialchars("{$error['errstr']} ({$error['errfile']}:{$error['errline']}\n");
-				}
-				echo '
'; - } - echo "

Stack Trace

"; - echo '
', self::formatBacktraceHtml(debug_backtrace()), '
'; - echo "

Globals

";
-			echo htmlspecialchars(print_r($GLOBALS, true));
-			echo '
'; - } else { - echo << -________________________¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶________ -____________________¶¶¶___________________¶¶¶¶_____ -________________¶¶¶_________________________¶¶¶¶___ -______________¶¶______________________________¶¶¶__ -___________¶¶¶_________________________________¶¶¶_ -_________¶¶_____________________________________¶¶¶ -________¶¶_________¶¶¶¶¶___________¶¶¶¶¶_________¶¶ -______¶¶__________¶¶¶¶¶¶__________¶¶¶¶¶¶_________¶¶ -_____¶¶___________¶¶¶¶____________¶¶¶¶___________¶¶ -____¶¶___________________________________________¶¶ -___¶¶___________________________________________¶¶_ -__¶¶____________________¶¶¶¶____________________¶¶_ -_¶¶_______________¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶______________¶¶__ -_¶¶____________¶¶¶¶___________¶¶¶¶¶___________¶¶___ -¶¶¶_________¶¶¶__________________¶¶__________¶¶____ -¶¶_________¶______________________¶¶________¶¶_____ -¶¶¶______¶________________________¶¶_______¶¶______ -¶¶¶_____¶_________________________¶¶_____¶¶________ -_¶¶¶___________________________________¶¶__________ -__¶¶¶________________________________¶¶____________ -___¶¶¶____________________________¶¶_______________ -____¶¶¶¶______________________¶¶¶__________________ -_______¶¶¶¶¶_____________¶¶¶¶¶_____________________ - -SADFACE; - } - echo ''; - exit(0); - } - - private static function formatArgument($arg, $expandArray = true) - { - if (is_string($arg)) { - $arg = "'$arg'"; - } elseif (is_object($arg)) { - $arg = 'instanceof ' . get_class($arg); - } elseif (is_array($arg)) { - if ($expandArray && count($arg) < 20) { - $expanded = ''; - foreach ($arg as $key => $value) { - if (!empty($expanded)) { - $expanded .= ', '; - } - $expanded .= $key . ': ' . self::formatArgument($value, false); - if (strlen($expanded) > 200) - break; - } - if (strlen($expanded) <= 200) - return '[' . $expanded . ']'; - } - $arg = 'Array(' . count($arg) . ')'; - } - return $arg; - } - - public static function formatBacktraceHtml($trace) - { - $output = ''; - foreach ($trace as $idx => $line) { - $args = array(); - foreach ($line['args'] as $arg) { - $arg = self::formatArgument($arg); - $args[] = '' . htmlspecialchars($arg) . ''; - } - $frame = str_pad('#' . $idx, 3, ' ', STR_PAD_LEFT); - $function = htmlspecialchars($line['function']); - $args = implode(', ', $args); - $file = preg_replace('~(/[^/]+)$~', '$1', htmlspecialchars($line['file'])); - // Add line - $output .= $frame . ' ' . $function . '(' - . $args . ')' . ' @ ' . $file . ':' . $line['line'] . "\n"; - } - return $output; - } - - public static function formatBacktracePlain($trace) - { - $output = ''; - foreach ($trace as $idx => $line) { - $args = array(); - foreach ($line['args'] as $arg) { - $args[] = self::formatArgument($arg); - } - $frame = str_pad('#' . $idx, 3, ' ', STR_PAD_LEFT); - $args = implode(', ', $args); - // Add line - $output .= "\n" . $frame . ' ' . $line['function'] . '(' - . $args . ')' . ' @ ' . $line['file'] . ':' . $line['line']; - } - return $output; - } - /** * Redirects the user via a '302 Moved' header. * An active session will be saved, any messages that haven't * been displayed yet will be appended to the redirect. + * * @param string|false $location Location to redirect to. "false" to redirect to same URL (useful after POSTs) * @param bool $preferRedirectPost if true, use the value from $_POST['redirect'] instead of $location */ - public static function redirect($location = false, $preferRedirectPost = false) + public static function redirect($location = false, bool $preferRedirectPost = false) { if ($location === false) { - $location = preg_replace('/([&?])message\[\]\=[^&]*/', '\1', $_SERVER['REQUEST_URI']); + $location = preg_replace('/([&?])message\[\]=[^&]*/', '\1', $_SERVER['REQUEST_URI']); } $messages = Message::toRequest(); if ($preferRedirectPost @@ -189,7 +48,7 @@ SADFACE; exit(0); } - public static function addRedirectParam($key, $value) + public static function addRedirectParam(string $key, string $value) { self::$redirectParams[] = $key . '=' . urlencode($value); } @@ -201,7 +60,7 @@ SADFACE; * token, this function will return false and display an error. * If the token matches, or the user is not logged in, it will return true. */ - public static function verifyToken() + public static function verifyToken(): bool { if (!User::isLoggedIn() && Session::get('token') === false) return true; @@ -218,12 +77,12 @@ SADFACE; * _word_ is underlined * \n is line break */ - public static function markup($string) + public static function markup(string $string): string { $string = htmlspecialchars($string); - $string = preg_replace('#(^|[\n \-_/\.])\*(.+?)\*($|[ \-_/\.\!\?,:])#is', '$1$2$3', $string); - $string = preg_replace('#(^|[\n \-\*/\.])_(.+?)_($|[ \-\*/\.\!\?,:])#is', '$1$2$3', $string); - $string = preg_replace('#(^|[\n \-_\*\.])/(.+?)/($|[ \-_\*\.\!\?,:])#is', '$1$2$3', $string); + $string = preg_replace('#(^|[\n \-_/.])\*(.+?)\*($|[ \-_/.!?,:])#is', '$1$2$3', $string); + $string = preg_replace('#(^|[\n \-*/.])_(.+?)_($|[ \-*/.!?,:])#is', '$1$2$3', $string); + $string = preg_replace('#(^|[\n \-_*.])/(.+?)/($|[ \-_*.!?,:])#is', '$1$2$3', $string); return nl2br($string); } @@ -236,7 +95,7 @@ SADFACE; * @param int $shift how many units to skip, i.e. if you pass in KiB or MiB * @return string human readable string representing the given file size */ - public static function readableFileSize($bytes, $decimals = -1, $shift = 0) + public static function readableFileSize(float $bytes, int $decimals = -1, int $shift = 0): string { // round doesn't reliably work for large floats, pick workaround depending on OS if (PHP_INT_SIZE === 4) { @@ -257,17 +116,26 @@ SADFACE; return sprintf("%.{$decimals}f", $bytes) . "\xe2\x80\x89" . $sz[$factor + $shift]; } - public static function sanitizeFilename($name) + public static function sanitizeFilename(string $name) { return preg_replace('/[^a-zA-Z0-9_\-]+/', '_', $name); } - public static function safePath($path, $prefix = '') + /** + * Make sure given path is not absolute, and does not contain '..', or weird characters. + * Returns sanitized path, or false if invalid. If prefix is given, also make sure + * $path starts with it. + * + * @param string $path path to check for safety + * @param string $prefix required prefix of $path + * @return false|string + */ + public static function safePath(string $path, string $prefix = '') { if (empty($path)) return false; $path = trim($path); - if ($path[0] == '/' || preg_match('/[\x00-\x19\?\*]/', $path)) + if ($path[0] == '/' || preg_match('/[\x00-\x19?*]/', $path)) return false; if (strpos($path, '..') !== false) return false; @@ -288,7 +156,7 @@ SADFACE; * @param int $code the code to turn into an error description * @return string the error description */ - public static function uploadErrorString($code) + public static function uploadErrorString(int $code): string { switch ($code) { case UPLOAD_ERR_INI_SIZE: @@ -326,7 +194,7 @@ SADFACE; * @param string $ip_addr input to check * @return boolean true iff $ip_addr is a valid public ipv4 address */ - public static function isPublicIpv4($ip_addr) + public static function isPublicIpv4(string $ip_addr): bool { if (!preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $ip_addr)) return false; @@ -347,23 +215,6 @@ SADFACE; return true; } - /** - * Check whether $arrax contains all keys given in $keyList - * @param array $array An array - * @param array $keyList A list of strings which must all be valid keys in $array - * @return boolean - */ - public static function hasAllKeys($array, $keyList) - { - if (!is_array($array)) - return false; - foreach ($keyList as $key) { - if (!isset($array[$key])) - return false; - } - return true; - } - /** * Send a file to user for download. * @@ -374,7 +225,7 @@ SADFACE; * true: error while reading the file * - on success, the function does not return */ - public static function sendFile($file, $name, $delete = false) + public static function sendFile(string $file, string $name, bool $delete = false): bool { while ((@ob_get_level()) > 0) @ob_end_clean(); @@ -404,7 +255,7 @@ SADFACE; * @param bool $secure true = only use strong random sources * @return string|bool string of requested length, false on error */ - public static function randomBytes($length, $secure = true) + public static function randomBytes(int $length, bool $secure = true) { if (function_exists('random_bytes')) { try { @@ -453,7 +304,7 @@ SADFACE; /** * @return string a random UUID, v4. */ - public static function randomUuid() + public static function randomUuid(): string { $b = unpack('h8a/h4b/h12c', self::randomBytes(12)); return sprintf('%08s-%04s-%04x-%04x-%012s', @@ -481,10 +332,11 @@ SADFACE; /** * Transform timestamp to easily readable string. * The format depends on how far the timestamp lies in the past. + * * @param int $ts unix timestamp * @return string human readable representation */ - public static function prettyTime($ts) + public static function prettyTime(int $ts): string { settype($ts, 'int'); if ($ts === 0) @@ -507,10 +359,11 @@ SADFACE; /** * Return localized strings for yes or no depending on $bool + * * @param bool $bool Input to evaluate * @return string Yes or No, in user's selected language */ - public static function boolToString($bool) + public static function boolToString(bool $bool): string { if ($bool) return Dictionary::translate('lang_yes', true); @@ -519,11 +372,12 @@ SADFACE; /** * Format a duration, in seconds, into a readable string. + * * @param int $seconds The number to format - * @param int $showSecs whether to show seconds, or rather cut after minutes + * @param bool $showSecs whether to show seconds, or rather cut after minutes * @return string */ - public static function formatDuration($seconds, $showSecs = true) + public static function formatDuration(int $seconds, bool $showSecs = true): string { settype($seconds, 'int'); static $UNITS = ['y' => 31536000, 'mon' => 2592000, 'd' => 86400]; @@ -550,9 +404,10 @@ SADFACE; * was a weird problem where firefox would keep sending a cookie with * path /slx-admin/ but trying to delete it from /slx-admin, which php's * setcookie automatically sends by default, did not clear it. + * * @param string $name cookie name */ - public static function clearCookie($name) + public static function clearCookie(string $name) { $parts = explode('/', $_SERVER['SCRIPT_NAME']); $path = ''; @@ -567,7 +422,7 @@ SADFACE; /** * Remove any non-utf8 sequences from string. */ - public static function cleanUtf8(string $string) : string + public static function cleanUtf8(string $string): string { // https://stackoverflow.com/a/1401716/2043481 $regex = '/ @@ -586,7 +441,7 @@ SADFACE; /** * Remove non-printable < 0x20 chars from ANSI string, then convert to UTF-8 */ - public static function ansiToUtf8(string $string) : string + public static function ansiToUtf8(string $string): string { $regex = '/ ( -- cgit v1.2.3-55-g7522