Skip to content

feat: richText resource extraction and streaming downloadFileToFile - #4

Merged
haofeng0705 merged 3 commits into
DingTalk-Real-AI:mainfrom
typefield:feat/richtext-resources-and-download-to-file
Sep 20, 2026
Merged

haofeng0705 merged 3 commits into
DingTalk-Real-AI:mainfrom
typefield:feat/richtext-resources-and-download-to-file

Conversation

@typefield

Copy link
Copy Markdown
Contributor

为钉钉 richText 消息补齐两项媒体能力:

1. richText 附件资源提取

richText 消息中的 picture/file 段此前被解析器直接丢弃,现在提取进 IncomingMessage.resources:

  • picture 段值即钉钉下载码 → { type: 'image', downloadCode }
  • file 段(downloadCode + fileName)→ { type: 'file', ... }
  • 脏数据防御:段值严格 typeof 判断、非法段跳过不影响其余段落;同一下载码单条消息内去重

2. DingTalkChannel.downloadFileToFile(流式落盘)

流式下载文件到本地路径,不整块载入内存(Readable.fromWeb + pipeline,背压感知):

  • SSRF 防护与 downloadFile 完全一致
  • 同目录临时文件 + 原子 rename,失败不落半截文件
  • 父目录必须已存在;返回写入字节数

验证

npm test:43 个测试全部通过(含新增:资源提取/去重/脏数据、流式落盘成功/非 200/父目录缺失;新增的 test/download-to-file.test.js 已挂入 npm test 脚本)。

与 #2 互不冲突,可任意顺序合并。@haofeng0705 请复查。

typefield and others added 2 commits September 16, 2026 23:43
- normalize: richText picture/file segments now extract into
  IncomingMessage.resources with strict typeof narrowing, dirty-segment
  skip, and per-message download-code dedup.
- DingTalkChannel.downloadFileToFile: streaming media download to a
  local path (Readable.fromWeb + pipeline, no whole-file buffering),
  SSRF-guarded like downloadFile, same-dir temp file + atomic rename,
  parent dir must exist, no partial file left behind. Returns bytes
  written.
- wire test/download-to-file.test.js into the npm test script.
- Tests: resource extraction/dedup/dirty-data; streaming download
  success, non-200 status, and missing-parent-dir regressions.
…ark channel-sdk

- normalize: richText picture/file segments now extract into
  IncomingMessage.resources (analog of lark's post attachment zone) with
  strict typeof narrowing and per-message download-code dedup.
- DingTalkChannel.downloadFileToFile: streaming media download to a local
  path (Readable.fromWeb + pipeline, no whole-file buffering), SSRF-guarded
  like downloadFile, same-dir temp file + atomic rename, parent dir must
  exist, no partial file left behind. Returns bytes written.
- Tests: resource extraction/dedup/dirty-data; streaming download success,
  non-200 status, and missing-parent-dir regressions.

@haofeng0705 haofeng0705 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复查结论:请求修改,当前不建议合入。

  1. 阻塞 src/normalize/message.js:52 从 part.picture 读取 richText 图片下载码;真实回调使用 downloadCode / pictureDownloadCode,当前测试数据掩盖了该问题。
  2. 阻塞 src/channel.js:144-155 只校验初始 URL,fetch 默认自动跟随重定向,重定向目标没有重新做 SSRF/DNS 校验。
  3. src/channel.js:154 临时文件名只由 PID 和毫秒时间戳组成,且未独占创建;同进程同毫秒并发写同一目标时会碰撞。已复现一个调用成功、另一个 ENOENT。

npm test 本地 43/43 通过,但未覆盖真实 richText 字段、重定向及并发写入。请补齐修复和回归测试后再合入。

…eToFile

- Support downloadCode and pictureDownloadCode with fallback to picture for richText images
- Use POST with JSON body for messageFiles/download in reply
- Prevent SSRF redirect bypass with manual redirect verification
- Prevent temp file collision on concurrent downloads to same destination
- Add GitHub Actions CI workflow

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@haofeng0705 haofeng0705 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复查通过。

  • richText 图片字段已兼容 downloadCode / pictureDownloadCode / picture(src/normalize/message.js:50-63)。
  • SSRF 重定向绕过已修复:fetchWithSsrfGuard 手动处理重定向并逐跳校验(src/channel.js:25-43),测试覆盖。
  • 临时文件名加入 crypto.randomBytes(8)(src/channel.js:173-174),并发写同一目标不再碰撞,测试覆盖。
  • 新增 CI workflow(Node 18/20/22)。
  • npm test 本地 45/45 通过。

可合入。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants