Files
MES/index.php
T
2026-08-08 18:28:49 +08:00

154 lines
6.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// 应用目录为当前目录
header('Content-Type: text/html; charset=utf-8');
// 禁止 CDN 和浏览器缓存动态页面(确保修改即时生效)
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
ini_set('default_charset', 'UTF-8');
// ===== Session 安全配置(必须在 session_start 之前) =====
ini_set('session.cookie_httponly', '1');
// 关闭 strict_mode — strict_mode=1 会导致:
// 浏览器带着一个已销毁的 Session Cookie 来时,PHP 静默创建新 Session
// 但补发的 Set-Cookie 只能在下次请求生效,造成当前请求 Session 为空
ini_set('session.use_strict_mode', '0');
ini_set('session.use_only_cookies', '1');
// P2-8 安全加固:会话 Cookie 统一使用 SameSite=Lax(降低 CSRF 风险)
// 说明:原 SameSite=None 仅用于跨站 iframe 场景;若前台工位确需嵌入第三方站点的 iframe,
// 可在此改回 'None',但这会重新引入 CSRF 风险,请谨慎评估。
$isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443);
if ($isHttps) {
ini_set('session.cookie_secure', '1');
}
ini_set('session.cookie_samesite', 'Lax');
// ===== 自动修复 session 存储目录(LNMP 重装后目录可能丢失) =====
$sessionPath = ini_get('session.save_path');
if ($sessionPath && !is_dir($sessionPath)) {
// php.ini 指定的 session 目录不存在,尝试创建
@mkdir($sessionPath, 0777, true);
}
// 如果创建失败或路径为空,回退到系统临时目录
if (!$sessionPath || !is_dir($sessionPath) || !is_writable($sessionPath)) {
$fallbackPath = sys_get_temp_dir() . '/php_sessions';
if (!is_dir($fallbackPath)) {
@mkdir($fallbackPath, 0777, true);
}
if (is_dir($fallbackPath) && is_writable($fallbackPath)) {
ini_set('session.save_path', $fallbackPath);
session_save_path($fallbackPath);
}
}
// ===== 处理浏览器带来的旧 Session Cookie(避免 Session 文件不存在导致数据丢失) =====
$cookieSid = $_COOKIE[session_name()] ?? null;
if ($cookieSid) {
// 检查对应的 session 文件是否存在
$sessionFile = ini_get('session.save_path') . '/sess_' . $cookieSid;
if (!file_exists($sessionFile)) {
// Session 文件已被销毁(如被 session_minimal.php 清除),
// 浏览器还带着旧 Cookie。先设置 session_id 让它复用旧 ID
// session_start() 会创建新文件(strict_mode=0 允许)
session_id($cookieSid);
}
}
session_start();
// ===== 补发 Session Cookie(兼容某些浏览器 Cookie 丢失场景) =====
// P2-11 安全加固:仅在「请求已携带会话 Cookie 且当前 Session ID 发生变化」
// (如登录/会话重生成)时补发,避免每个响应都重复发送 Set-Cookie。
// 全新会话由 session_start() 自动发送,此处不再补发,消除重复头。
$hadIncomingSid = isset($_COOKIE[session_name()]);
if ($hadIncomingSid && (!isset($_COOKIE[session_name()]) || $_COOKIE[session_name()] !== session_id())) {
$cookieParams = session_get_cookie_params();
setcookie(
session_name(),
session_id(),
[
'expires' => 0, // 会话 Cookie
'path' => $cookieParams['path'] ?: '/',
'domain' => $cookieParams['domain'] ?: '',
'secure' => $cookieParams['secure'],
'httponly' => true,
'samesite' => 'Lax',
]
);
}
// ===== Session 诊断日志(仅首次请求记录,避免日志泛滥) =====
if (empty($_SESSION['_diag_logged'])) {
$_SESSION['_diag_logged'] = 1;
$diagPath = ini_get('session.save_path');
$diagWritable = is_writable($diagPath) ? 'writable' : 'NOT_WRITABLE';
$diagSameSite = ini_get('session.cookie_samesite');
$diagSecure = ini_get('session.cookie_secure');
$diagHttpOnly = ini_get('session.cookie_httponly');
$sessHash = session_id() ? hash('sha256', session_id()) : 'none';
error_log("[SESSION_DIAG] path={$diagPath} {$diagWritable} samesite={$diagSameSite} secure={$diagSecure} httponly={$diagHttpOnly} session_hash={$sessHash}");
}
define('APP_PATH', __DIR__ . '/');
// 基础URL路径(自动检测子目录,无需手动配置)
// 例如:部署在 /mes/ 下则自动为 /mes,部署在根目录则为空
$scriptDir = dirname($_SERVER['SCRIPT_NAME']);
define('BASE_URL', ($scriptDir === '/' || $scriptDir === '\\') ? '' : $scriptDir);
// 从 .env 读取调试模式开关(生产环境设为 false)
$envDebug = getenv('APP_DEBUG');
if ($envDebug === false || $envDebug === '') {
// 环境变量未设置时,从 .env 文件读取
$envFile = __DIR__ . '/.env';
if (file_exists($envFile)) {
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$line = trim($line);
if ($line === '' || $line[0] === '#') continue;
if (strpos($line, '=') !== false) {
list($k, $v) = explode('=', $line, 2);
if (trim($k) === 'APP_DEBUG') {
$envDebug = trim($v);
break;
}
}
}
}
}
define('APP_DEBUG', ($envDebug === 'true' || $envDebug === '1'));
// 启用输出缓冲,防止 PHP 错误/警告污染 JSON/HTML 输出
ob_start();
// 加载全局辅助函数(必须在框架和视图之前加载)
require(APP_PATH . 'core/helper.php');
// 加载框架文件
require(APP_PATH . 'core/Core.php');
// 加载配置文件
$config = require(APP_PATH . 'config/config.php');
// ===== v3-P2 CSP:基于 nonce 的内容安全策略(严格模式) =====
// 每次请求生成独立 nonce 并注入 CSP 头;视图通过 csp_nonce() 为 <script>/<style> 打标记。
// 全站内联事件处理器(onclick 等)已改造为 addEventListener / 事件委托,故 script-src 不再保留 'unsafe-inline'。
// 第三方库均以外链 <script src> 加载(受 'self' 允许),不依赖内联脚本。
$cspNonce = base64_encode(random_bytes(16));
$GLOBALS['csp_nonce'] = $cspNonce;
$cspHeader = "default-src 'self'; "
. "script-src 'self' 'nonce-{$cspNonce}'; "
. "style-src 'self' 'unsafe-inline'; "
. "img-src 'self' data: blob:; "
. "font-src 'self'; "
. "connect-src 'self'; "
. "frame-ancestors 'self'; "
. "base-uri 'self'; "
. "form-action 'self'";
header("Content-Security-Policy: {$cspHeader}");
// 实例化框架类
(new core\Core($config))->run();