mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-06 08:30:29 +00:00
fix: use PathUnescape for filename URL decoding (#1214)
This commit is contained in:
1 parent
71c9e6947a
commit
6756d82ffa
5 files changed
+98
-6
No files matched your search
@@ -171,9 +171,9 @@ func (f *Fetcher) Resolve(req *base.Request) error {
|
||||
// get file filePath by URL
|
||||
if file.Name == "" {
|
||||
file.Name = path.Base(httpReq.URL.Path)
|
||||
// Url decode
|
||||
// Url decode - use PathUnescape for paths to handle %2B correctly
|
||||
if file.Name != "" {
|
||||
file.Name, _ = url.QueryUnescape(file.Name)
|
||||
file.Name, _ = url.PathUnescape(file.Name)
|
||||
}
|
||||
}
|
||||
// unknown file filePath
|
||||
@@ -656,7 +656,8 @@ func parseFilenameExtended(cd string) string {
|
||||
if len(parts) == 2 {
|
||||
// parts[0] is charset (e.g., "UTF-8")
|
||||
// parts[1] is percent-encoded value
|
||||
decoded, err := url.QueryUnescape(parts[1])
|
||||
// Use PathUnescape to handle %2B correctly (should decode to +, not space)
|
||||
decoded, err := url.PathUnescape(parts[1])
|
||||
if err == nil {
|
||||
return decoded
|
||||
}
|
||||
@@ -665,7 +666,8 @@ func parseFilenameExtended(cd string) string {
|
||||
// Try with single quote delimiter as well (some servers use this)
|
||||
parts = strings.SplitN(value, "'", 3)
|
||||
if len(parts) >= 3 {
|
||||
decoded, err := url.QueryUnescape(parts[2])
|
||||
// Use PathUnescape to handle %2B correctly (should decode to +, not space)
|
||||
decoded, err := url.PathUnescape(parts[2])
|
||||
if err == nil {
|
||||
return decoded
|
||||
}
|
||||
@@ -687,8 +689,8 @@ func decodeFilenameParam(filename string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// Try URL decoding
|
||||
decoded := util.TryUrlQueryUnescape(filename)
|
||||
// Try URL decoding - use PathUnescape for filenames to handle %2B correctly
|
||||
decoded := util.TryUrlPathUnescape(filename)
|
||||
|
||||
// Check if the result is valid UTF-8. If not, try GBK decoding.
|
||||
// This handles the case where Chinese Windows servers send GBK-encoded filenames
|
||||
|
||||
@@ -113,6 +113,32 @@ func TestFetcher_Resolve(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, t)
|
||||
// Test filename with plus signs (e.g., C++ Primer)
|
||||
// Before fix: %2B decoded to space -> "C++ Primer" became "C Primer"
|
||||
// After fix: %2B correctly decoded to + -> "C++ Primer Plus.mobi"
|
||||
testResolve(test.StartTestCustomServer, "plus-sign-encoded", &base.Resource{
|
||||
Size: test.BuildSize,
|
||||
Range: false,
|
||||
Files: []*base.FileInfo{
|
||||
{
|
||||
Name: "C++ Primer Plus.mobi",
|
||||
Size: test.BuildSize,
|
||||
},
|
||||
},
|
||||
}, t)
|
||||
// Test filename with plus sign in URL path
|
||||
// Before fix: %2B decoded to space
|
||||
// After fix: %2B correctly decoded to +
|
||||
testResolve(test.StartTestCustomServer, "C%2B%2B%20Primer.txt", &base.Resource{
|
||||
Size: 0,
|
||||
Range: false,
|
||||
Files: []*base.FileInfo{
|
||||
{
|
||||
Name: "C++ Primer.txt",
|
||||
Size: 0,
|
||||
},
|
||||
},
|
||||
}, t)
|
||||
|
||||
// Test URL without file path - should use domain/host as filename
|
||||
listener := test.StartTestRootServer()
|
||||
|
||||
@@ -169,6 +169,29 @@ func StartTestCustomServer() net.Listener {
|
||||
defer file.Close()
|
||||
io.Copy(writer, file)
|
||||
})
|
||||
// Test endpoint for filenames with plus signs (C++ files, etc.)
|
||||
// This tests that %2B decodes to + not space
|
||||
mux.HandleFunc("/plus-sign-encoded", func(writer http.ResponseWriter, request *http.Request) {
|
||||
// Use filename*= format with %2B encoding for plus signs
|
||||
writer.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''C%2B%2B%20%20Primer%20%20Plus.mobi`)
|
||||
writer.Header().Set("Content-Type", "application/octet-stream")
|
||||
writer.Header().Set("Content-Length", fmt.Sprintf("%d", BuildSize))
|
||||
file, err := os.Open(BuildFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
io.Copy(writer, file)
|
||||
})
|
||||
// Test endpoint for plus sign in URL path
|
||||
mux.HandleFunc("/C%2B%2B%20Primer.txt", func(writer http.ResponseWriter, request *http.Request) {
|
||||
file, err := os.Open(BuildFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer file.Close()
|
||||
io.Copy(writer, file)
|
||||
})
|
||||
return mux
|
||||
})
|
||||
}
|
||||
|
||||
@@ -66,3 +66,16 @@ func TryUrlQueryUnescape(s string) string {
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// TryUrlPathUnescape tries to unescape a URL path-encoded string.
|
||||
// Unlike QueryUnescape, PathUnescape does not treat '+' as a space.
|
||||
// This is the correct function to use for decoding URL paths and filenames
|
||||
// where %2B should decode to '+', not to a space.
|
||||
//
|
||||
// If unescaping fails, it returns the original string.
|
||||
func TryUrlPathUnescape(s string) string {
|
||||
if decoded, err := url.PathUnescape(s); err == nil {
|
||||
return decoded
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -149,3 +149,31 @@ func TestTryURLDecode(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryUrlPathUnescape(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"normal.txt", "normal.txt"},
|
||||
{"%E7%8F%80%E5%B0%94%E8%AF%BA.zip", "珀尔诺.zip"},
|
||||
{"hello%20world.txt", "hello world.txt"},
|
||||
{"bad%2-text", "bad%2-text"},
|
||||
// The key difference: %2B should decode to + (not space)
|
||||
{"C%2B%2B%20Primer.txt", "C++ Primer.txt"},
|
||||
{"test%2Bfile.txt", "test+file.txt"},
|
||||
// Plus sign in path should remain as-is (not decoded to space)
|
||||
{"test+plus.txt", "test+plus.txt"},
|
||||
// Mixed encoding
|
||||
{"C%2B%2B%20%20Primer%20%20Plus.mobi", "C++ Primer Plus.mobi"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
got := TryUrlPathUnescape(tt.input)
|
||||
if got != tt.expected {
|
||||
t.Errorf("TryUrlPathUnescape(%q) = %q, want %q", tt.input, got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user