83 lines
1.8 KiB
Go
83 lines
1.8 KiB
Go
package magnet
|
|
|
|
import (
|
|
"encoding/base32"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
)
|
|
|
|
type MagnetLink struct {
|
|
InfoHash [20]byte
|
|
Name string
|
|
Trackers []string
|
|
}
|
|
|
|
func Parse(uri string) (*MagnetLink, error) {
|
|
uri = strings.TrimSpace(uri)
|
|
if !strings.HasPrefix(uri, "magnet:") {
|
|
return nil, errors.New("invalid magnet link prefix")
|
|
}
|
|
|
|
parts := strings.SplitN(uri, "?", 2)
|
|
if len(parts) < 2 {
|
|
return nil, errors.New("magnet link missing query parameters")
|
|
}
|
|
|
|
q, err := url.ParseQuery(parts[1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse magnet link query: %w", err)
|
|
}
|
|
xts := q["xt"]
|
|
if len(xts) == 0 {
|
|
return nil, errors.New("magnet link missing exact topic (xt)")
|
|
}
|
|
|
|
var infoHash [20]byte
|
|
foundHash := false
|
|
|
|
for _, xt := range xts {
|
|
if strings.HasPrefix(xt, "urn:btih:") {
|
|
hashStr := strings.TrimPrefix(xt, "urn:btih:")
|
|
if len(hashStr) == 40 {
|
|
hashBytes, err := hex.DecodeString(hashStr)
|
|
if err == nil {
|
|
copy(infoHash[:], hashBytes)
|
|
foundHash = true
|
|
break
|
|
}
|
|
} else if len(hashStr) == 32 {
|
|
// Some magnet links have base32 encoded infohash (Base32 without padding is common, or with padding)
|
|
// Try standard encoding first, maybe upper/lower case.
|
|
hashStrUpper := strings.ToUpper(hashStr)
|
|
hashBytes, err := base32.StdEncoding.DecodeString(hashStrUpper)
|
|
if err != nil {
|
|
hashBytes, err = base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(hashStrUpper)
|
|
}
|
|
if err == nil {
|
|
copy(infoHash[:], hashBytes)
|
|
foundHash = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if !foundHash {
|
|
return nil, errors.New("magnet link missing valid BitTorrent info hash (urn:btih:)")
|
|
}
|
|
|
|
ml := &MagnetLink{
|
|
InfoHash: infoHash,
|
|
Trackers: q["tr"],
|
|
}
|
|
|
|
dns := q["dn"]
|
|
if len(dns) > 0 {
|
|
ml.Name = dns[0]
|
|
}
|
|
|
|
return ml, nil
|
|
}
|