Albirew/nyaa-pantsu
Albirew
/
nyaa-pantsu
Archivé
1
0
Bifurcation 0

add torrent file parsing for upload form

* vendor bencode library github.com/zeebo/bencode

* add metainfo parsing library from XD

* fix up upload handler so to be less cluttered
Cette révision appartient à :
Jeff Becker 2017-05-06 07:43:24 -04:00
Parent be07e15633
révision 11a6fce027
22 fichiers modifiés avec 1551 ajouts et 41 suppressions

Voir le fichier

@ -63,16 +63,8 @@ type SearchForm struct {
HideAdvancedSearch bool
}
type UploadForm struct {
Name string
Magnet string
Category string
Description string
}
// Some Default Values to ease things out
func NewSearchForm(params ...string) SearchForm {
searchForm := SearchForm{}
func NewSearchForm(params ...string) (searchForm SearchForm) {
if len(params) > 1 {
searchForm.Category = params[0]
} else {
@ -84,23 +76,16 @@ func NewSearchForm(params ...string) SearchForm {
searchForm.Sort = "torrent_id"
}
if len(params) > 3 {
searchForm.Order = params[2]
order := params[2]
if order == "DESC" {
searchForm.Order = order
} else if order == "ASC" {
searchForm.Order = order
} else {
// TODO: handle invalid value (?)
}
} else {
searchForm.Order = "DESC"
}
return searchForm
}
func NewUploadForm(params ...string) UploadForm {
uploadForm := UploadForm{}
if len(params) > 1 {
uploadForm.Category = params[0]
} else {
uploadForm.Category = "3_12"
}
if len(params) > 2 {
uploadForm.Description = params[1]
} else {
uploadForm.Description = "Description"
}
return uploadForm
return
}

106
router/upload.go Fichier normal
Voir le fichier

@ -0,0 +1,106 @@
package router
import (
"errors"
"github.com/ewhal/nyaa/util"
"github.com/ewhal/nyaa/util/metainfo"
"github.com/zeebo/bencode"
"net/http"
)
// UploadForm serializing HTTP form for torrent upload
type UploadForm struct {
Name string
Magnet string
Category string
Description string
}
// TODO: these should be in another package (?)
// form value for torrent name
const UploadFormName = "name"
// form value for torrent file
const UploadFormTorrent = "torrent"
// form value for magnet uri (?)
const UploadFormMagnet = "magnet"
// form value for category
const UploadFormCategory = "c"
// form value for description
const UploadFormDescription = "desc"
// error indicating a torrent is private
var ErrPrivateTorrent = errors.New("torrent is private")
// error indicating a torrent's name is invalid
var ErrInvalidTorrentName = errors.New("torrent name is invalid")
// error indicating a torrent's description is invalid
var ErrInvalidTorrentDescription = errors.New("torrent description is invalid")
/**
UploadForm.ExtractInfo takes an http request and computes all fields for this form
*/
func (f *UploadForm) ExtractInfo(r *http.Request) error {
f.Name = r.FormValue(UploadFormName)
f.Category = r.FormValue(UploadFormCategory)
f.Description = r.FormValue(UploadFormDescription)
f.Magnet = r.FormValue(UploadFormMagnet)
// trim whitespaces
f.Name = util.TrimWhitespaces(f.Name)
f.Description = util.TrimWhitespaces(f.Description)
f.Magnet = util.TrimWhitespaces(f.Magnet)
if len(f.Name) == 0 {
return ErrInvalidTorrentName
}
if len(f.Description) == 0 {
return ErrInvalidTorrentDescription
}
if len(f.Magnet) == 0 {
// try parsing torrent file if provided if no magnet is specified
tfile, _, err := r.FormFile(UploadFormTorrent)
if err != nil {
return err
}
var torrent metainfo.TorrentFile
// decode torrent
err = bencode.NewDecoder(tfile).Decode(&torrent)
if err != nil {
return metainfo.ErrInvalidTorrentFile
}
// check if torrent is private
if torrent.IsPrivate() {
return ErrPrivateTorrent
}
// generate magnet
f.Magnet = util.InfoHashToMagnet(torrent.Infohash(), f.Name)
}
return nil
}
// NewUploadForm creates a new upload form given parameters as list
func NewUploadForm(params ...string) (uploadForm UploadForm) {
if len(params) > 1 {
uploadForm.Category = params[0]
} else {
uploadForm.Category = "3_12"
}
if len(params) > 2 {
uploadForm.Description = params[1]
} else {
uploadForm.Description = "Description"
}
return
}

Voir le fichier

@ -9,26 +9,22 @@ import (
func UploadHandler(w http.ResponseWriter, r *http.Request) {
var templates = template.Must(template.New("upload").Funcs(FuncMap).ParseFiles("templates/index.html", "templates/upload.html"))
templates.ParseGlob("templates/_*.html") // common
var err error
var uploadForm UploadForm
if r.Method == "POST" {
err := r.ParseForm()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
defer r.Body.Close()
err = uploadForm.ExtractInfo(r)
if err == nil {
//validate name + hash
//add to db and redirect depending on result
}
uploadForm = UploadForm{
r.Form.Get("name"),
r.Form.Get("magnet"),
r.Form.Get("c"),
r.Form.Get("desc"),
}
//validate name + hash
//add to db and redirect depending on result
} else if r.Method == "GET" {
htv := UploadTemplateVariables{uploadForm, NewSearchForm(), Navigation{}, r.URL, mux.CurrentRoute(r)}
err = templates.ExecuteTemplate(w, "index.html", htv)
} else {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
htv := UploadTemplateVariables{uploadForm, NewSearchForm(), Navigation{}, r.URL, mux.CurrentRoute(r)}
err := templates.ExecuteTemplate(w, "index.html", htv)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}

20
util/magnet.go Fichier normal
Voir le fichier

@ -0,0 +1,20 @@
package util
import (
"encoding/hex"
"fmt"
"net/url"
)
// convert a binary infohash to a magnet uri given a display name and tracker urls
func InfoHashToMagnet(ih [20]byte, name string, trackers ...url.URL) (str string) {
str = hex.EncodeToString(ih[:])
str = fmt.Sprintf("magnet:?xt=urn:btih:%s", str)
if len(name) > 0 {
str += fmt.Sprintf("&dn=%s", name)
}
for idx := range trackers {
str += fmt.Sprintf("&tr=%s", trackers[idx].String())
}
return
}

5
util/metainfo/doc.go Fichier normal
Voir le fichier

@ -0,0 +1,5 @@
/**
package for parsing bitorrent meta info objects
originally from XD i2p bittorrent client: https://github.com/majestrate/XD
*/
package metainfo

8
util/metainfo/errors.go Fichier normal
Voir le fichier

@ -0,0 +1,8 @@
package metainfo
import (
"errors"
)
// error for indicating we have an invalid torrent file
var ErrInvalidTorrentFile = errors.New("invalid bittorrent file")

143
util/metainfo/metainfo.go Fichier normal
Voir le fichier

@ -0,0 +1,143 @@
package metainfo
// this file is from https://github.com/majestrate/XD
import (
"crypto/sha1"
"github.com/zeebo/bencode"
"io"
"os"
"path/filepath"
)
type FilePath []string
// get filepath
func (f FilePath) FilePath() string {
return filepath.Join(f...)
}
/** open file using base path */
func (f FilePath) Open(base string) (*os.File, error) {
return os.OpenFile(filepath.Join(base, f.FilePath()), os.O_RDWR|os.O_CREATE, 0600)
}
type FileInfo struct {
// length of file
Length uint64 `bencode:"length"`
// relative path of file
Path FilePath `bencode:"path"`
// md5sum
Sum []byte `bencode:"md5sum,omitempty"`
}
// info section of torrent file
type Info struct {
// length of pices in bytes
PieceLength uint32 `bencode:"piece length"`
// piece data
Pieces []byte `bencode:"pieces"`
// name of root file
Path string `bencode:"name"`
// file metadata
Files []FileInfo `bencode:"files,omitempty"`
// private torrent
Private *int64 `bencode:"private,omitempty"`
// length of file in signle file mode
Length uint64 `bencode:"length,omitempty"`
// md5sum
Sum []byte `bencode:"md5sum,omitempty"`
}
// get fileinfos from this info section
func (i Info) GetFiles() (infos []FileInfo) {
if i.Length > 0 {
infos = append(infos, FileInfo{
Length: i.Length,
Path: FilePath([]string{i.Path}),
Sum: i.Sum,
})
} else {
infos = append(infos, i.Files...)
}
return
}
func (i Info) NumPieces() uint32 {
return uint32(len(i.Pieces) / 20)
}
// a torrent file
type TorrentFile struct {
Info Info `bencode:"info"`
Announce string `bencode:"announce"`
AnnounceList [][]string `bencode:"announce-list"`
Created uint64 `bencode:"created"`
Comment []byte `bencode:"comment"`
CreatedBy []byte `bencode:"created by"`
Encoding []byte `bencode:"encoding"`
}
// get total size of files from torrent info section
func (tf *TorrentFile) TotalSize() uint64 {
if tf.IsSingleFile() {
return tf.Info.Length
}
total := uint64(0)
for _, f := range tf.Info.Files {
total += f.Length
}
return total
}
func (tf *TorrentFile) GetAllAnnounceURLS() (l []string) {
if len(tf.Announce) > 0 {
l = append(l, tf.Announce)
}
for _, al := range tf.AnnounceList {
for _, a := range al {
if len(a) > 0 {
l = append(l, a)
}
}
}
return
}
func (tf *TorrentFile) TorrentName() string {
return string(tf.Info.Path)
}
// return true if this torrent is private otherwise return false
func (tf *TorrentFile) IsPrivate() bool {
return tf.Info.Private == nil || *tf.Info.Private == 0
}
// calculate infohash
func (tf *TorrentFile) Infohash() (ih [20]byte) {
s := sha1.New()
enc := bencode.NewEncoder(s)
enc.Encode(&tf.Info)
d := s.Sum(nil)
copy(ih[:], d[:])
return
}
// return true if this torrent is for a single file
func (tf *TorrentFile) IsSingleFile() bool {
return tf.Info.Length > 0
}
// bencode this file via an io.Writer
func (tf *TorrentFile) Encode(w io.Writer) (err error) {
enc := bencode.NewEncoder(w)
err = enc.Encode(tf)
return
}
// load from an io.Reader
func (tf *TorrentFile) Decode(r io.Reader) (err error) {
dec := bencode.NewDecoder(r)
err = dec.Decode(tf)
return
}

27
util/metainfo/metainfo_test.go Fichier normal
Voir le fichier

@ -0,0 +1,27 @@
package metainfo
import (
"github.com/zeebo/bencode"
"os"
"strings"
"testing"
)
func TestLoadTorrent(t *testing.T) {
f, err := os.Open("test.torrent")
if err != nil {
t.Error(err)
}
defer f.Close()
tf := new(TorrentFile)
dec := bencode.NewDecoder(f)
err = dec.Decode(tf)
if err != nil {
t.Error(err)
}
if strings.ToUpper(tf.Infohash().Hex()) != "6BCDC07177EC43658C1B4D5450640059663A5214" {
t.Error(tf.Infohash().Hex())
}
// TODO: check members
}

17
util/whitespace.go Fichier normal
Voir le fichier

@ -0,0 +1,17 @@
package util
import (
"strings"
)
// return true if r is a whitespace rune
func IsWhitespace(r rune) bool {
return r == '\n' || r == '\t' || r == '\r' || r == ' '
}
// trim whitespaces from a string
func TrimWhitespaces(s string) string {
s = strings.TrimLeftFunc(s, IsWhitespace)
s = strings.TrimRightFunc(s, IsWhitespace)
return s
}

6
vendor/github.com/zeebo/bencode/.gitignore générée externe Fichier normal
Voir le fichier

@ -0,0 +1,6 @@
*.6
_test
_obj
test
*.torrent
main.go

7
vendor/github.com/zeebo/bencode/AUTHORS générée externe Fichier normal
Voir le fichier

@ -0,0 +1,7 @@
Jeff Wendling <shadynasty.biz>
Liam Edwards-Playne <liamz.co>
Casey Bodley <cbodley@gmail.com>
Conrad Pankoff <deoxxa@fknsrs.biz>
Cenk Alti <cenkalti@gmail.com>
Jan Winkelmann <j-winkelmann@tuhh.de>
Patrick Mézard <patrick@mezard.eu>

19
vendor/github.com/zeebo/bencode/LICENSE générée externe Fichier normal
Voir le fichier

@ -0,0 +1,19 @@
Copyright (c) 2013 The Authors (see AUTHORS)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

9
vendor/github.com/zeebo/bencode/README.md générée externe Fichier normal
Voir le fichier

@ -0,0 +1,9 @@
# bencode
Bencode is a library for encoding/decoding bencoded data into Go data structures.
The api is designed to be similar to the JSON api in the Go standard library.
## Documentation
Documentation at http://zeebo.github.com/bencode
or http://godoc.org/github.com/zeebo/bencode

442
vendor/github.com/zeebo/bencode/decode.go générée externe Fichier normal
Voir le fichier

@ -0,0 +1,442 @@
package bencode
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"reflect"
"strconv"
"strings"
)
var (
reflectByteSliceType = reflect.TypeOf([]byte(nil))
reflectStringType = reflect.TypeOf("")
)
//A Decoder reads and decodes bencoded data from an input stream.
type Decoder struct {
r *bufio.Reader
raw bool
buf []byte
n int
}
//BytesParsed returns the number of bytes that have actually been parsed
func (d *Decoder) BytesParsed() int {
return d.n
}
//read also writes into the buffer when d.raw is set.
func (d *Decoder) read(p []byte) (n int, err error) {
n, err = d.r.Read(p)
if d.raw {
d.buf = append(d.buf, p[:n]...)
}
d.n += n
return
}
//readBytes also writes into the buffer when d.raw is set.
func (d *Decoder) readBytes(delim byte) (line []byte, err error) {
line, err = d.r.ReadBytes(delim)
if d.raw {
d.buf = append(d.buf, line...)
}
d.n += len(line)
return
}
//readByte also writes into the buffer when d.raw is set.
func (d *Decoder) readByte() (b byte, err error) {
b, err = d.r.ReadByte()
if d.raw {
d.buf = append(d.buf, b)
}
d.n += 1
return
}
//readFull also writes into the buffer when d.raw is set.
func (d *Decoder) readFull(p []byte) (n int, err error) {
n, err = io.ReadFull(d.r, p)
if d.raw {
d.buf = append(d.buf, p[:n]...)
}
d.n += n
return
}
func (d *Decoder) peekByte() (b byte, err error) {
ch, err := d.r.Peek(1)
if err != nil {
return
}
b = ch[0]
return
}
//NewDecoder returns a new decoder that reads from r
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: bufio.NewReader(r)}
}
//Decode reads the bencoded value from its input and stores it in the value pointed to by val.
//Decode allocates maps/slices as necessary with the following additional rules:
//To decode a bencoded value into a nil interface value, the type stored in the interface value is one of:
// int64 for bencoded integers
// string for bencoded strings
// []interface{} for bencoded lists
// map[string]interface{} for bencoded dicts
func (d *Decoder) Decode(val interface{}) error {
rv := reflect.ValueOf(val)
if rv.Kind() != reflect.Ptr || rv.IsNil() {
return errors.New("Unwritable type passed into decode")
}
return d.decodeInto(rv)
}
//DecodeString reads the data in the string and stores it into the value pointed to by val.
//Read the docs for Decode for more information.
func DecodeString(in string, val interface{}) error {
buf := strings.NewReader(in)
d := NewDecoder(buf)
return d.Decode(val)
}
//DecodeBytes reads the data in b and stores it into the value pointed to by val.
//Read the docs for Decode for more information.
func DecodeBytes(b []byte, val interface{}) error {
r := bytes.NewReader(b)
d := NewDecoder(r)
return d.Decode(val)
}
func indirect(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() {
v = v.Addr()
}
for {
if v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
continue
}
if v.Kind() != reflect.Ptr {
break
}
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
v = v.Elem()
}
return v
}
func (d *Decoder) decodeInto(val reflect.Value) (err error) {
v := indirect(val)
//if we're decoding into a RawMessage set raw to true for the rest of
//the call stack, and switch out the value with an interface{}.
if _, ok := v.Interface().(RawMessage); ok && !d.raw {
var x interface{}
v = reflect.ValueOf(&x).Elem()
//set d.raw for the lifetime of this function call, and set the raw
//message when the function is exiting.
d.buf = d.buf[:0]
d.raw = true
defer func() {
d.raw = false
v := indirect(val)
v.SetBytes(append([]byte(nil), d.buf...))
}()
}
next, err := d.peekByte()
if err != nil {
return
}
switch next {
case 'i':
err = d.decodeInt(v)
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
err = d.decodeString(v)
case 'l':
err = d.decodeList(v)
case 'd':
err = d.decodeDict(v)
default:
err = errors.New("Invalid input")
}
return
}
func (d *Decoder) decodeInt(v reflect.Value) error {
//we need to read an i, some digits, and an e.
ch, err := d.readByte()
if err != nil {
return err
}
if ch != 'i' {
panic("got not an i when peek returned an i")
}
line, err := d.readBytes('e')
if err != nil {
return err
}
digits := string(line[:len(line)-1])
switch v.Kind() {
default:
return fmt.Errorf("Cannot store int64 into %s", v.Type())
case reflect.Interface:
n, err := strconv.ParseInt(digits, 10, 64)
if err != nil {
return err
}
v.Set(reflect.ValueOf(n))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n, err := strconv.ParseInt(digits, 10, 64)
if err != nil {
return err
}
v.SetInt(n)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
n, err := strconv.ParseUint(digits, 10, 64)
if err != nil {
return err
}
v.SetUint(n)
case reflect.Bool:
n, err := strconv.ParseUint(digits, 10, 64)
if err != nil {
return err
}
v.SetBool(n != 0)
}
return nil
}
func (d *Decoder) decodeString(v reflect.Value) error {
//read until a colon to get the number of digits to read after
line, err := d.readBytes(':')
if err != nil {
return err
}
//parse it into an int for making a slice
l32, err := strconv.ParseInt(string(line[:len(line)-1]), 10, 32)
l := int(l32)
if err != nil {
return err
}
if l < 0 {
return fmt.Errorf("invalid negative string length: %d", l)
}
//read exactly l bytes out and make our string
buf := make([]byte, l)
_, err = d.readFull(buf)
if err != nil {
return err
}
switch v.Kind() {
default:
return fmt.Errorf("Cannot store string into %s", v.Type())
case reflect.Slice:
if v.Type() != reflectByteSliceType {
return fmt.Errorf("Cannot store string into %s", v.Type())
}
v.SetBytes(buf)
case reflect.String:
v.SetString(string(buf))
case reflect.Interface:
v.Set(reflect.ValueOf(string(buf)))
}
return nil
}
func (d *Decoder) decodeList(v reflect.Value) error {
//if we have an interface, just put a []interface{} in it!
if v.Kind() == reflect.Interface {
var x []interface{}
defer func(p reflect.Value) { p.Set(v) }(v)
v = reflect.ValueOf(&x).Elem()
}
if v.Kind() != reflect.Array && v.Kind() != reflect.Slice {
return fmt.Errorf("Cant store a []interface{} into %s", v.Type())
}
//read out the l that prefixes the list
ch, err := d.readByte()
if err != nil {
return err
}
if ch != 'l' {
panic("got something other than a list head after a peek")
}
for i := 0; ; i++ {
//peek for the end token and read it out
ch, err := d.peekByte()
if err != nil {
return err
}
switch ch {
case 'e':
_, err := d.readByte() //consume the end
return err
}
//grow it if required
if i >= v.Cap() && v.IsValid() {
newcap := v.Cap() + v.Cap()/2
if newcap < 4 {
newcap = 4
}
newv := reflect.MakeSlice(v.Type(), v.Len(), newcap)
reflect.Copy(newv, v)
v.Set(newv)
}
//reslice into cap (its a slice now since it had to have grown)
if i >= v.Len() && v.IsValid() {
v.SetLen(i + 1)
}
//decode a value into the index
if err := d.decodeInto(v.Index(i)); err != nil {
return err
}
}
panic("unreachable")
}
func (d *Decoder) decodeDict(v reflect.Value) error {
//if we have an interface{}, just put a map[string]interface{} in it!
if v.Kind() == reflect.Interface {
var x map[string]interface{}
defer func(p reflect.Value) { p.Set(v) }(v)
v = reflect.ValueOf(&x).Elem()
}
//consume the head token
ch, err := d.readByte()
if err != nil {
return err
}
if ch != 'd' {
panic("got an incorrect token when it was checked already")
}
//check for correct type
var (
f reflect.StructField
mapElem reflect.Value
isMap bool
)
switch v.Kind() {
case reflect.Map:
t := v.Type()
if t.Key() != reflectStringType {
return fmt.Errorf("Can't store a map[string]interface{} into %s", v.Type())
}
if v.IsNil() {
v.Set(reflect.MakeMap(t))
}
isMap = true
mapElem = reflect.New(t.Elem()).Elem()
case reflect.Struct:
default:
return fmt.Errorf("Can't store a map[string]interface{} into %s", v.Type())
}
for {
var subv reflect.Value
//peek the next value type
ch, err := d.peekByte()
if err != nil {
return err
}
if ch == 'e' {
_, err = d.readByte() //consume the end token
return err
}
//peek the next value we're suppsed to read
var key string
if err := d.decodeString(reflect.ValueOf(&key).Elem()); err != nil {
return err
}
if isMap {
mapElem.Set(reflect.Zero(v.Type().Elem()))
subv = mapElem
} else {
var ok bool
t := v.Type()
if isValidTag(key) {
for i := 0; i < v.NumField(); i++ {
f = t.Field(i)
tagName, _ := parseTag(f.Tag.Get("bencode"))
if tagName == key && tagName != "-" {
// If we have found a matching tag
// that isn't '-'
ok = true
break
}
}
}
if !ok {
f, ok = t.FieldByName(key)
}
if !ok {
f, ok = t.FieldByNameFunc(matchName(key))
}
if ok {
if f.PkgPath != "" && !f.Anonymous {
return fmt.Errorf("Can't store into unexported field: %s", f)
}
subv = v.FieldByIndex(f.Index)
}
}
if !subv.IsValid() {
//if it's invalid, grab but ignore the next value
var x interface{}
err := d.decodeInto(reflect.ValueOf(&x).Elem())
if err != nil {
return err
}
continue
}
//subv now contains what we load into
if err := d.decodeInto(subv); err != nil {
return err
}
if isMap {
v.SetMapIndex(reflect.ValueOf(key), subv)
}
}
panic("unreachable")
}

182
vendor/github.com/zeebo/bencode/decode_test.go générée externe Fichier normal
Voir le fichier

@ -0,0 +1,182 @@
package bencode
import (
"reflect"
"testing"
)
func TestDecode(t *testing.T) {
type testCase struct {
in string
val interface{}
expect interface{}
err bool
}
type dT struct {
X string
Y int
Z string `bencode:"zff"`
}
var decodeCases = []testCase{
//integers
{`i5e`, new(int), int(5), false},
{`i-10e`, new(int), int(-10), false},
{`i8e`, new(uint), uint(8), false},
{`i8e`, new(uint8), uint8(8), false},
{`i8e`, new(uint16), uint16(8), false},
{`i8e`, new(uint32), uint32(8), false},
{`i8e`, new(uint64), uint64(8), false},
{`i8e`, new(int), int(8), false},
{`i8e`, new(int8), int8(8), false},
{`i8e`, new(int16), int16(8), false},
{`i8e`, new(int32), int32(8), false},
{`i8e`, new(int64), int64(8), false},
{`i-2e`, new(uint), nil, true},
//bools
{`i1e`, new(bool), true, false},
{`i0e`, new(bool), false, false},
{`i8e`, new(bool), true, false},
//strings
{`3:foo`, new(string), "foo", false},
{`4:foob`, new(string), "foob", false},
{`6:short`, new(string), nil, true},
//lists
{`l3:foo3:bare`, new([]string), []string{"foo", "bar"}, false},
{`li15ei20ee`, new([]int), []int{15, 20}, false},
{`ld3:fooi0eed3:bari1eee`, new([]map[string]int), []map[string]int{
{"foo": 0},
{"bar": 1},
}, false},
//dicts
{`d3:foo3:bar4:foob3:fooe`, new(map[string]string), map[string]string{
"foo": "bar",
"foob": "foo",
}, false},
{`d1:X3:foo1:Yi10e3:zff3:bare`, new(dT), dT{"foo", 10, "bar"}, false},
{`d1:X3:foo1:Yi10e1:Z3:bare`, new(dT), dT{"foo", 10, "bar"}, false},
{`d1:X3:foo1:Yi10e1:h3:bare`, new(dT), dT{"foo", 10, ""}, false},
{`d3:fooli0ei1ee3:barli2ei3eee`, new(map[string][]int), map[string][]int{
"foo": []int{0, 1},
"bar": []int{2, 3},
}, false},
{`de`, new(map[string]string), map[string]string{}, false},
//into interfaces
{`i5e`, new(interface{}), int64(5), false},
{`li5ee`, new(interface{}), []interface{}{int64(5)}, false},
{`5:hello`, new(interface{}), "hello", false},
{`d5:helloi5ee`, new(interface{}), map[string]interface{}{"hello": int64(5)}, false},
//malformed
{`i53:foo`, new(interface{}), nil, true},
{`6:foo`, new(interface{}), nil, true},
{`di5ei2ee`, new(interface{}), nil, true},
{`d3:fooe`, new(interface{}), nil, true},
{`l3:foo3:bar`, new(interface{}), nil, true},
{`d-1:`, new(interface{}), nil, true},
}
for i, tt := range decodeCases {
err := DecodeString(tt.in, tt.val)
if !tt.err && err != nil {
t.Errorf("#%d: Unexpected err: %v", i, err)
continue
}
if tt.err && err == nil {
t.Errorf("#%d: Expected err is nil", i)
continue
}
v := reflect.ValueOf(tt.val).Elem().Interface()
if !reflect.DeepEqual(v, tt.expect) && !tt.err {
t.Errorf("#%d: Val: %#v != %#v", i, v, tt.expect)
}
}
}
func TestRawDecode(t *testing.T) {
type testCase struct {
in string
expect []byte
err bool
}
var rawDecodeCases = []testCase{
{`i5e`, []byte(`i5e`), false},
{`5:hello`, []byte(`5:hello`), false},
{`li5ei10e5:helloe`, []byte(`li5ei10e5:helloe`), false},
{`llleee`, []byte(`llleee`), false},
{`li5eli5eli5eeee`, []byte(`li5eli5eli5eeee`), false},
{`d5:helloi5ee`, []byte(`d5:helloi5ee`), false},
}
for i, tt := range rawDecodeCases {
var x RawMessage
err := DecodeString(tt.in, &x)
if !tt.err && err != nil {
t.Errorf("#%d: Unexpected err: %v", i, err)
continue
}
if tt.err && err == nil {
t.Errorf("#%d: Expected err is nil", i)
continue
}
if !reflect.DeepEqual(x, RawMessage(tt.expect)) && !tt.err {
t.Errorf("#%d: Val: %#v != %#v", i, x, tt.expect)
}
}
}
func TestNestedRawDecode(t *testing.T) {
type testCase struct {
in string
val interface{}
expect interface{}
err bool
}
type message struct {
Key string
Val int
Raw RawMessage
}
var cases = []testCase{
{`li5e5:hellod1:a1:beli5eee`, new([]RawMessage), []RawMessage{
RawMessage(`i5e`),
RawMessage(`5:hello`),
RawMessage(`d1:a1:be`),
RawMessage(`li5ee`),
}, false},
{`d1:a1:b1:c1:de`, new(map[string]RawMessage), map[string]RawMessage{
"a": RawMessage(`1:b`),
"c": RawMessage(`1:d`),
}, false},
{`d3:Key5:hello3:Rawldedei5e1:ae3:Vali10ee`, new(message), message{
Key: "hello",
Val: 10,
Raw: RawMessage(`ldedei5e1:ae`),
}, false},
}
for i, tt := range cases {
err := DecodeString(tt.in, tt.val)
if !tt.err && err != nil {
t.Errorf("#%d: Unexpected err: %v", i, err)
continue
}
if tt.err && err == nil {
t.Errorf("#%d: Expected err is nil", i)
continue
}
v := reflect.ValueOf(tt.val).Elem().Interface()
if !reflect.DeepEqual(v, tt.expect) && !tt.err {
t.Errorf("#%d: Val:\n%#v !=\n%#v", i, v, tt.expect)
}
}
}

7
vendor/github.com/zeebo/bencode/doc.go générée externe Fichier normal
Voir le fichier

@ -0,0 +1,7 @@
/*
Package bencode implements encoding and decoding of bencoded objects.
It has a similar API to the encoding/json package and many other
serialization formats.
*/
package bencode

218
vendor/github.com/zeebo/bencode/encode.go générée externe Fichier normal
Voir le fichier

@ -0,0 +1,218 @@
package bencode
import (
"bytes"
"fmt"
"io"
"reflect"
"sort"
)
type sortValues []reflect.Value
func (p sortValues) Len() int { return len(p) }
func (p sortValues) Less(i, j int) bool { return p[i].String() < p[j].String() }
func (p sortValues) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
type sortFields []reflect.StructField
func (p sortFields) Len() int { return len(p) }
func (p sortFields) Less(i, j int) bool {
iName, jName := p[i].Name, p[j].Name
if name, _ := parseTag(p[i].Tag.Get("bencode")); name != "" {
iName = name
}
if name, _ := parseTag(p[j].Tag.Get("bencode")); name != "" {
jName = name
}
return iName < jName
}
func (p sortFields) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
//An Encoder writes bencoded objects to an output stream.
type Encoder struct {
w io.Writer
}
//NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w}
}
//Encode writes the bencoded data of val to its output stream.
//See the documentation for Decode about the conversion of Go values to
//bencoded data.
func (e *Encoder) Encode(val interface{}) error {
return encodeValue(e.w, reflect.ValueOf(val))
}
//EncodeString returns the bencoded data of val as a string.
func EncodeString(val interface{}) (string, error) {
buf := new(bytes.Buffer)
e := NewEncoder(buf)
if err := e.Encode(val); err != nil {
return "", err
}
return buf.String(), nil
}
//EncodeBytes returns the bencoded data of val as a slice of bytes.
func EncodeBytes(val interface{}) ([]byte, error) {
buf := new(bytes.Buffer)
e := NewEncoder(buf)
if err := e.Encode(val); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func encodeValue(w io.Writer, val reflect.Value) error {
//inspect the val to check
v := indirect(val)
//send in a raw message if we have that type
if rm, ok := v.Interface().(RawMessage); ok {
_, err := io.Copy(w, bytes.NewReader(rm))
return err
}
switch v.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
_, err := fmt.Fprintf(w, "i%de", v.Int())
return err
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
_, err := fmt.Fprintf(w, "i%de", v.Uint())
return err
case reflect.Bool:
i := 0
if v.Bool() {
i = 1
}
_, err := fmt.Fprintf(w, "i%de", i)
return err
case reflect.String:
_, err := fmt.Fprintf(w, "%d:%s", len(v.String()), v.String())
return err
case reflect.Slice, reflect.Array:
// handle byte slices like strings
if byteSlice, ok := val.Interface().([]byte); ok {
_, err := fmt.Fprintf(w, "%d:", len(byteSlice))
if err == nil {
_, err = w.Write(byteSlice)
}
return err
}
if _, err := fmt.Fprint(w, "l"); err != nil {
return err
}
for i := 0; i < v.Len(); i++ {
if err := encodeValue(w, v.Index(i)); err != nil {
return err
}
}
_, err := fmt.Fprint(w, "e")
return err
case reflect.Map:
if _, err := fmt.Fprint(w, "d"); err != nil {
return err
}
var (
keys sortValues = v.MapKeys()
mval reflect.Value
)
sort.Sort(keys)
for i := range keys {
if err := encodeValue(w, keys[i]); err != nil {
return err
}
mval = v.MapIndex(keys[i])
if err := encodeValue(w, mval); err != nil {
return err
}
}
_, err := fmt.Fprint(w, "e")
return err
case reflect.Struct:
t := v.Type()
if _, err := fmt.Fprint(w, "d"); err != nil {
return err
}
//put keys into keys
var (
keys = make(sortFields, t.NumField())
fieldValue reflect.Value
rkey reflect.Value
)
for i := range keys {
keys[i] = t.Field(i)
}
sort.Sort(keys)
for _, key := range keys {
rkey = reflect.ValueOf(key.Name)
fieldValue = v.FieldByIndex(key.Index)
// filter out unexported values etc.
if !fieldValue.CanInterface() {
continue
}
/* Tags
* Near identical to usage in JSON except with key 'bencode'
* Struct values encode as BEncode dictionaries. Each exported
struct field becomes a set in the dictionary unless
- the field's tag is "-", or
- the field is empty and its tag specifies the "omitempty"
option.
* The default key string is the struct field name but can be
specified in the struct field's tag value. The "bencode"
key in struct field's tag value is the key name, followed
by an optional comma and options.
*/
tagValue := key.Tag.Get("bencode")
if tagValue != "" {
// Keys with '-' are omit from output
if tagValue == "-" {
continue
}
name, options := parseTag(tagValue)
// Keys with 'omitempty' are omitted if the field is empty
if options.Contains("omitempty") && isEmptyValue(fieldValue) {
continue
}
// All other values are treated as the key string
if isValidTag(name) {
rkey = reflect.ValueOf(name)
}
}
//encode the key
if err := encodeValue(w, rkey); err != nil {
return err
}
//encode the value
if err := encodeValue(w, fieldValue); err != nil {
return err
}
}
_, err := fmt.Fprint(w, "e")
return err
}
return fmt.Errorf("Can't encode type: %s", v.Type())
}

142
vendor/github.com/zeebo/bencode/encode_test.go générée externe Fichier normal
Voir le fichier

@ -0,0 +1,142 @@
package bencode
import "testing"
func TestEncode(t *testing.T) {
type encodeTestCase struct {
in interface{}
out string
err bool
}
type eT struct {
A string
X string `bencode:"D"`
Y string `bencode:"B"`
Z string `bencode:"C"`
}
type sortProblem struct {
A string
B string `bencode:","`
}
var encodeCases = []encodeTestCase{
//integers
{10, `i10e`, false},
{-10, `i-10e`, false},
{0, `i0e`, false},
{int(10), `i10e`, false},
{int8(10), `i10e`, false},
{int16(10), `i10e`, false},
{int32(10), `i10e`, false},
{int64(10), `i10e`, false},
{uint(10), `i10e`, false},
{uint8(10), `i10e`, false},
{uint16(10), `i10e`, false},
{uint32(10), `i10e`, false},
{uint64(10), `i10e`, false},
//strings
{"foo", `3:foo`, false},
{"barbb", `5:barbb`, false},
{"", `0:`, false},
//lists
{[]interface{}{"foo", 20}, `l3:fooi20ee`, false},
{[]interface{}{90, 20}, `li90ei20ee`, false},
{[]interface{}{[]interface{}{"foo", "bar"}, 20}, `ll3:foo3:barei20ee`, false},
{[]map[string]int{
{"a": 0, "b": 1},
{"c": 2, "d": 3},
}, `ld1:ai0e1:bi1eed1:ci2e1:di3eee`, false},
{[][]byte{
[]byte{'0', '2', '4', '6', '8'},
[]byte{'a', 'c', 'e'},
}, `l5:024683:acee`, false},
//boolean
{true, "i1e", false},
{false, "i0e", false},
//dicts
{map[string]interface{}{
"a": "foo",
"c": "bar",
"b": "tes",
}, `d1:a3:foo1:b3:tes1:c3:bare`, false},
{eT{"foo", "bar", "far", "boo"}, `d1:A3:foo1:B3:far1:C3:boo1:D3:bare`, false},
{map[string][]int{
"a": {0, 1},
"b": {2, 3},
}, `d1:ali0ei1ee1:bli2ei3eee`, false},
{struct{ A, b int }{1, 2}, "d1:Ai1ee", false},
//raw
{RawMessage(`i5e`), `i5e`, false},
{[]RawMessage{
RawMessage(`i5e`),
RawMessage(`5:hello`),
RawMessage(`ldededee`),
}, `li5e5:helloldededeee`, false},
{map[string]RawMessage{
"a": RawMessage(`i5e`),
"b": RawMessage(`5:hello`),
"c": RawMessage(`ldededee`),
}, `d1:ai5e1:b5:hello1:cldededeee`, false},
//problem sorting
{sortProblem{A: "foo", B: "bar"}, `d1:A3:foo1:B3:bare`, false},
}
for i, tt := range encodeCases {
data, err := EncodeString(tt.in)
if !tt.err && err != nil {
t.Errorf("#%d: Unexpected err: %v", i, err)
continue
}
if tt.err && err == nil {
t.Errorf("#%d: Expected err is nil", i)
continue
}
if tt.out != data {
t.Errorf("#%d: Val: %q != %q", i, data, tt.out)
}
}
}
func TestEncodeOmit(t *testing.T) {
type encodeTestCase struct {
in interface{}
out string
err bool
}
type eT struct {
A string `bencode:",omitempty"`
B int `bencode:",omitempty"`
C *int `bencode:",omitempty"`
}
var encodeCases = []encodeTestCase{
{eT{}, `de`, false},
{eT{A: "a"}, `d1:A1:ae`, false},
{eT{B: 5}, `d1:Bi5ee`, false},
{eT{C: new(int)}, `d1:Ci0ee`, false},
}
for i, tt := range encodeCases {
data, err := EncodeString(tt.in)
if !tt.err && err != nil {
t.Errorf("#%d: Unexpected err: %v", i, err)
continue
}
if tt.err && err == nil {
t.Errorf("#%d: Expected err is nil", i)
continue
}
if tt.out != data {
t.Errorf("#%d: Val: %q != %q", i, data, tt.out)
}
}
}

67
vendor/github.com/zeebo/bencode/example_test.go générée externe Fichier normal
Voir le fichier

@ -0,0 +1,67 @@
package bencode
import (
"fmt"
"io"
)
var (
data string
r io.Reader
w io.Writer
)
func ExampleDecodeString() {
var torrent interface{}
if err := DecodeString(data, &torrent); err != nil {
panic(err)
}
}
func ExampleEncodeString() {
var torrent interface{}
data, err := EncodeString(torrent)
if err != nil {
panic(err)
}
fmt.Println(data)
}
func ExampleDecodeBytes() {
var torrent interface{}
if err := DecodeBytes([]byte(data), &torrent); err != nil {
panic(err)
}
}
func ExampleEncodeBytes() {
var torrent interface{}
data, err := EncodeBytes(torrent)
if err != nil {
panic(err)
}
fmt.Println(data)
}
func ExampleEncoder_Encode() {
var x struct {
Foo string
Bar []string `bencode:"name"`
}
enc := NewEncoder(w)
if err := enc.Encode(x); err != nil {
panic(err)
}
}
func ExampleDecoder_Decode() {
dec := NewDecoder(r)
var torrent struct {
Announce string
List [][]string `bencode:"announce-list"`
}
if err := dec.Decode(&torrent); err != nil {
panic(err)
}
}

5
vendor/github.com/zeebo/bencode/raw.go générée externe Fichier normal
Voir le fichier

@ -0,0 +1,5 @@
package bencode
//RawMessage is a special type that will store the raw bencode data when
//encoding or decoding.
type RawMessage []byte

76
vendor/github.com/zeebo/bencode/tag.go générée externe Fichier normal
Voir le fichier

@ -0,0 +1,76 @@
package bencode
import (
"reflect"
"strings"
"unicode"
)
// tagOptions is the string following a comma in a struct field's "bencode"
// tag, or the empty string. It does not include the leading comma.
type tagOptions string
// parseTag splits a struct field's tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, tagOptions("")
}
// Contains returns whether checks that a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (options tagOptions) Contains(optionName string) bool {
s := string(options)
for s != "" {
var next string
i := strings.Index(s, ",")
if i >= 0 {
s, next = s[:i], s[i+1:]
}
if s == optionName {
return true
}
s = next
}
return false
}
func isValidTag(key string) bool {
if key == "" {
return false
}
for _, c := range key {
if c != ' ' && c != '$' && c != '-' && c != '_' && c != '.' && !unicode.IsLetter(c) && !unicode.IsDigit(c) {
return false
}
}
return true
}
func matchName(key string) func(string) bool {
return func(s string) bool {
return strings.ToLower(key) == strings.ToLower(s)
}
}
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return false
}

23
vendor/github.com/zeebo/bencode/tracer.go générée externe Fichier normal
Voir le fichier

@ -0,0 +1,23 @@
package bencode
import (
"log"
"strings"
)
var traceDepth int
func prefix() string {
return strings.Repeat("\t", traceDepth)
}
func un(val string) {
traceDepth--
log.Print(prefix(), "<-", val)
}
func trace(val string) string {
log.Print(prefix(), "->", val)
traceDepth++
return val
}