fix(t244): accept headerless ERP images safely

This commit is contained in:
QiuSW
2026-07-29 17:01:52 +08:00
parent 3691e791e9
commit 9195019170
4 changed files with 324 additions and 18 deletions
+7 -4
View File
@@ -5,7 +5,7 @@
## 当前快照
- 日期:2026-07-29
- 阶段:T-243 已完成货运删除的采购需求草稿边界修复
- 阶段:T-244 已完成 ERP 图片缺失媒体类型兼容
- Git:当前分支为 `main`;T-001 至 T-004、T-101 至 T-104、T-201 至 T-219
均按文档提交、实现提交的顺序纳入历史
- 生产代码:`android-buyer/` 已接入 Roubao Android 源码
@@ -21,7 +21,9 @@
`FAILED`,日期范围仍后台异步。HTTP `WriteTimeout` 为 70 秒。T-238 已兼容真实详情
`created` 的分钟精度,仍按 Asia/Shanghai 严格解析并转 UTC。T-239 至 T-241 已保存
`productSpec`、TWD 原始单价和数字图片引用,以有界并发缓存归一化 JPEG,并在鉴权
本地路由、货运列表和详情中显示图片、状态与采购字段。T-242/T-243 允许 ADMIN 从
本地路由、货运列表和详情中显示图片、状态与采购字段。T-244 对缺失 `Content-Type`
或通用二进制的 ERP 图片使用 512 字节受控嗅探,完整解码和 20 MB 限制不变。
T-242/T-243 允许 ADMIN 从
详情页删除本地货运快照、缓存图以及无参考图/任务的采购需求草稿;已绑定参考图或已生成
采购任务时分别原子拒绝,不修改 ERP、同步运行历史或增量水位,完整订单号可重新导入。
- ERP Go 迁移:T-225 已用脱敏 fixture 固定 `internal/platform/shunyunbao` 的 header、
@@ -45,7 +47,7 @@
- Android Studio:未安装;`winget` 静默安装卡住后已终止,不阻塞命令行构建
- 测试:T-219 Android Debug/Release 单元测试与构建和根 `init.ps1` 通过;
Debug APK `1.4.16 (21)` 已覆盖安装到 PKG110
- 后端测试:T-226 至 T-243 已运行 `go test ./...`、`go test -race ./...`、`go vet ./...`
- 后端测试:T-226 至 T-244 已运行 `go test ./...`、`go test -race ./...`、`go vet ./...`
和三个 Go 入口构建;T-227 增加 Go source 的伪 ERP 会话预检、完整单号、日期分页去重、
详情 allowlist 和稳定错误码覆盖;根 `init.ps1` 的 Android 测试/Debug APK 与 Go 标准
验证也通过,均未访问真实 ERP;
@@ -60,7 +62,8 @@
- 货运管理展示:列表按外部商品 ID 确定性展示第一条当前商品的 56px 代表图、标题和
总项数;详情展示 84px/移动端 72px 本地图片、单一规格 SKU、数量、TWD 原始单价、
图片状态和采购操作。Playwright 已在 1440、768、390、375 四档宽度验证无横向溢出、
破图、遮挡和控制台错误。
破图、遮挡和控制台错误。ERP 缺失图片媒体类型时,JPEG/PNG 使用标准嗅探,WebP 使用
严格文件签名,再由 asset store 完整验证并保存为本地归一化 JPEG。
- 货运本地删除:详情页二次确认后调用 CSRF 保护的 SSR POST,JSON API 提供幂等 DELETE;
删除事务覆盖全部历史商品并清理无引用的 `BLOCKED`、`NEEDS_IMAGE`、`SOURCE_CHANGED`
采购草稿;已绑定参考图或采购任务分别返回稳定冲突,任务优先。提交后以有界预算清理
+21 -10
View File
@@ -4,7 +4,7 @@ title: 兼容 ERP 图片响应缺少媒体类型
phase: 2
deps:
- T-241
status: TODO
status: DONE
created: 2026-07-29
context_ref: 24eef29
work_branch: null
@@ -32,7 +32,8 @@ write_paths:
- HTTP 2xx 且 `Content-Type` 为 `image/jpeg`、`image/png`、`image/webp` 时沿用现有路径。
- `Content-Type` 缺失或规范化后为 `application/octet-stream` 时,只读取正文前最多
512 字节并使用 Go 标准库 `http.DetectContentType` 判定真实类型。
512 字节;JPEG/PNG 使用 Go 标准库 `http.DetectContentType`,WebP 使用严格
`RIFF....WEBP` 文件签名判定。
- 嗅探只接受 JPEG、PNG、WebP;空正文、JSON、HTML、SVG、未知二进制继续返回
`ErrFreightImageInvalid`。
- 已读取的前缀必须无损拼回返回流,后续本地存储继续执行完整解码、声明类型一致性、
@@ -55,14 +56,14 @@ write_paths:
## 验收要点
- [ ] 缺少 `Content-Type` 的有效 JPEG 保存为本系统归一化 JPEG,图片状态为 `READY`。
- [ ] `application/octet-stream` 的有效 PNG/WebP 可以进入既有解码与存储路径。
- [ ] 嗅探不会丢失正文前缀,完整内容只读取一次并最终关闭 ERP 响应 body。
- [ ] JSON、HTML、SVG、空正文、未知二进制和明确不支持的媒体类型仍被拒绝。
- [ ] 已知或流式超限图片仍受 20 MB 限制,不能绕过 asset store。
- [ ] 401/403、404、非 2xx、会话清理、固定 URL 和数字 ID 校验不回归。
- [ ] 货运同步仍为图片 best-effort,不因单张图片失败回滚业务数据。
- [ ] 标准 Go 测试、race、vet 和三个入口构建通过。
- [x] 缺少 `Content-Type` 的有效 JPEG 保存为本系统归一化 JPEG,图片状态为 `READY`。
- [x] `application/octet-stream` 的有效 PNG/WebP 可以进入既有解码与存储路径。
- [x] 嗅探不会丢失正文前缀,完整内容只读取一次并最终关闭 ERP 响应 body。
- [x] JSON、HTML、SVG、空正文、未知二进制和明确不支持的媒体类型仍被拒绝。
- [x] 已知或流式超限图片仍受 20 MB 限制,不能绕过 asset store。
- [x] 401/403、404、非 2xx、会话清理、固定 URL 和数字 ID 校验不回归。
- [x] 货运同步仍为图片 best-effort,不因单张图片失败回滚业务数据。
- [x] 标准 Go 测试、race、vet 和三个入口构建通过。
## 边界
@@ -78,3 +79,13 @@ write_paths:
`ERP_IMAGE_INVALID`;受控响应探测确认 HTTP 200 正文为有效 JPEG,但
`Content-Type` 和 `Content-Length` 均缺失。根因位于 source adapter 的响应头强校验,
不是 ERP 鉴权、数字图片 ID、图片不存在、本地目录或图片体积。
- 2026-07-29:适配器仅对缺失媒体类型或 `application/octet-stream` 响应使用固定
512 字节 buffered peek;支持头直接沿用,明确 JSON/HTML/SVG 等类型保持 fail-closed。
成功流组合 buffered reader 与原 body closer,既不丢前缀,也不复制完整图片。
- 2026-07-29:测试确认 Go 1.23 的 `http.DetectContentType` 不识别 WebP,因此增加
严格 `RIFF....WEBP` 前缀检测;asset store 随后仍执行完整 WebP 解码、媒体类型一致性、
尺寸/像素与 20 MB 校验。端到端测试已将缺失响应头的有效 JPEG 归一化保存并重新解码。
- 2026-07-29:`go test ./...`、`go test -race ./...`、`go vet ./...` 和 API、
authctl、migrate 三个入口构建通过。测试覆盖缺失/通用媒体类型、JPEG/PNG/WebP、
前缀完整性、body 关闭语义、明确非图片、嗅探非图片、已知超限、401/403、404 与非 2xx;
自动化验证未访问真实 ERP。