Skip to content

feat(im): use Content-Disposition filename when downloading message r…#536

Open
chenxingtong-bytedance wants to merge 1 commit intolarksuite:mainfrom
chenxingtong-bytedance:fix/filename_adapt
Open

feat(im): use Content-Disposition filename when downloading message r…#536
chenxingtong-bytedance wants to merge 1 commit intolarksuite:mainfrom
chenxingtong-bytedance:fix/filename_adapt

Conversation

@chenxingtong-bytedance
Copy link
Copy Markdown
Contributor

@chenxingtong-bytedance chenxingtong-bytedance commented Apr 17, 2026

Summary

When downloading message resources, the saved filename was always derived from
file_key (e.g. file_v2_abc123.xlsx), ignoring the original filename the
sender uploaded. This PR resolves filenames from the Content-Disposition
response header first, falling back to Content-Type-based extension inference
only when the header is absent.

Changes

  • Add parseContentDispositionFilename to extract and sanitize filenames from
    Content-Disposition, with RFC 5987 (filename*=UTF-8''...) support via the
    standard mime package
  • Update resolveIMResourceDownloadPath to prioritise Content-Disposition
    filename; when --output is not specified, the full server filename is used;
    when --output is specified without an extension, only the extension is taken
    from the Content-Disposition filename
  • Add userSpecifiedOutput bool parameter to downloadIMResourceToPath and
    resolveIMResourceDownloadPath to distinguish default vs. explicit output paths
  • Update skills/lark-im/references/lark-im-messages-resources-download.md to
    reflect the new filename resolution priority

Test Plan

  • go test ./shortcuts/im/... passed
  • Added TestParseContentDispositionFilename (11 cases): plain filename,

Summary by CodeRabbit

  • New Features

    • Downloads now prefer and preserve server-provided filenames (including UTF-8 encoding) and intelligently infer or append file extensions when needed.
  • Bug Fixes

    • Improved error propagation, retry and cancellation behavior, and cleanup on failed or size-mismatched downloads.
  • Tests

    • Added coverage for filename parsing, download path resolution, range downloads, error paths, retries, and cancellation.
  • Documentation

    • Updated output docs to reflect enhanced filename and extension resolution.

@github-actions github-actions Bot added domain/im PR touches the im domain size/M Single-domain feat or fix with limited business impact labels Apr 17, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 98875ee3-e163-4b51-b39f-b9e57828962b

📥 Commits

Reviewing files that changed from the base of the PR and between efdd9b8 and 63b3fd8.

📒 Files selected for processing (4)
  • shortcuts/im/helpers_network_test.go
  • shortcuts/im/helpers_test.go
  • shortcuts/im/im_messages_resources_download.go
  • skills/lark-im/references/lark-im-messages-resources-download.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • skills/lark-im/references/lark-im-messages-resources-download.md
  • shortcuts/im/helpers_network_test.go
  • shortcuts/im/helpers_test.go
  • shortcuts/im/im_messages_resources_download.go

📝 Walkthrough

Walkthrough

Adds server-provided filename handling for IM resource downloads by parsing Content-Disposition (including RFC 5987), threads a new userSpecifiedOutput boolean from the CLI --output flag into download logic, and updates resolution logic/tests to prefer disposition-derived filenames or derive extensions from Content-Type when appropriate.

Changes

Cohort / File(s) Summary
Tests
shortcuts/im/helpers_network_test.go, shortcuts/im/helpers_test.go
Updated all downloadIMResourceToPath test calls to add the new userSpecifiedOutput boolean; added tests for parseContentDispositionFilename and resolveIMResourceDownloadPath covering RFC5987, sanitization, extension inference, and user-specified output behavior.
Download implementation
shortcuts/im/im_messages_resources_download.go
Detects whether --output was set and passes userSpecifiedOutput into downloadIMResourceToPath; returns a network error if HTTP response is nil; adds parseContentDispositionFilename; updates resolveIMResourceDownloadPath to use Content-Disposition filename and/or Content-Type-derived extension according to userSpecifiedOutput.
Docs
skills/lark-im/references/lark-im-messages-resources-download.md
Updates --output documentation to state that server-provided Content-Disposition filename is preferred when --output is omitted, with extension inference from Content-Disposition then Content-Type.

Sequence Diagram

sequenceDiagram
    actor User as User/CLI
    participant Handler as Download Handler
    participant HTTP as HTTP Client
    participant Parser as Content-Disposition Parser
    participant Resolver as Path Resolver

    User->>Handler: Request download (with/without --output)
    Handler->>Handler: Determine userSpecifiedOutput
    Handler->>HTTP: Perform HTTP request
    HTTP-->>Handler: Response (+ headers)
    Handler->>Parser: Parse Content-Disposition (RFC5987)
    Parser-->>Handler: Sanitized filename or empty
    Handler->>Resolver: Resolve final path (safePath, userSpecifiedOutput, disposition, content-type)
    Resolver-->>Handler: Final output path
    Handler->>HTTP: Download content (with/without Range)
    HTTP-->>Handler: Success / Error
    Handler-->>User: Return result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • liangshuo-1
  • jackie3927
  • YangJunzhou-01

Poem

🐰 A rabbit found a filename bright,
Sniffed Content-Disposition by moonlight,
RFC5987 danced in a line,
Safe paths and extensions now align,
Hopping off with downloads done right.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers Summary and Changes sections well but the Test Plan section is incomplete (one item is missing a checkbox and test case details are cut off). Complete the Test Plan by finishing the test case description and ensure all checklist items are properly formatted and completed.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title references using Content-Disposition filename for message resource downloads, matching the core objective of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
shortcuts/im/im_messages_resources_download.go (1)

35-35: ⚠️ Potential issue | 🟡 Minor

Flag description is stale after this change.

The --output description still states defaults to file_key, but the new resolution logic prefers the Content-Disposition filename when --output is omitted. Consider refreshing the help text so users understand the new default.

✏️ Proposed wording
-		{Name: "output", Desc: "local save path (relative only, no .. traversal; defaults to file_key)"},
+		{Name: "output", Desc: "local save path (relative only, no .. traversal; defaults to server filename from Content-Disposition, or file_key)"},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/im/im_messages_resources_download.go` at line 35, Update the stale
flag description for the "output" CLI flag (Name: "output") to reflect the new
resolution order: state that when omitted the tool will prefer the filename from
the HTTP Content-Disposition header and fall back to the file_key, while still
requiring a relative path with no ".." traversal; change the Desc string where
"output" is defined to this new concise wording.
🧹 Nitpick comments (3)
shortcuts/im/helpers_test.go (2)

668-700: LGTM on TestResolveIMResourceDownloadPath.

Matrix covers the main axes: safePath-with-ext (idempotent), default vs user-specified output, CD-present vs absent, RFC 5987, and MIME fallback. One gap worth adding: the precedence case where userSpecifiedOutput=false, safePath has no extension, CD is absent, and contentType is empty — the expected outcome is that safePath is returned unchanged. Optional to add.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/im/helpers_test.go` around lines 668 - 700, Add a unit test case to
TestResolveIMResourceDownloadPath covering the missing precedence: call
resolveIMResourceDownloadPath with safePath having no extension (e.g.,
"file_xxx"), contentType == "" and contentDisposition == "" and
userSpecifiedOutput == false, and assert it returns the original safePath
unchanged; update the tests slice in shortcuts/im/helpers_test.go where other
cases are defined so this new case (name like "default path, no CD, no MIME")
sits alongside the existing entries referencing resolveIMResourceDownloadPath.

640-666: Test coverage for parseContentDispositionFilename is thorough.

Good spread across plain/quoted, RFC 5987, precedence, traversal, Windows paths, control chars, malformed, and whitespace. Consider adding one more case to lock in behavior for a filename that is ONLY dots after sanitization (e.g., filename="../..") to ensure it returns "" rather than "..".

✏️ Optional additional case
 		{name: "whitespace trimmed", header: `attachment; filename="  report.pdf  "`, want: "report.pdf"},
+		{name: "dots-only after strip", header: `attachment; filename="../.."`, want: ""},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/im/helpers_test.go` around lines 640 - 666, Add a test case to
TestParseContentDispositionFilename that covers a filename composed only of dots
after sanitization (e.g., header `attachment; filename="../.."`) and assert
parseContentDispositionFilename returns an empty string; this ensures
parseContentDispositionFilename correctly treats names that reduce to "." or
".." as invalid/empty after path-strip sanitization.
shortcuts/im/im_messages_resources_download.go (1)

324-347: Resolution precedence looks correct; one minor observation.

When !userSpecifiedOutput, safePath is the validated file_key (no separators enforced by normalizeDownloadOutputPath), so filepath.Dir(safePath) is always "." in production. The dir != "." branch on lines 334–335 is only exercised by the unit test's synthetic "downloads/file_xxx" input. Not a bug — just noting the branch is effectively dead for the real default flow; you may want to simplify to return cdFilename and drop the join (or keep it as defensive code for future callers).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/im/im_messages_resources_download.go` around lines 324 - 347, In
resolveIMResourceDownloadPath, simplify the !userSpecifiedOutput branch: because
safePath is the normalized file_key whose filepath.Dir is always ".", return
cdFilename directly instead of computing dir and conditionally joining; remove
the dir/Join logic (or keep a minimal defensive comment if you prefer) so the
function returns cdFilename when cdFilename != "" and !userSpecifiedOutput.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@shortcuts/im/im_messages_resources_download.go`:
- Line 35: Update the stale flag description for the "output" CLI flag (Name:
"output") to reflect the new resolution order: state that when omitted the tool
will prefer the filename from the HTTP Content-Disposition header and fall back
to the file_key, while still requiring a relative path with no ".." traversal;
change the Desc string where "output" is defined to this new concise wording.

---

Nitpick comments:
In `@shortcuts/im/helpers_test.go`:
- Around line 668-700: Add a unit test case to TestResolveIMResourceDownloadPath
covering the missing precedence: call resolveIMResourceDownloadPath with
safePath having no extension (e.g., "file_xxx"), contentType == "" and
contentDisposition == "" and userSpecifiedOutput == false, and assert it returns
the original safePath unchanged; update the tests slice in
shortcuts/im/helpers_test.go where other cases are defined so this new case
(name like "default path, no CD, no MIME") sits alongside the existing entries
referencing resolveIMResourceDownloadPath.
- Around line 640-666: Add a test case to TestParseContentDispositionFilename
that covers a filename composed only of dots after sanitization (e.g., header
`attachment; filename="../.."`) and assert parseContentDispositionFilename
returns an empty string; this ensures parseContentDispositionFilename correctly
treats names that reduce to "." or ".." as invalid/empty after path-strip
sanitization.

In `@shortcuts/im/im_messages_resources_download.go`:
- Around line 324-347: In resolveIMResourceDownloadPath, simplify the
!userSpecifiedOutput branch: because safePath is the normalized file_key whose
filepath.Dir is always ".", return cdFilename directly instead of computing dir
and conditionally joining; remove the dir/Join logic (or keep a minimal
defensive comment if you prefer) so the function returns cdFilename when
cdFilename != "" and !userSpecifiedOutput.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 76bdd1b6-a6b4-442b-b8d0-70ab3bde606c

📥 Commits

Reviewing files that changed from the base of the PR and between 94bba91 and efdd9b8.

📒 Files selected for processing (4)
  • shortcuts/im/helpers_network_test.go
  • shortcuts/im/helpers_test.go
  • shortcuts/im/im_messages_resources_download.go
  • skills/lark-im/references/lark-im-messages-resources-download.md

…esources

Change-Id: I68b48cf428aa8aded4ad9d55fa042f9d68263c3a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/im PR touches the im domain size/M Single-domain feat or fix with limited business impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant