land_api输出缓冲加固防HTML污染;admin前端apiCall容错HTML响应

This commit is contained in:
ypsdf1
2026-10-12 00:23:11 +08:00
parent 8a669b1e24
commit 489b83f29d
2 changed files with 54 additions and 4 deletions
+21 -4
View File
@@ -2950,19 +2950,36 @@ async function apiCall(action, params = {}, method = 'GET') {
const url = new URL(LAND_API, window.location.href);
url.searchParams.set('action', action);
url.searchParams.set('secret', SECRET);
let r;
if (method === 'GET') {
for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
const r = await fetch(url);
return r.json();
r = await fetch(url);
} else {
const body = new URLSearchParams(params);
body.set('secret', SECRET);
const r = await fetch(url, {
r = await fetch(url, {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: body.toString()
});
return r.json();
}
// ★ 2026-10-11 防 HTML 污染:后端异常时可能返回 HTML 错误页,
// 统一转成带状态码与响应片段的友好错误,不再直接 r.json() 抛 "Unexpected token '<'"
const text = await r.text().catch(() => '');
const t = text.replace(/^\uFEFF/, '').trim();
if (!t) {
throw new Error('接口 ' + action + ' 返回空响应 (HTTP ' + r.status + ')');
}
if (t.charAt(0) === '<') {
console.error('[apiCall] ' + action + ' 返回 HTML 而非 JSON, HTTP ' + r.status + ':', t.substring(0, 300));
throw new Error('接口 ' + action + ' 返回了 HTML 页面 (HTTP ' + r.status
+ '),服务器可能出错,请查看后端日志。片段: ' + t.substring(0, 100));
}
try {
return JSON.parse(t);
} catch (e) {
console.error('[apiCall] ' + action + ' JSON 解析失败, HTTP ' + r.status + ':', t.substring(0, 300));
throw new Error('接口 ' + action + ' 返回无效 JSON (HTTP ' + r.status + '): ' + t.substring(0, 100));
}
}
+33
View File
@@ -4,6 +4,39 @@
* POST: Java端推送领地数据
* GET: admin/玩家面板读取领地数据
*/
// ★ 2026-10-11 防 HTML 污染加固(对齐 api/admin.php 的既有做法):
// display_errors 关闭(fatal/警告不再吐 HTML 错误页)+ 输出缓冲 +
// shutdown 兜底:只要最终输出不是合法 JSON 开头,就替换为 JSON 错误,
// 保证管理面板永远收到 JSON(页面不再报 "Unexpected token '<'")。
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ob_start();
register_shutdown_function(function () {
$err = error_get_last();
$fatal = $err && in_array($err['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true);
$buf = '';
while (ob_get_level() > 0) {
$buf .= ob_get_clean();
}
$trim = ltrim($buf);
if ($buf !== '' && $trim !== '' && $trim[0] !== '{' && $trim[0] !== '[') {
// 输出被 HTML/文本污染 → 整体替换为 JSON 错误(保留片段便于排查)
echo json_encode([
'success' => false,
'error' => 'API 输出被非 JSON 内容污染' . ($fatal && !empty($err['message']) ? ': ' . $err['message'] : ''),
'snippet' => mb_substr($trim, 0, 160),
], JSON_UNESCAPED_UNICODE);
return;
}
if ($fatal && $buf === '') {
echo json_encode([
'success' => false,
'error' => '服务器内部错误: ' . (isset($err['message']) ? $err['message'] : 'unknown'),
], JSON_UNESCAPED_UNICODE);
return;
}
echo $buf;
});
header('Content-Type: application/json; charset=utf-8');
error_reporting(E_ERROR | E_PARSE);
// 取 SECRET_KEY(插件与 Web 端约定的通信密钥,值在 config.php 里由部署者自己填)。