183 lines
3.3 KiB
Go
183 lines
3.3 KiB
Go
package metainfo
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"fmt"
|
|
"os"
|
|
|
|
"storrent/bencode"
|
|
)
|
|
|
|
type File struct {
|
|
Info Info
|
|
InfoHash [20]byte
|
|
Size int64
|
|
}
|
|
|
|
type Info struct {
|
|
Name string
|
|
PieceSize int64
|
|
Pieces [][20]byte
|
|
Size int64
|
|
Files []Entry
|
|
}
|
|
|
|
type Entry struct {
|
|
Size int64
|
|
Offset int64
|
|
Path []string
|
|
}
|
|
|
|
type Segment struct {
|
|
File int
|
|
Offset int64
|
|
Size int64
|
|
}
|
|
|
|
func (i *Info) Segments(off, size int64) []Segment {
|
|
var segs []Segment
|
|
for idx, f := range i.Files {
|
|
end := f.Offset + f.Size
|
|
if off >= end {
|
|
continue
|
|
}
|
|
n := min(size, end-off)
|
|
segs = append(segs, Segment{File: idx, Offset: off - f.Offset, Size: n})
|
|
off += n
|
|
size -= n
|
|
if size == 0 {
|
|
break
|
|
}
|
|
}
|
|
return segs
|
|
}
|
|
|
|
func Parse(path string) (*File, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ParseBytes(data)
|
|
}
|
|
|
|
func ParseBytes(data []byte) (*File, error) {
|
|
v, _, err := bencode.Decode(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
d, ok := v.(map[string]any)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid torrent dict")
|
|
}
|
|
|
|
f := &File{}
|
|
raw, err := findInfoBytes(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
f.InfoHash = sha1.Sum(raw)
|
|
dict, ok := d["info"].(map[string]any)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid info dict")
|
|
}
|
|
f.Info, err = parseInfo(dict)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if f.Info.Size > 0 {
|
|
f.Size = f.Info.Size
|
|
} else {
|
|
for _, e := range f.Info.Files {
|
|
f.Size += e.Size
|
|
}
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
func findInfoBytes(data []byte) ([]byte, error) {
|
|
if len(data) == 0 || data[0] != 'd' {
|
|
return nil, fmt.Errorf("not a dict")
|
|
}
|
|
|
|
i := 1
|
|
for i < len(data) && data[i] != 'e' {
|
|
k, n, err := bencode.DecodeString(data[i:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
i += n
|
|
if k == "info" {
|
|
_, n, err := bencode.Decode(data[i:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return data[i : i+n], nil
|
|
}
|
|
_, n, err = bencode.Decode(data[i:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
i += n
|
|
}
|
|
return nil, fmt.Errorf("info not found")
|
|
}
|
|
|
|
func parseInfo(d map[string]any) (Info, error) {
|
|
var info Info
|
|
name, ok := d["name"].(string)
|
|
if !ok {
|
|
return info, fmt.Errorf("invalid name")
|
|
}
|
|
info.Name = name
|
|
pl, ok := d["piece length"].(int64)
|
|
if !ok {
|
|
return info, fmt.Errorf("invalid piece length")
|
|
}
|
|
info.PieceSize = pl
|
|
ps, ok := d["pieces"].(string)
|
|
if !ok || len(ps)%20 != 0 {
|
|
return info, fmt.Errorf("invalid pieces")
|
|
}
|
|
npieces := len(ps) / 20
|
|
info.Pieces = make([][20]byte, npieces)
|
|
for i := range npieces {
|
|
copy(info.Pieces[i][:], ps[i*20:(i+1)*20])
|
|
}
|
|
if n, ok := d["length"].(int64); ok {
|
|
info.Size = n
|
|
return info, nil
|
|
}
|
|
|
|
fs, ok := d["files"].([]any)
|
|
if !ok {
|
|
return info, fmt.Errorf("invalid files")
|
|
}
|
|
|
|
off := int64(0)
|
|
for _, f := range fs {
|
|
fd, ok := f.(map[string]any)
|
|
if !ok {
|
|
return info, fmt.Errorf("invalid file entry")
|
|
}
|
|
n, ok := fd["length"].(int64)
|
|
if !ok {
|
|
return info, fmt.Errorf("invalid file length")
|
|
}
|
|
list, ok := fd["path"].([]any)
|
|
if !ok {
|
|
return info, fmt.Errorf("invalid file path")
|
|
}
|
|
var path []string
|
|
for _, p := range list {
|
|
s, ok := p.(string)
|
|
if !ok {
|
|
return info, fmt.Errorf("invalid path element")
|
|
}
|
|
path = append(path, s)
|
|
}
|
|
info.Files = append(info.Files, Entry{Size: n, Offset: off, Path: path})
|
|
off += n
|
|
}
|
|
return info, nil
|
|
}
|