From 11a6fce027e860c48b2c7f30f7d0b2633df29fc8 Mon Sep 17 00:00:00 2001 From: Jeff Becker Date: Sat, 6 May 2017 07:43:24 -0400 Subject: [PATCH] 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 --- router/templateVariables.go | 35 +- router/upload.go | 106 +++++ router/uploadHandler.go | 28 +- util/magnet.go | 20 + util/metainfo/doc.go | 5 + util/metainfo/errors.go | 8 + util/metainfo/metainfo.go | 143 ++++++ util/metainfo/metainfo_test.go | 27 ++ util/whitespace.go | 17 + vendor/github.com/zeebo/bencode/.gitignore | 6 + vendor/github.com/zeebo/bencode/AUTHORS | 7 + vendor/github.com/zeebo/bencode/LICENSE | 19 + vendor/github.com/zeebo/bencode/README.md | 9 + vendor/github.com/zeebo/bencode/decode.go | 442 ++++++++++++++++++ .../github.com/zeebo/bencode/decode_test.go | 182 ++++++++ vendor/github.com/zeebo/bencode/doc.go | 7 + vendor/github.com/zeebo/bencode/encode.go | 218 +++++++++ .../github.com/zeebo/bencode/encode_test.go | 142 ++++++ .../github.com/zeebo/bencode/example_test.go | 67 +++ vendor/github.com/zeebo/bencode/raw.go | 5 + vendor/github.com/zeebo/bencode/tag.go | 76 +++ vendor/github.com/zeebo/bencode/tracer.go | 23 + 22 files changed, 1551 insertions(+), 41 deletions(-) create mode 100644 router/upload.go create mode 100644 util/magnet.go create mode 100644 util/metainfo/doc.go create mode 100644 util/metainfo/errors.go create mode 100644 util/metainfo/metainfo.go create mode 100644 util/metainfo/metainfo_test.go create mode 100644 util/whitespace.go create mode 100644 vendor/github.com/zeebo/bencode/.gitignore create mode 100644 vendor/github.com/zeebo/bencode/AUTHORS create mode 100644 vendor/github.com/zeebo/bencode/LICENSE create mode 100644 vendor/github.com/zeebo/bencode/README.md create mode 100644 vendor/github.com/zeebo/bencode/decode.go create mode 100644 vendor/github.com/zeebo/bencode/decode_test.go create mode 100644 vendor/github.com/zeebo/bencode/doc.go create mode 100644 vendor/github.com/zeebo/bencode/encode.go create mode 100644 vendor/github.com/zeebo/bencode/encode_test.go create mode 100644 vendor/github.com/zeebo/bencode/example_test.go create mode 100644 vendor/github.com/zeebo/bencode/raw.go create mode 100644 vendor/github.com/zeebo/bencode/tag.go create mode 100644 vendor/github.com/zeebo/bencode/tracer.go diff --git a/router/templateVariables.go b/router/templateVariables.go index 97f9e644..657b9a02 100644 --- a/router/templateVariables.go +++ b/router/templateVariables.go @@ -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 } diff --git a/router/upload.go b/router/upload.go new file mode 100644 index 00000000..44a2f077 --- /dev/null +++ b/router/upload.go @@ -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 +} diff --git a/router/uploadHandler.go b/router/uploadHandler.go index 5505a0d9..999a909b 100644 --- a/router/uploadHandler.go +++ b/router/uploadHandler.go @@ -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) } diff --git a/util/magnet.go b/util/magnet.go new file mode 100644 index 00000000..32a65b8e --- /dev/null +++ b/util/magnet.go @@ -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 +} diff --git a/util/metainfo/doc.go b/util/metainfo/doc.go new file mode 100644 index 00000000..523604e9 --- /dev/null +++ b/util/metainfo/doc.go @@ -0,0 +1,5 @@ +/** +package for parsing bitorrent meta info objects +originally from XD i2p bittorrent client: https://github.com/majestrate/XD +*/ +package metainfo diff --git a/util/metainfo/errors.go b/util/metainfo/errors.go new file mode 100644 index 00000000..df666c63 --- /dev/null +++ b/util/metainfo/errors.go @@ -0,0 +1,8 @@ +package metainfo + +import ( + "errors" +) + +// error for indicating we have an invalid torrent file +var ErrInvalidTorrentFile = errors.New("invalid bittorrent file") diff --git a/util/metainfo/metainfo.go b/util/metainfo/metainfo.go new file mode 100644 index 00000000..61a57ee1 --- /dev/null +++ b/util/metainfo/metainfo.go @@ -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 +} diff --git a/util/metainfo/metainfo_test.go b/util/metainfo/metainfo_test.go new file mode 100644 index 00000000..77ce0d57 --- /dev/null +++ b/util/metainfo/metainfo_test.go @@ -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 +} diff --git a/util/whitespace.go b/util/whitespace.go new file mode 100644 index 00000000..b309293f --- /dev/null +++ b/util/whitespace.go @@ -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 +} diff --git a/vendor/github.com/zeebo/bencode/.gitignore b/vendor/github.com/zeebo/bencode/.gitignore new file mode 100644 index 00000000..846eb6d2 --- /dev/null +++ b/vendor/github.com/zeebo/bencode/.gitignore @@ -0,0 +1,6 @@ +*.6 +_test +_obj +test +*.torrent +main.go diff --git a/vendor/github.com/zeebo/bencode/AUTHORS b/vendor/github.com/zeebo/bencode/AUTHORS new file mode 100644 index 00000000..a00a6719 --- /dev/null +++ b/vendor/github.com/zeebo/bencode/AUTHORS @@ -0,0 +1,7 @@ +Jeff Wendling +Liam Edwards-Playne +Casey Bodley +Conrad Pankoff +Cenk Alti +Jan Winkelmann +Patrick Mézard diff --git a/vendor/github.com/zeebo/bencode/LICENSE b/vendor/github.com/zeebo/bencode/LICENSE new file mode 100644 index 00000000..67a86898 --- /dev/null +++ b/vendor/github.com/zeebo/bencode/LICENSE @@ -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. diff --git a/vendor/github.com/zeebo/bencode/README.md b/vendor/github.com/zeebo/bencode/README.md new file mode 100644 index 00000000..d428c70c --- /dev/null +++ b/vendor/github.com/zeebo/bencode/README.md @@ -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 diff --git a/vendor/github.com/zeebo/bencode/decode.go b/vendor/github.com/zeebo/bencode/decode.go new file mode 100644 index 00000000..ad00f226 --- /dev/null +++ b/vendor/github.com/zeebo/bencode/decode.go @@ -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") +} diff --git a/vendor/github.com/zeebo/bencode/decode_test.go b/vendor/github.com/zeebo/bencode/decode_test.go new file mode 100644 index 00000000..09b25462 --- /dev/null +++ b/vendor/github.com/zeebo/bencode/decode_test.go @@ -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) + } + } +} diff --git a/vendor/github.com/zeebo/bencode/doc.go b/vendor/github.com/zeebo/bencode/doc.go new file mode 100644 index 00000000..d9f5a000 --- /dev/null +++ b/vendor/github.com/zeebo/bencode/doc.go @@ -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 diff --git a/vendor/github.com/zeebo/bencode/encode.go b/vendor/github.com/zeebo/bencode/encode.go new file mode 100644 index 00000000..13c2518b --- /dev/null +++ b/vendor/github.com/zeebo/bencode/encode.go @@ -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()) +} diff --git a/vendor/github.com/zeebo/bencode/encode_test.go b/vendor/github.com/zeebo/bencode/encode_test.go new file mode 100644 index 00000000..94ec17bd --- /dev/null +++ b/vendor/github.com/zeebo/bencode/encode_test.go @@ -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) + } + } +} diff --git a/vendor/github.com/zeebo/bencode/example_test.go b/vendor/github.com/zeebo/bencode/example_test.go new file mode 100644 index 00000000..98d361a8 --- /dev/null +++ b/vendor/github.com/zeebo/bencode/example_test.go @@ -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) + } +} diff --git a/vendor/github.com/zeebo/bencode/raw.go b/vendor/github.com/zeebo/bencode/raw.go new file mode 100644 index 00000000..f6f29f4f --- /dev/null +++ b/vendor/github.com/zeebo/bencode/raw.go @@ -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 diff --git a/vendor/github.com/zeebo/bencode/tag.go b/vendor/github.com/zeebo/bencode/tag.go new file mode 100644 index 00000000..03822769 --- /dev/null +++ b/vendor/github.com/zeebo/bencode/tag.go @@ -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 +} diff --git a/vendor/github.com/zeebo/bencode/tracer.go b/vendor/github.com/zeebo/bencode/tracer.go new file mode 100644 index 00000000..af8af2c0 --- /dev/null +++ b/vendor/github.com/zeebo/bencode/tracer.go @@ -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 +}