docs: import wiki at afc651f75a3a
+84
@@ -0,0 +1,84 @@
|
||||
<!-- docs-wiki-sync:docs/tasks/T-253.md@afc651f75a3abc2676bb13aa8a80f6aa6a25a72e -->
|
||||
> 同步来源:[`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=<opaque-token>`,首个 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=<opaque-token>` 返回小体积 `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
|
||||
权限均保持启用。
|
||||
Reference in New Issue
Block a user