Ztorrent/internal/magnet/magnet.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
}