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
1d7e150572
révision
c088cb2642
|
@ -63,16 +63,8 @@ type SearchForm struct {
|
||||||
HideAdvancedSearch bool
|
HideAdvancedSearch bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type UploadForm struct {
|
|
||||||
Name string
|
|
||||||
Magnet string
|
|
||||||
Category string
|
|
||||||
Description string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some Default Values to ease things out
|
// Some Default Values to ease things out
|
||||||
func NewSearchForm(params ...string) SearchForm {
|
func NewSearchForm(params ...string) (searchForm SearchForm) {
|
||||||
searchForm := SearchForm{}
|
|
||||||
if len(params) > 1 {
|
if len(params) > 1 {
|
||||||
searchForm.Category = params[0]
|
searchForm.Category = params[0]
|
||||||
} else {
|
} else {
|
||||||
|
@ -84,23 +76,16 @@ func NewSearchForm(params ...string) SearchForm {
|
||||||
searchForm.Sort = "torrent_id"
|
searchForm.Sort = "torrent_id"
|
||||||
}
|
}
|
||||||
if len(params) > 3 {
|
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 {
|
} else {
|
||||||
searchForm.Order = "DESC"
|
searchForm.Order = "DESC"
|
||||||
}
|
}
|
||||||
return searchForm
|
return
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
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) {
|
func UploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var templates = template.Must(template.New("upload").Funcs(FuncMap).ParseFiles("templates/index.html", "templates/upload.html"))
|
var templates = template.Must(template.New("upload").Funcs(FuncMap).ParseFiles("templates/index.html", "templates/upload.html"))
|
||||||
templates.ParseGlob("templates/_*.html") // common
|
templates.ParseGlob("templates/_*.html") // common
|
||||||
|
var err error
|
||||||
var uploadForm UploadForm
|
var uploadForm UploadForm
|
||||||
if r.Method == "POST" {
|
if r.Method == "POST" {
|
||||||
err := r.ParseForm()
|
defer r.Body.Close()
|
||||||
if err != nil {
|
err = uploadForm.ExtractInfo(r)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
if err == nil {
|
||||||
|
//validate name + hash
|
||||||
|
//add to db and redirect depending on result
|
||||||
}
|
}
|
||||||
uploadForm = UploadForm{
|
} else if r.Method == "GET" {
|
||||||
r.Form.Get("name"),
|
htv := UploadTemplateVariables{uploadForm, NewSearchForm(), Navigation{}, r.URL, mux.CurrentRoute(r)}
|
||||||
r.Form.Get("magnet"),
|
err = templates.ExecuteTemplate(w, "index.html", htv)
|
||||||
r.Form.Get("c"),
|
} else {
|
||||||
r.Form.Get("desc"),
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
}
|
return
|
||||||
//validate name + hash
|
|
||||||
//add to db and redirect depending on result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
htv := UploadTemplateVariables{uploadForm, NewSearchForm(), Navigation{}, r.URL, mux.CurrentRoute(r)}
|
|
||||||
|
|
||||||
err := templates.ExecuteTemplate(w, "index.html", htv)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
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