From 943ad190e60ef1bb42ce9a64ccdf7513190dcad9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Jan 2026 08:57:05 +0000 Subject: [PATCH] Fix empty filename when Content-Disposition header is missing in /dl command Add filenameFromURL helper to extract filename from URL path when Content-Disposition header is empty or not present. This fixes the issue where direct link downloads fail due to empty filename. Co-authored-by: krau <71133316+krau@users.noreply.github.com> --- core/tasks/directlinks/execute.go | 6 ++- core/tasks/directlinks/util.go | 29 +++++++++++ core/tasks/directlinks/util_test.go | 78 +++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 core/tasks/directlinks/util_test.go diff --git a/core/tasks/directlinks/execute.go b/core/tasks/directlinks/execute.go index 87791e2..ed07fb4 100644 --- a/core/tasks/directlinks/execute.go +++ b/core/tasks/directlinks/execute.go @@ -45,10 +45,14 @@ func (t *Task) Execute(ctx context.Context) error { fetchedTotalBytes.Add(resp.ContentLength) file.Size = resp.ContentLength if name := resp.Header.Get("Content-Disposition"); name != "" { - // Set file name + // Set file name from Content-Disposition header filename := parseFilename(name) file.Name = filename } + // Fallback: extract filename from URL if Content-Disposition is empty + if file.Name == "" { + file.Name = filenameFromURL(file.URL) + } return nil }) diff --git a/core/tasks/directlinks/util.go b/core/tasks/directlinks/util.go index 8a64d76..5d7d1c8 100644 --- a/core/tasks/directlinks/util.go +++ b/core/tasks/directlinks/util.go @@ -173,6 +173,35 @@ func parseFilenameFallback(cd string) string { return decodeFilenameParam(value) } +// filenameFromURL extracts filename from a URL path. +// It uses the last path segment and removes any query parameters. +// Returns empty string if the URL cannot be parsed or has no valid path. +func filenameFromURL(rawURL string) string { + u, err := url.Parse(rawURL) + if err != nil { + return "" + } + + // Get the path and extract the base name + path := u.Path + if path == "" || path == "/" { + return "" + } + + // Find the last path segment + idx := strings.LastIndex(path, "/") + if idx >= 0 && idx < len(path)-1 { + filename := path[idx+1:] + // URL decode the filename + if decoded, err := url.QueryUnescape(filename); err == nil { + return decoded + } + return filename + } + + return "" +} + var progressUpdatesLevels = []struct { size int64 // 文件大小阈值 stepPercent int // 每多少 % 更新一次 diff --git a/core/tasks/directlinks/util_test.go b/core/tasks/directlinks/util_test.go new file mode 100644 index 0000000..62a4548 --- /dev/null +++ b/core/tasks/directlinks/util_test.go @@ -0,0 +1,78 @@ +package directlinks + +import ( + "testing" +) + +func TestFilenameFromURL(t *testing.T) { + tests := []struct { + name string + url string + expected string + }{ + { + name: "simple file", + url: "https://example.com/file.zip", + expected: "file.zip", + }, + { + name: "file with path", + url: "https://example.com/path/to/document.pdf", + expected: "document.pdf", + }, + { + name: "url with query params", + url: "https://example.com/file.mp4?token=abc123", + expected: "file.mp4", + }, + { + name: "url with fragment", + url: "https://example.com/file.txt#section", + expected: "file.txt", + }, + { + name: "url encoded filename", + url: "https://example.com/%E6%B5%8B%E8%AF%95.zip", + expected: "测试.zip", + }, + { + name: "url encoded Chinese filename", + url: "https://example.com/10%E6%9C%8817%E6%97%A5(6).mp4", + expected: "10月17日(6).mp4", + }, + { + name: "root path only", + url: "https://example.com/", + expected: "", + }, + { + name: "no path", + url: "https://example.com", + expected: "", + }, + { + name: "empty url", + url: "", + expected: "", + }, + { + name: "file with spaces encoded", + url: "https://example.com/my%20file.txt", + expected: "my file.txt", + }, + { + name: "complex path with multiple slashes", + url: "https://cdn.example.com/a/b/c/d/e/video.mkv", + expected: "video.mkv", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := filenameFromURL(tt.url) + if result != tt.expected { + t.Errorf("filenameFromURL(%q) = %q, expected %q", tt.url, result, tt.expected) + } + }) + } +}