fix(extensions): match magnet URI patterns (#1619)

Co-authored-by: Hou KangLe <[email protected]>
This commit is contained in:
HouKangleandHou KangLe authored and GitHub committed 2026-10-26 14:15:10 +08:00
1 parent b8ff5ff97f
commit 903c5c46c4
3 files changed
+103 -2

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
package download
import (
"fmt"
"os"
"path/filepath"
"testing"
"github.com/GopeedLab/gopeed/pkg/base"
)
func TestDownloader_Extension_MagnetMatch(t *testing.T) {
const magnetURL = "magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567"
for _, pattern := range []string{"magnet:*", "magnet:?*"} {
t.Run(pattern, func(t *testing.T) {
d := NewDownloader(&DownloaderConfig{
Storage: NewMemStorage(),
StorageDir: t.TempDir(),
TempDir: t.TempDir(),
})
if err := d.Setup(); err != nil {
t.Fatal(err)
}
defer d.Clear()
extDir := t.TempDir()
manifest := fmt.Sprintf(`{"name":"magnet-match","author":"test","title":"Magnet match","version":"1.0.0","scripts":[{"event":"onResolve","match":{"urls":[%q]},"entry":"index.js"}]}`, pattern)
script := `gopeed.events.onResolve(ctx => {
ctx.res = {files: [{name: "resolved.txt", size: 1, req: {url: "https://example.com/resolved.txt"}}]};
});`
for name, content := range map[string]string{"manifest.json": manifest, "index.js": script} {
if err := os.WriteFile(filepath.Join(extDir, name), []byte(content), 0600); err != nil {
t.Fatal(err)
}
}
if _, err := d.InstallExtensionByFolder(extDir, true); err != nil {
t.Fatal(err)
}
for _, req := range []*base.Request{
{URL: magnetURL},
{URL: "https://example.com/rewritten", RawURL: magnetURL},
} {
// Exercise extension activation without falling back to a live
// BitTorrent metadata lookup when the pattern fails to match.
res, err := d.triggerOnResolve(req)
if err != nil {
t.Fatal(err)
}
if res == nil || len(res.Files) != 1 || res.Files[0].Name != "resolved.txt" {
t.Fatalf("magnet extension did not resolve request %+v: %+v", req, res)
}
}
})
}
}
+15 -2
View File
@@ -6,14 +6,27 @@ import (
"strings"
)
// Match url with pattern by chrome extension match pattern style
// Match url with pattern by chrome extension match pattern style.
// Magnet URI patterns such as magnet:?* are also supported, with * as a wildcard.
// https://developer.chrome.com/docs/extensions/mv3/match_patterns/
func Match(pattern string, u string) bool {
scheme, host, path := parsePattern(pattern)
url, err := url.Parse(u)
if err != nil {
return false
}
// Magnet URIs have no authority or path; match the raw scheme-specific
// part instead, preserving query punctuation and percent-encoding.
if strings.HasPrefix(pattern, "magnet:") && !strings.HasPrefix(pattern, "magnet://") {
if url.Scheme != "magnet" {
return false
}
_, target, _ := strings.Cut(u, ":")
expression := regexp.QuoteMeta(strings.TrimPrefix(pattern, "magnet:"))
expression = strings.ReplaceAll(expression, `\*`, ".*")
matched, _ := regexp.MatchString("^"+expression+"$", target)
return matched
}
scheme, host, path := parsePattern(pattern)
if scheme != "*" && scheme != url.Scheme {
return false
}
+32
View File
@@ -34,3 +34,35 @@ func TestMatch(t *testing.T) {
})
}
}
func TestMatchMagnet(t *testing.T) {
const magnetURL = "magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567&dn=example.zip"
tests := []struct {
name string
pattern string
url string
want bool
}{
{"scheme wildcard", "magnet:*", magnetURL, true},
{"query wildcard", "magnet:?*", magnetURL, true},
{"info hash wildcard", "magnet:?xt=urn:btih:*", magnetURL, true},
{"exact URI", magnetURL, magnetURL, true},
{"encoded name", "magnet:?*&dn=example%20file.zip", "magnet:?xt=urn:btih:abc&dn=example%20file.zip", true},
{"literal query punctuation", "magnet:?*&dn=a+b[1].zip", "magnet:?xt=urn:btih:abc&dn=a+b[1].zip", true},
{"literal question mark", "magnet:?*", "magnet:xt=urn:btih:abc", false},
{"literal dot", "magnet:?*&dn=example.zip", "magnet:?xt=urn:btih:abc&dn=exampleXzip", false},
{"different hash type", "magnet:?xt=urn:btmh:*", magnetURL, false},
{"different hash", "magnet:?xt=urn:btih:other*", magnetURL, false},
{"different scheme", "magnet:*", "https://example.org/file.zip", false},
{"invalid URI", "magnet:*", "magnet:\x00", false},
{"existing catch-all", "*://*/*", magnetURL, true},
{"existing magnet pattern", "magnet://*", magnetURL, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Match(tt.pattern, tt.url); got != tt.want {
t.Errorf("Match(%q, %q) = %v, want %v", tt.pattern, tt.url, got, tt.want)
}
})
}
}