Files
larksuite__cli/shortcuts/im/im_resource_download_test.go
sammi-bytedance 501bf539af feat(im): complete audio/post rendering and add opt-in --download-resources (#1245)
Block 1 — field completion: audio renders <audio key="..." duration="Xs"/>
(falls back to [Voice: Xs]/[Voice]); post renders emotion -> :emoji_type:,
applies text.style (bold/italic/underline/lineThrough), passes through md;
sticker unchanged.

Block 2 — opt-in --download-resources (default off) on +chat-messages-list,
+messages-mget, +threads-messages-list: extract downloadable resource refs
during formatting (image/file/audio/video/media + post-embedded; sticker
excluded; merge_forward sub-items carry the top-level container message_id,
since the resources endpoint rejects sub-item ids with "234003 File not in
msg" and can only fetch a forwarded resource through the container; thread
replies get their own block), then download each distinct (message_id,
file_key) once into ./lark-im-resources/ with bounded concurrency (3), filling
back local_path/size_bytes; single-resource failures are isolated (error:true +
stderr warning). Path safety reuses normalizeDownloadOutputPath +
ResolveSavePath.

Batch download keys each file on disk by its unique file_key basename and only
appends an extension (from the Content-Disposition filename or MIME type) —
it does NOT substitute the server's Content-Disposition filename. Otherwise two
resources whose servers return the same filename (e.g. download.bin) would
resolve to the same ./lark-im-resources/ path and clobber each other
concurrently. The friendly "adopt the server filename" behavior is kept only
for an explicit +messages-resources-download with no --output.

Resource ref extraction guards against self-referential / cyclic merge_forward
prefetch maps (a real API sub-item list can include the container's own id or a
back-pointing merge_forward) via a visited set, so extraction terminates instead
of overflowing the stack. The container message_id is threaded through nested
merge_forwards as the download owner.

Also: document the feature (including the im:message:readonly scope requirement)
in skills/lark-im — SKILL.md is generated from skill-template/domains/im.md
(edit the source), plus the hand-written message-enrichment + 3 command
references.

Change-Id: I3a71d7d1b193130f551aaa2ec180ac1500d59ac4
Meego: https://meego.larkoffice.com/5e96d7bff4e7c525510f9156/story/detail/7331555925
2026-06-10 20:07:49 +08:00

31 lines
1.0 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package im
import "testing"
// TestDownloadResourcePathSafety verifies the --download-resources path builder
// confines downloads to ./lark-im-resources/ and rejects abnormal file_keys
// (path separators, traversal, absolute paths) via the existing
// normalizeDownloadOutputPath guard (AC8).
func TestDownloadResourcePathSafety(t *testing.T) {
if rel, err := resolveResourceDownloadPath("file_123"); err != nil || rel != "lark-im-resources/file_123" {
t.Fatalf("resolveResourceDownloadPath(file_123) = (%q, %v), want (lark-im-resources/file_123, nil)", rel, err)
}
bad := []string{
"", // empty
"a/b", // forward slash
`a\b`, // backslash
"..", // traversal-only
"../etc", // traversal with slash
"/abs", // absolute-ish (leading slash)
}
for _, key := range bad {
if rel, err := resolveResourceDownloadPath(key); err == nil {
t.Fatalf("resolveResourceDownloadPath(%q) = (%q, nil), want rejection", key, rel)
}
}
}