Add error handling for external dependencies. (#55)
Protect functions that interact with databases and filesystems. Reviewed-on: https://gitea.subcultureofone.org/greg/tkr/pulls/55 Co-authored-by: Greg Sarjeant <greg@subcultureofone.org> Co-committed-by: Greg Sarjeant <greg@subcultureofone.org>
This commit is contained in:
		
							parent
							
								
									51abf33ad1
								
							
						
					
					
						commit
						d9d0ed9571
					
				| @ -34,16 +34,26 @@ $db = $prerequisites->getDatabase(); | |||||||
| 
 | 
 | ||||||
| // Make sure the initial setup is complete unless we're already heading to setup
 | // Make sure the initial setup is complete unless we're already heading to setup
 | ||||||
| if (!(preg_match('/setup$/', $path))) { | if (!(preg_match('/setup$/', $path))) { | ||||||
|     // Make sure required tables (user, settings) are populated
 |     try { | ||||||
|     $user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn(); |         // Make sure required tables (user, settings) are populated
 | ||||||
|     $settings_count = (int) $db->query("SELECT COUNT(*) FROM settings")->fetchColumn(); |         $user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn(); | ||||||
|  |         $settings_count = (int) $db->query("SELECT COUNT(*) FROM settings")->fetchColumn(); | ||||||
| 
 | 
 | ||||||
|     // If either required table has no records, redirect to setup.
 |         // If either required table has no records, redirect to setup.
 | ||||||
|     if ($user_count === 0 || $settings_count === 0){ |         if ($user_count === 0 || $settings_count === 0){ | ||||||
|         $init = require APP_ROOT . '/config/init.php'; |             $init = require APP_ROOT . '/config/init.php'; | ||||||
|         header('Location: ' . $init['base_path'] . 'setup'); |             header('Location: ' . $init['base_path'] . 'setup'); | ||||||
|  |             exit; | ||||||
|  |         } | ||||||
|  |     } catch (Exception $e) { | ||||||
|  |         // Database error during setup validation - show error page
 | ||||||
|  |         error_log("Database error during setup validation: " . $e->getMessage()); | ||||||
|  |         http_response_code(500); | ||||||
|  |         echo "<h1>Database Error</h1>"; | ||||||
|  |         echo "<p>Cannot validate setup status. The database may be corrupted or locked.</p>"; | ||||||
|  |         echo "<p>Please check your installation or contact your hosting provider.</p>"; | ||||||
|         exit; |         exit; | ||||||
|     }; |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
| @ -83,7 +93,7 @@ if ($method === 'POST' && $path != 'setup') { | |||||||
|         if (!Session::isValid($_POST['csrf_token'])) { |         if (!Session::isValid($_POST['csrf_token'])) { | ||||||
|             // Invalid session - redirect to /login
 |             // Invalid session - redirect to /login
 | ||||||
|             Log::info('Attempt to POST with invalid session. Redirecting to login.'); |             Log::info('Attempt to POST with invalid session. Redirecting to login.'); | ||||||
|             header('Location: ' . Util::buildRelativeUrl($app->config->basePath, 'login')); |             header('Location: ' . Util::buildRelativeUrl($app['config']->basePath, 'login')); | ||||||
|             exit; |             exit; | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|  | |||||||
| @ -112,29 +112,35 @@ class CssController extends Controller { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Set the theme back to default
 |         // Set the theme back to default
 | ||||||
|         $app['config']->cssId = null; |         try { | ||||||
|         $app['config'] = $app['config']->save(); |             $app['config']->cssId = null; | ||||||
| 
 |             $app['config'] = $app['config']->save(); | ||||||
|         // Set flash message
 |             Session::setFlashMessage('success', 'Theme ' . $cssFilename . ' deleted.'); | ||||||
|         Session::setFlashMessage('success', 'Theme ' . $cssFilename . ' deleted.'); |         } catch (Exception $e) { | ||||||
|  |             Log::error("Failed to update config after deleting theme: " . $e->getMessage()); | ||||||
|  |             Session::setFlashMessage('error', 'Theme deleted but failed to update settings'); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function handleSetTheme() { |     private function handleSetTheme() { | ||||||
|         global $app; |         global $app; | ||||||
| 
 | 
 | ||||||
|         if ($_POST['selectCssFile']){ |         try { | ||||||
|             // Set custom theme
 |             if ($_POST['selectCssFile']){ | ||||||
|             $app['config']->cssId = $_POST['selectCssFile']; |                 // Set custom theme
 | ||||||
|         } else { |                 $app['config']->cssId = $_POST['selectCssFile']; | ||||||
|             // Set default theme
 |             } else { | ||||||
|             $app['config']->cssId = null; |                 // Set default theme
 | ||||||
|  |                 $app['config']->cssId = null; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Update the site theme
 | ||||||
|  |             $app['config'] = $app['config']->save(); | ||||||
|  |             Session::setFlashMessage('success', 'Theme applied.'); | ||||||
|  |         } catch (Exception $e) { | ||||||
|  |             Log::error("Failed to save theme setting: " . $e->getMessage()); | ||||||
|  |             Session::setFlashMessage('error', 'Failed to apply theme'); | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         // Update the site theme
 |  | ||||||
|         $app['config'] = $app['config']->save(); |  | ||||||
| 
 |  | ||||||
|         // Set flash message
 |  | ||||||
|         Session::setFlashMessage('success', 'Theme applied.'); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function handleUpload() { |     private function handleUpload() { | ||||||
|  | |||||||
| @ -5,10 +5,15 @@ class FeedController extends Controller { | |||||||
|     public function __construct() { |     public function __construct() { | ||||||
|         global $app; |         global $app; | ||||||
|          |          | ||||||
|         $tickModel = new TickModel($app['db'], $app['config']); |         try { | ||||||
|         $this->ticks = $tickModel->getPage($app['config']->itemsPerPage); |             $tickModel = new TickModel($app['db'], $app['config']); | ||||||
| 
 |             $this->ticks = $tickModel->getPage($app['config']->itemsPerPage); | ||||||
|         Log::debug("Loaded " . count($this->ticks) . " ticks for feeds"); |             Log::debug("Loaded " . count($this->ticks) . " ticks for feeds"); | ||||||
|  |         } catch (Exception $e) { | ||||||
|  |             Log::error("Failed to load ticks for feed: " . $e->getMessage()); | ||||||
|  |             // Provide empty feed rather than crashing - RSS readers can handle this
 | ||||||
|  |             $this->ticks = []; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function rss(){ |     public function rss(){ | ||||||
|  | |||||||
| @ -19,7 +19,12 @@ class Log { | |||||||
|         // (should be handled by Prerequisites, but doesn't hurt)
 |         // (should be handled by Prerequisites, but doesn't hurt)
 | ||||||
|         $logDir = dirname(self::$logFile); |         $logDir = dirname(self::$logFile); | ||||||
|         if (!is_dir($logDir)) { |         if (!is_dir($logDir)) { | ||||||
|             mkdir($logDir, 0770, true); |             try { | ||||||
|  |                 mkdir($logDir, 0770, true); | ||||||
|  |             } catch (Exception $e) { | ||||||
|  |                 // Fall back to error_log if we can't create log directory
 | ||||||
|  |                 error_log("Failed to create log directory {$logDir}: " . $e->getMessage()); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -61,35 +66,45 @@ class Log { | |||||||
|         $logEntry = "[{$timestamp}] {$level}: " . Util::getClientIp() . "{$context} - {$message}\n"; |         $logEntry = "[{$timestamp}] {$level}: " . Util::getClientIp() . "{$context} - {$message}\n"; | ||||||
| 
 | 
 | ||||||
|         // Rotate if we're at the max file size (1000 lines)
 |         // Rotate if we're at the max file size (1000 lines)
 | ||||||
|         if (file_exists(self::$logFile)) { |         try { | ||||||
|             $lineCount = count(file(self::$logFile)); |             if (file_exists(self::$logFile)) { | ||||||
|             if ($lineCount >= self::$maxLines) { |                 $lineCount = count(file(self::$logFile)); | ||||||
|                 self::rotate(); |                 if ($lineCount >= self::$maxLines) { | ||||||
|                 Log::info("Log rotated at {$timestamp}"); |                     self::rotate(); | ||||||
|  |                     Log::info("Log rotated at {$timestamp}"); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         file_put_contents(self::$logFile, $logEntry, FILE_APPEND | LOCK_EX); |             file_put_contents(self::$logFile, $logEntry, FILE_APPEND | LOCK_EX); | ||||||
|  |         } catch (Exception $e) { | ||||||
|  |             // Fall back to error_log if file operations fail
 | ||||||
|  |             error_log("Log write failed: " . $e->getMessage() . " - Original message: " . trim($logEntry)); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static function rotate() { |     private static function rotate() { | ||||||
|         // Rotate existing history files: tkr.4.log -> tkr.5.log, etc.
 |         try { | ||||||
|         for ($i = self::$maxFiles - 1; $i >= 1; $i--) { |             // Rotate existing history files: tkr.4.log -> tkr.5.log, etc.
 | ||||||
|             $oldFile = self::$logFile . '.' . $i; |             for ($i = self::$maxFiles - 1; $i >= 1; $i--) { | ||||||
|             $newFile = self::$logFile . '.' . ($i + 1); |                 $oldFile = self::$logFile . '.' . $i; | ||||||
|  |                 $newFile = self::$logFile . '.' . ($i + 1); | ||||||
| 
 | 
 | ||||||
|             if (file_exists($oldFile)) { |                 if (file_exists($oldFile)) { | ||||||
|                 if ($i == self::$maxFiles - 1) { |                     if ($i == self::$maxFiles - 1) { | ||||||
|                     unlink($oldFile); // Delete oldest log if we already have 5 files of history
 |                         unlink($oldFile); // Delete oldest log if we already have 5 files of history
 | ||||||
|                 } else { |                     } else { | ||||||
|                     rename($oldFile, $newFile); // Bump the file number up by one
 |                         rename($oldFile, $newFile); // Bump the file number up by one
 | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         // Move current active log to .1
 |             // Move current active log to .1
 | ||||||
|         if (file_exists(self::$logFile)) { |             if (file_exists(self::$logFile)) { | ||||||
|             rename(self::$logFile, self::$logFile . '.1'); |                 rename(self::$logFile, self::$logFile . '.1'); | ||||||
|  |             } | ||||||
|  |         } catch (Exception $e) { | ||||||
|  |             // Log rotation failure - not critical, just continue
 | ||||||
|  |             error_log("Log rotation failed: " . $e->getMessage()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user