From 07b3194a6f8fc9721d20db94cb3a078d03454998 Mon Sep 17 00:00:00 2001 From: ila Date: Fri, 7 Aug 2026 16:37:00 +0800 Subject: [PATCH] docs: import wiki at afc651f75a3a --- T-253.-.md | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 T-253.-.md diff --git a/T-253.-.md b/T-253.-.md new file mode 100644 index 0000000..51c5d95 --- /dev/null +++ b/T-253.-.md @@ -0,0 +1,84 @@ + +> 同步来源:[`docs/tasks/T-253.md`](/chengma/mroubao/src/commit/afc651f75a3abc2676bb13aa8a80f6aa6a25a72e/docs/tasks/T-253.md) · commit `afc651f75a3a` + +--- +id: T-253 +title: 修复 PDD ps 分享链接重定向被误判为网络失败 +phase: 2 +deps: + - T-252 +status: DONE +created: 2026-07-30 +context_ref: e6d4209 +work_branch: null +write_paths: + - docs/tasks/T-253.md + - docs/current-state.md + - android-buyer/app/build.gradle.kts + - android-buyer/app/src/main/** + - android-buyer/app/src/test/** +--- + +## 问题 / 背景 + +T-252 上线后,任务 `09240616-5d87-4ab1-a37a-57949725f8b2` 仍以 +`CANDIDATE_SHARED_LINK_NETWORK_FAILED` 结束。拼多多分享得到的 URL 形如 +`https://mobile.yangkeduo.com/goods2.html?ps=`,首个 URL 没有 +`goods_id`。 + +现场请求证明该 `ps` 是不透明分享令牌:PDD 对首个请求返回小体积 `307`,并在同一 +受信域名的 `Location` URL 中携带唯一 `goods_id`。Android 解析器虽然已经关闭 +OkHttp 自动跳转并实现受控重定向,却在读取 `Location` 前调用 +`readByteArray(65537)`;Okio 会把该参数解释为“必须读取指定字节数”,小于该长度的 +正常响应因此抛出 `EOFException`,随后被泛化成网络失败。 + +本任务修复响应处理顺序和有界正文读取,不把 `ps` 猜测、散列或保存成商品身份。 + +## 根因与边界 + +- `ps` 只用于向 PDD 请求一次受控解析,不能代替稳定 `goods_id`,也不能用于 Admin + 下单授权。 +- `301/302/303/307/308` 的商品身份来源只能是经过 HTTPS 和 host allowlist 校验的 + `Location`;跳转响应正文不参与身份提取。 +- 只有非跳转响应才允许检查正文;正文最多读取 64 KiB,并额外探测一个字节判断超限。 + 小于上限的正常正文不得触发 `EOFException`,超限正文不得参与身份推断。 +- 保留 T-252 的 PDD host allowlist、最多三次跳转、6 秒调用预算、失败码和原始分享 + 文本不落盘/不上传边界。 +- 不增加第三方解析服务,不让 VLM 推测 `goods_id`,不改变候选搜索轮次和下单边界。 + +## 方案 + +1. 重排 resolver:直接链接仍优先本地解析;收到跳转响应后先校验 `Location`,从 + 下一跳 URL 提取 `goods_id` 或继续受控跳转,不读取响应正文。 +2. 将 OkHttp 正文读取改为“最多读取上限加一字节”,避免固定长度读取在 EOF 时抛错; + 超限正文按无可用身份处理。 +3. 增加 OkHttp `Response` 级回归测试,覆盖小体积 `307`、`ps` 到 + `goods_id`、小体积 `200` 正文、超限正文以及外域跳转拒绝。 +4. 提升 Android 构建版本,执行 Debug/Release 单测、lint 和 APK 构建。 + +## 验收要点 + +- [x] `goods2.html?ps=` 返回小体积 `307` 时,不读取跳转正文且不会抛 + `EOFException`。 +- [x] 同域 `Location` 中的唯一 `goods_id` 被解析为规范商品身份,不发起不必要的 + 第二个网络请求。 +- [x] 外域、非 HTTPS、缺失或超过三次的跳转继续 fail-closed。 +- [x] 小于 64 KiB 的 `2xx` 正文可正常解析;大于 64 KiB 的正文不用于身份提取。 +- [x] 真正的 I/O 异常才映射为 `CANDIDATE_SHARED_LINK_NETWORK_FAILED`。 +- [x] Android Debug/Release 单测、lint 和构建通过。 + +## 执行记录 + +- 2026-07-30:用用户提供的真实 `ps` 分享链接只读复现。PDD 返回 `307`,受信 + `Location` 含唯一 `goods_id`;本地代码审计定位到固定长度响应读取导致的 + `EOFException`。文档不记录原始分享令牌。 +- 2026-07-30:resolver 对跳转响应改为先验证 `Location` 并直接提取下一跳 + `goods_id`,不再读取跳转正文;普通响应改用 OkHttp `peekBody` 最多读取 + 64 KiB 加一字节,底层响应在 transport 返回前可靠关闭。 +- 2026-07-30:增加真实 OkHttp `Response` 级小体积跳转、不可读跳转正文、小体积 + 成功正文和超限正文回归测试。Debug/Release 各 235 项 App 单测零失败,两个变体 + lint 和 APK 构建通过;根 `init.ps1` 及全部 Go 后端测试通过。生成 Android + `1.4.18 (23)` Debug APK。 +- 2026-07-30:通过 USB 在 OnePlus PKG110 Android 16/API 36 上覆盖安装 + `1.4.18 (23)` 并正常启动 `MainActivity`;采购无障碍、悬浮窗、通知和 Shizuku + 权限均保持启用。