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 à :
Parent
be07e15633
révision
11a6fce027
22 fichiers modifiés avec 1551 ajouts et 41 suppressions
|
@ -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
106
router/upload.go
Fichier normal
|
@ -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
|
||||
}
|
|
@ -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
20
util/magnet.go
Fichier normal
|
@ -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
5
util/metainfo/doc.go
Fichier normal
|
@ -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
8
util/metainfo/errors.go
Fichier normal
|
@ -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
143
util/metainfo/metainfo.go
Fichier normal
|
@ -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
27
util/metainfo/metainfo_test.go
Fichier normal
|
@ -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
17
util/whitespace.go
Fichier normal
|
@ -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
6
vendor/github.com/zeebo/bencode/.gitignore
générée
externe
Fichier normal
|
@ -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
7
vendor/github.com/zeebo/bencode/AUTHORS
générée
externe
Fichier normal
|
@ -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
19
vendor/github.com/zeebo/bencode/LICENSE
générée
externe
Fichier normal
|
@ -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
9
vendor/github.com/zeebo/bencode/README.md
générée
externe
Fichier normal
|
@ -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
442
vendor/github.com/zeebo/bencode/decode.go
générée
externe
Fichier normal
|
@ -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
182
vendor/github.com/zeebo/bencode/decode_test.go
générée
externe
Fichier normal
|
@ -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
7
vendor/github.com/zeebo/bencode/doc.go
générée
externe
Fichier normal
|
@ -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
218
vendor/github.com/zeebo/bencode/encode.go
générée
externe
Fichier normal
|
@ -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
142
vendor/github.com/zeebo/bencode/encode_test.go
générée
externe
Fichier normal
|
@ -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
67
vendor/github.com/zeebo/bencode/example_test.go
générée
externe
Fichier normal
|
@ -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
5
vendor/github.com/zeebo/bencode/raw.go
générée
externe
Fichier normal
|
@ -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
76
vendor/github.com/zeebo/bencode/tag.go
générée
externe
Fichier normal
|
@ -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
23
vendor/github.com/zeebo/bencode/tracer.go
générée
externe
Fichier normal
|
@ -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
|
||||
}
|
Référencer dans un nouveau ticket