connect_error) { die('数据库连接失败: ' . $db->connect_error); } $db->set_charset('utf8mb4'); } return $db; } function e($str) { return htmlspecialchars($str ?? '', ENT_QUOTES, 'UTF-8'); } function redirect($url) { header('Location: ' . $url); exit; } function json_response($data, $code = 200) { http_response_code($code); header('Content-Type: application/json; charset=utf-8'); echo json_encode($data, JSON_UNESCAPED_UNICODE); exit; } /** * CSRF Protection */ function csrf_token() { if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } return $_SESSION['csrf_token']; } function csrf_field() { return ''; } function csrf_verify($method = 'POST') { if ($method === 'POST' || $method === 'BOTH') { $token = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? ''; if (empty($token) || !hash_equals($_SESSION['csrf_token'] ?? '', $token)) { http_response_code(403); echo 'CSRF token mismatch'; exit; } } } /** * Authentication */ function get_admin_user() { return get_setting('admin_user', 'admin'); } function get_admin_hash() { return get_setting('admin_hash', ''); } function verify_admin_password($password) { $hash = get_admin_hash(); if (empty($hash)) { // Fallback: no hash set yet, use legacy plain-text check // After first settings save this branch is never reached return false; } return password_verify($password, $hash); } function set_admin_password($password) { $hash = password_hash($password, PASSWORD_DEFAULT); update_setting('admin_hash', $hash); } function auth_check() { if (empty($_SESSION['admin'])) { redirect('login.php'); } } function auth_verify($username, $password) { if ($username !== get_admin_user()) { return false; } return verify_admin_password($password); } function get_setting($key, $default = '') { $db = get_db(); $stmt = $db->prepare("SELECT value FROM settings WHERE `key` = ?"); $stmt->bind_param('s', $key); $stmt->execute(); $result = $stmt->get_result(); if ($row = $result->fetch_assoc()) { return $row['value']; } return $default; } function update_setting($key, $value) { $db = get_db(); $stmt = $db->prepare("INSERT INTO settings (`key`, value) VALUES (?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value)"); $stmt->bind_param('ss', $key, $value); return $stmt->execute(); } function time_ago($datetime) { $timestamp = strtotime($datetime); $diff = time() - $timestamp; if ($diff < 60) return '刚刚'; if ($diff < 3600) return floor($diff / 60) . '分钟前'; if ($diff < 86400) return floor($diff / 3600) . '小时前'; if ($diff < 2592000) return floor($diff / 86400) . '天前'; return date('Y-m-d', $timestamp); } /** * HTML sanitizer - prevent XSS in rich-text content * Uses DOMDocument + whitelist approach (no external dependencies needed) */ function sanitize_html($html) { if (empty($html)) return ''; // Step 1: Remove dangerous tags via regex pre-clean (before DOM parsing) $html = preg_replace('#<(script|style|iframe|object|embed|form|input|button|select|textarea)[^>]*>.*?\1>#is', '', $html); // Step 2: Remove event-handler attributes (onclick, onerror, etc.) $html = preg_replace('/\s+on\w+\s*=\s*["\'][^"\']*["\']/i', '', $html); // Step 3: Use DOMDocument to normalize and re-serialize libxml_use_internal_errors(true); $dom = new DOMDocument(); // Wrap in a div so we can use loadHTML on fragment $dom->loadHTML('' . "\n" . '