laoyang 96dceb1a51 修复上线后暴露的两处回归:节点写路径 HY093、类型白名单可被扩展名绕过
两处都是端到端测试抓出来的,php -l 与模板渲染检查都碰不到。

一、新建 / 编辑执行节点整条失效(SQLSTATE[HY093])

  上一提交为「不再写历史交通列」,把 line_name / carrier / seat_info / gate
  四个占位符从 NodeController::store() 与 update() 的 SQL 里删掉了,但
  collectInput() 返回的 $data 仍然带着这四个键(值恒为 null)。

  本项目 Database 用的是原生预处理(ATTR_EMULATE_PREPARES => false),
  参数数组里多一个 SQL 没有的键会直接抛 HY093 —— 于是:

    nodes/store 失败 → nodes/edit 取不到 CSRF → 依赖它的用例连锁失败,
    e2e 一度 106 通过 / 125 失败。

  修法:两处 SQL 恢复这四个占位符,值显式写 null。
  这既让参数个数对上,也顺带在每次编辑时清掉历史脏数据。
  已在代码里写明「删不得」的原因,并在部署脚本里加一条静态断言
  (数 ':line_name' 出现次数,期望 2)当哨兵。

  另新增 _tooling/trip_pdo_param_check.py:静态比对 prepare() 的命名占位符
  与 execute() 数组的键。对删除前版本精确报 2 处,对修复后 0 处。

二、改个扩展名就能绕过上传类型白名单

  Mime::detectFile() 的第三层「扩展名兜底」让「内容认不出来」的文件凭文件名
  进入白名单 —— 一个内容是纯文本、名字叫 .jpg 的文件被判成 image/jpeg。

  改成 refineByMagic():扩展名只能用于**细化**已经认出来的容器类型,
  把 application/zip 细分成 OOXML(docx / xlsx)、把 OLE2 细分成 doc / xls,
  不能把 null 变成任何具体类型。内容认不出来就是认不出来。

  配套新增 _tooling/trip_mime_check.php 单元测试(27 项,不连库不起服务),
  重点守住「改后缀不能绕过白名单」这条安全语义。

验证

  本地:php -l 55 文件 0 失败;模板渲染 234 项全通过;SQL 列名 15 张表无未知列;
        PDO 占位符 0 问题;CSS 202 个类全有定义;Mime 单元测试 27 项全通过。
  线上:部署脚本 10 步 EXIT=0,双 PHP 渲染 14 条路由字节数完全一致,
        26 项内容断言全通过(含照片代理 69/69 字节逐字节一致);
        端到端测试 239 项通过 / 0 项失败(修复前 236/3)。
2026-09-22 23:30:59 +08:00
2026-09-22 12:45:12 +00:00
2026-09-22 12:45:12 +00:00
S
Description
家庭旅行记录系统
Readme Apache-2.0
596 KiB
Languages
PHP 88.5%
CSS 11.5%