mirror of
https://github.com/sot-tech/mochi.git
synced 2026-06-01 18:53:36 -07:00
WIP Add support for custom torrents' approval storages
* migrate torrentapproval to list storage * add initial support for torrent file storage (watch directory with fsnotify) * replace frontend/http/bencode package with github.com/zeebo/bencode module * sanitize code (fix warnings) TODO: * parse torrent files to get hashes, * watch directory event types DON'T use for now
This commit is contained in:
@@ -1,41 +0,0 @@
|
||||
// Package bencode implements bencoding of data as defined in BEP 3 using
|
||||
// type assertion over reflection for performance.
|
||||
package bencode
|
||||
|
||||
import "bytes"
|
||||
|
||||
// Enforce that Dict implements the Marshaler interface.
|
||||
var _ Marshaler = Dict{}
|
||||
|
||||
// Dict represents a bencode dictionary.
|
||||
type Dict map[string]interface{}
|
||||
|
||||
// NewDict allocates the memory for a Dict.
|
||||
func NewDict() Dict {
|
||||
return make(Dict)
|
||||
}
|
||||
|
||||
// MarshalBencode implements the Marshaler interface for Dict.
|
||||
func (d Dict) MarshalBencode() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
err := marshalMap(&buf, map[string]interface{}(d))
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// Enforce that List implements the Marshaler interface.
|
||||
var _ Marshaler = List{}
|
||||
|
||||
// List represents a bencode list.
|
||||
type List []interface{}
|
||||
|
||||
// MarshalBencode implements the Marshaler interface for List.
|
||||
func (l List) MarshalBencode() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
err := marshalList(&buf, []interface{}(l))
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// NewList allocates the memory for a List.
|
||||
func NewList() List {
|
||||
return make(List, 0)
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package bencode
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// A Decoder reads bencoded objects from an input stream.
|
||||
type Decoder struct {
|
||||
r *bufio.Reader
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that reads from r.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
return &Decoder{r: bufio.NewReader(r)}
|
||||
}
|
||||
|
||||
// Decode unmarshals the next bencoded value in the stream.
|
||||
func (dec *Decoder) Decode() (interface{}, error) {
|
||||
return unmarshal(dec.r)
|
||||
}
|
||||
|
||||
// Unmarshal deserializes and returns the bencoded value in buf.
|
||||
func Unmarshal(buf []byte) (interface{}, error) {
|
||||
r := bufio.NewReader(bytes.NewBuffer(buf))
|
||||
return unmarshal(r)
|
||||
}
|
||||
|
||||
// unmarshal reads bencoded values from a bufio.Reader
|
||||
func unmarshal(r *bufio.Reader) (interface{}, error) {
|
||||
tok, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch tok {
|
||||
case 'i':
|
||||
return readTerminatedInt(r, 'e')
|
||||
|
||||
case 'l':
|
||||
return readList(r)
|
||||
|
||||
case 'd':
|
||||
return readDict(r)
|
||||
|
||||
default:
|
||||
err = r.UnreadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
length, err := readTerminatedInt(r, ':')
|
||||
if err != nil {
|
||||
return nil, errors.New("bencode: unknown input sequence")
|
||||
}
|
||||
|
||||
buf := make([]byte, length)
|
||||
n, err := r.Read(buf)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if int64(n) != length {
|
||||
return nil, errors.New("bencode: short read")
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
}
|
||||
|
||||
func readTerminator(r io.ByteScanner, term byte) (bool, error) {
|
||||
tok, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return false, err
|
||||
} else if tok == term {
|
||||
return true, nil
|
||||
}
|
||||
return false, r.UnreadByte()
|
||||
}
|
||||
|
||||
func readTerminatedInt(r *bufio.Reader, term byte) (int64, error) {
|
||||
buf, err := r.ReadSlice(term)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if len(buf) <= 1 {
|
||||
return 0, errors.New("bencode: empty integer field")
|
||||
}
|
||||
|
||||
return strconv.ParseInt(string(buf[:len(buf)-1]), 10, 64)
|
||||
}
|
||||
|
||||
func readList(r *bufio.Reader) (List, error) {
|
||||
list := NewList()
|
||||
for {
|
||||
ok, err := readTerminator(r, 'e')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if ok {
|
||||
break
|
||||
}
|
||||
|
||||
v, err := unmarshal(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list = append(list, v)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func readDict(r *bufio.Reader) (Dict, error) {
|
||||
dict := NewDict()
|
||||
for {
|
||||
ok, err := readTerminator(r, 'e')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if ok {
|
||||
break
|
||||
}
|
||||
|
||||
v, err := unmarshal(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, ok := v.(string)
|
||||
if !ok {
|
||||
return nil, errors.New("bencode: non-string map key")
|
||||
}
|
||||
|
||||
dict[key], err = unmarshal(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return dict, nil
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package bencode
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var unmarshalTests = []struct {
|
||||
input string
|
||||
expected interface{}
|
||||
}{
|
||||
{"i42e", int64(42)},
|
||||
{"i-42e", int64(-42)},
|
||||
|
||||
{"7:example", "example"},
|
||||
|
||||
{"l3:one3:twoe", List{"one", "two"}},
|
||||
{"le", List{}},
|
||||
|
||||
{"d3:one2:aa3:two2:bbe", Dict{"one": "aa", "two": "bb"}},
|
||||
{"de", Dict{}},
|
||||
}
|
||||
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
for _, tt := range unmarshalTests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
got, err := Unmarshal([]byte(tt.input))
|
||||
require.Nil(t, err, "unmarshal should not fail")
|
||||
require.Equal(t, got, tt.expected, "unmarshalled values should match the expected results")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type bufferLoop struct {
|
||||
val string
|
||||
}
|
||||
|
||||
func (r *bufferLoop) Read(b []byte) (int, error) {
|
||||
n := copy(b, r.val)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalScalar(b *testing.B) {
|
||||
d1 := NewDecoder(&bufferLoop{"7:example"})
|
||||
d2 := NewDecoder(&bufferLoop{"i42e"})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
d1.Decode()
|
||||
d2.Decode()
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalLarge(t *testing.T) {
|
||||
data := Dict{
|
||||
"k1": List{"a", "b", "c"},
|
||||
"k2": int64(42),
|
||||
"k3": "val",
|
||||
"k4": int64(-42),
|
||||
}
|
||||
|
||||
buf, _ := Marshal(data)
|
||||
dec := NewDecoder(&bufferLoop{string(buf)})
|
||||
|
||||
got, err := dec.Decode()
|
||||
require.Nil(t, err, "decode should not fail")
|
||||
require.Equal(t, got, data, "encoding and decoding should equal the original value")
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalLarge(b *testing.B) {
|
||||
data := map[string]interface{}{
|
||||
"k1": []string{"a", "b", "c"},
|
||||
"k2": 42,
|
||||
"k3": "val",
|
||||
"k4": uint(42),
|
||||
}
|
||||
|
||||
buf, _ := Marshal(data)
|
||||
dec := NewDecoder(&bufferLoop{string(buf)})
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
dec.Decode()
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
package bencode
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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: w}
|
||||
}
|
||||
|
||||
// Encode writes the bencoding of v to the stream.
|
||||
func (enc *Encoder) Encode(v interface{}) error {
|
||||
return marshal(enc.w, v)
|
||||
}
|
||||
|
||||
// Marshal returns the bencoding of v.
|
||||
func Marshal(v interface{}) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
err := marshal(&buf, v)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// Marshaler is the interface implemented by objects that can marshal
|
||||
// themselves.
|
||||
type Marshaler interface {
|
||||
MarshalBencode() ([]byte, error)
|
||||
}
|
||||
|
||||
// marshal writes types bencoded to an io.Writer.
|
||||
func marshal(w io.Writer, data interface{}) (err error) {
|
||||
switch v := data.(type) {
|
||||
case Marshaler:
|
||||
var bencoded []byte
|
||||
bencoded, err = v.MarshalBencode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(bencoded)
|
||||
|
||||
case []byte:
|
||||
err = marshalBytes(w, v)
|
||||
|
||||
case string:
|
||||
err = marshalString(w, v)
|
||||
|
||||
case []string:
|
||||
err = marshalStringSlice(w, v)
|
||||
|
||||
case int:
|
||||
err = marshalInt(w, int64(v))
|
||||
|
||||
case int16:
|
||||
err = marshalInt(w, int64(v))
|
||||
|
||||
case int32:
|
||||
err = marshalInt(w, int64(v))
|
||||
|
||||
case int64:
|
||||
err = marshalInt(w, int64(v))
|
||||
|
||||
case uint:
|
||||
err = marshalUint(w, uint64(v))
|
||||
|
||||
case uint16:
|
||||
err = marshalUint(w, uint64(v))
|
||||
|
||||
case uint32:
|
||||
err = marshalUint(w, uint64(v))
|
||||
|
||||
case uint64:
|
||||
err = marshalUint(w, uint64(v))
|
||||
|
||||
case time.Duration: // Assume seconds
|
||||
err = marshalInt(w, int64(v/time.Second))
|
||||
|
||||
case map[string]interface{}:
|
||||
err = marshalMap(w, v)
|
||||
|
||||
case []interface{}:
|
||||
err = marshalList(w, v)
|
||||
|
||||
case []Dict:
|
||||
var interfaceSlice = make([]interface{}, len(v))
|
||||
for i, d := range v {
|
||||
interfaceSlice[i] = d
|
||||
}
|
||||
err = marshalList(w, interfaceSlice)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("attempted to marshal unsupported type:\n%T", v)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func marshalInt(w io.Writer, v int64) error {
|
||||
if _, err := w.Write([]byte{'i'}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write([]byte(strconv.FormatInt(v, 10))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := w.Write([]byte{'e'})
|
||||
return err
|
||||
}
|
||||
|
||||
func marshalUint(w io.Writer, v uint64) error {
|
||||
if _, err := w.Write([]byte{'i'}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write([]byte(strconv.FormatUint(v, 10))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := w.Write([]byte{'e'})
|
||||
return err
|
||||
}
|
||||
|
||||
func marshalBytes(w io.Writer, v []byte) error {
|
||||
if _, err := w.Write([]byte(strconv.Itoa(len(v)))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write([]byte{':'}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := w.Write(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func marshalString(w io.Writer, v string) error {
|
||||
return marshalBytes(w, []byte(v))
|
||||
}
|
||||
|
||||
func marshalStringSlice(w io.Writer, v []string) error {
|
||||
if _, err := w.Write([]byte{'l'}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, val := range v {
|
||||
if err := marshal(w, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err := w.Write([]byte{'e'})
|
||||
return err
|
||||
}
|
||||
|
||||
func marshalList(w io.Writer, v []interface{}) error {
|
||||
if _, err := w.Write([]byte{'l'}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, val := range v {
|
||||
if err := marshal(w, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err := w.Write([]byte{'e'})
|
||||
return err
|
||||
}
|
||||
|
||||
func marshalMap(w io.Writer, v map[string]interface{}) error {
|
||||
if _, err := w.Write([]byte{'d'}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for key, val := range v {
|
||||
if err := marshalString(w, key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := marshal(w, val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err := w.Write([]byte{'e'})
|
||||
return err
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package bencode
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var marshalTests = []struct {
|
||||
input interface{}
|
||||
expected []string
|
||||
}{
|
||||
{int(42), []string{"i42e"}},
|
||||
{int(-42), []string{"i-42e"}},
|
||||
{uint(43), []string{"i43e"}},
|
||||
{int64(44), []string{"i44e"}},
|
||||
{uint64(45), []string{"i45e"}},
|
||||
{int16(44), []string{"i44e"}},
|
||||
{uint16(45), []string{"i45e"}},
|
||||
|
||||
{"example", []string{"7:example"}},
|
||||
{[]byte("example"), []string{"7:example"}},
|
||||
{30 * time.Minute, []string{"i1800e"}},
|
||||
|
||||
{[]string{"one", "two"}, []string{"l3:one3:twoe", "l3:two3:onee"}},
|
||||
{[]interface{}{"one", "two"}, []string{"l3:one3:twoe", "l3:two3:onee"}},
|
||||
{[]string{}, []string{"le"}},
|
||||
|
||||
{map[string]interface{}{"one": "aa", "two": "bb"}, []string{"d3:one2:aa3:two2:bbe", "d3:two2:bb3:one2:aae"}},
|
||||
{map[string]interface{}{}, []string{"de"}},
|
||||
|
||||
{[]Dict{{"a": "b"}, {"c": "d"}}, []string{"ld1:a1:bed1:c1:dee", "ld1:c1:ded1:a1:bee"}},
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
for _, tt := range marshalTests {
|
||||
t.Run(fmt.Sprintf("%#v", tt.input), func(t *testing.T) {
|
||||
got, err := Marshal(tt.input)
|
||||
require.Nil(t, err, "marshal should not fail")
|
||||
require.Contains(t, tt.expected, string(got), "the marshaled result should be one of the expected permutations")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalScalar(b *testing.B) {
|
||||
buf := &bytes.Buffer{}
|
||||
encoder := NewEncoder(buf)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
encoder.Encode("test")
|
||||
encoder.Encode(123)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalLarge(b *testing.B) {
|
||||
data := map[string]interface{}{
|
||||
"k1": []string{"a", "b", "c"},
|
||||
"k2": 42,
|
||||
"k3": "val",
|
||||
"k4": uint(42),
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
encoder := NewEncoder(buf)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
encoder.Encode(data)
|
||||
}
|
||||
}
|
||||
+11
-9
@@ -1,13 +1,15 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"github.com/zeebo/bencode"
|
||||
"net/http"
|
||||
|
||||
"github.com/chihaya/chihaya/bittorrent"
|
||||
"github.com/chihaya/chihaya/frontend/http/bencode"
|
||||
"github.com/chihaya/chihaya/pkg/log"
|
||||
)
|
||||
|
||||
type strMap map[string]interface{}
|
||||
|
||||
// WriteError communicates an error to a BitTorrent client over HTTP.
|
||||
func WriteError(w http.ResponseWriter, err error) error {
|
||||
message := "internal server error"
|
||||
@@ -18,7 +20,7 @@ func WriteError(w http.ResponseWriter, err error) error {
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return bencode.NewEncoder(w).Encode(bencode.Dict{
|
||||
return bencode.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"failure reason": message,
|
||||
})
|
||||
}
|
||||
@@ -26,7 +28,7 @@ func WriteError(w http.ResponseWriter, err error) error {
|
||||
// WriteAnnounceResponse communicates the results of an Announce to a
|
||||
// BitTorrent client over HTTP.
|
||||
func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceResponse) error {
|
||||
bdict := bencode.Dict{
|
||||
bdict := strMap{
|
||||
"complete": resp.Complete,
|
||||
"incomplete": resp.Incomplete,
|
||||
"interval": resp.Interval,
|
||||
@@ -57,7 +59,7 @@ func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceRespo
|
||||
}
|
||||
|
||||
// Add the peers to the dictionary.
|
||||
var peers []bencode.Dict
|
||||
var peers []strMap
|
||||
for _, peer := range resp.IPv4Peers {
|
||||
peers = append(peers, dict(peer))
|
||||
}
|
||||
@@ -72,15 +74,15 @@ func WriteAnnounceResponse(w http.ResponseWriter, resp *bittorrent.AnnounceRespo
|
||||
// WriteScrapeResponse communicates the results of a Scrape to a BitTorrent
|
||||
// client over HTTP.
|
||||
func WriteScrapeResponse(w http.ResponseWriter, resp *bittorrent.ScrapeResponse) error {
|
||||
filesDict := bencode.NewDict()
|
||||
filesDict := make(strMap)
|
||||
for _, scrape := range resp.Files {
|
||||
filesDict[string(scrape.InfoHash[:])] = bencode.Dict{
|
||||
filesDict[string(scrape.InfoHash[:])] = strMap{
|
||||
"complete": scrape.Complete,
|
||||
"incomplete": scrape.Incomplete,
|
||||
}
|
||||
}
|
||||
|
||||
return bencode.NewEncoder(w).Encode(bencode.Dict{
|
||||
return bencode.NewEncoder(w).Encode(strMap{
|
||||
"files": filesDict,
|
||||
})
|
||||
}
|
||||
@@ -107,8 +109,8 @@ func compact6(peer bittorrent.Peer) (buf []byte) {
|
||||
return
|
||||
}
|
||||
|
||||
func dict(peer bittorrent.Peer) bencode.Dict {
|
||||
return bencode.Dict{
|
||||
func dict(peer bittorrent.Peer) strMap {
|
||||
return strMap{
|
||||
"peer id": string(peer.ID[:]),
|
||||
"ip": peer.IP.String(),
|
||||
"port": peer.Port,
|
||||
|
||||
Reference in New Issue
Block a user