11 g "github.com/anacrolix/generics"
13 "github.com/anacrolix/torrent/types/infohash"
16 // Magnet link components.
18 InfoHash Hash // Expected in this implementation
19 Trackers []string // "tr" values
20 DisplayName string // "dn" value, if not empty
21 Params url.Values // All other values, such as "x.pe", "as", "xs" etc.
24 const btihPrefix = "urn:btih:"
26 func (m Magnet) String() string {
28 vs := make(url.Values, len(m.Params)+len(m.Trackers)+2)
29 for k, v := range m.Params {
30 vs[k] = append([]string(nil), v...)
33 for _, tr := range m.Trackers {
36 if m.DisplayName != "" {
37 vs.Add("dn", m.DisplayName)
40 // Transmission and Deluge both expect "urn:btih:" to be unescaped. Deluge wants it to be at the
41 // start of the magnet link. The InfoHash field is expected to be BitTorrent in this
45 RawQuery: "xt=" + btihPrefix + m.InfoHash.HexString(),
48 u.RawQuery += "&" + vs.Encode()
53 // Deprecated: Use ParseMagnetUri.
54 var ParseMagnetURI = ParseMagnetUri
56 // ParseMagnetUri parses Magnet-formatted URIs into a Magnet instance
57 func ParseMagnetUri(uri string) (m Magnet, err error) {
58 u, err := url.Parse(uri)
60 err = fmt.Errorf("error parsing uri: %w", err)
63 if u.Scheme != "magnet" {
64 err = fmt.Errorf("unexpected scheme %q", u.Scheme)
69 for _, xt := range q["xt"] {
71 lazyAddParam(&m.Params, "xt", xt)
74 encoded, found := strings.CutPrefix(xt, btihPrefix)
76 lazyAddParam(&m.Params, "xt", xt)
79 m.InfoHash, err = parseEncodedV1Infohash(encoded)
81 err = fmt.Errorf("error parsing v1 infohash %q: %w", xt, err)
87 err = errors.New("missing v1 infohash")
91 m.DisplayName = popFirstValue(q, "dn").UnwrapOrZeroValue()
94 copyParams(&m.Params, q)
98 func parseEncodedV1Infohash(encoded string) (ih infohash.T, err error) {
99 decode := func() func(dst, src []byte) (int, error) {
100 switch len(encoded) {
104 return base32.StdEncoding.Decode
109 err = fmt.Errorf("unhandled xt parameter encoding (encoded length %d)", len(encoded))
112 n, err := decode(ih[:], []byte(encoded))
114 err = fmt.Errorf("error decoding xt: %w", err)
123 func popFirstValue(vs url.Values, key string) g.Option[string] {
127 return g.None[string]()