mirror of
https://github.com/sot-tech/mochi.git
synced 2026-07-13 20:18:09 -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:
@@ -6,8 +6,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/chihaya/chihaya/bittorrent"
|
||||
"github.com/chihaya/chihaya/middleware"
|
||||
|
||||
@@ -2,7 +2,6 @@ package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/chihaya/chihaya/bittorrent"
|
||||
"github.com/chihaya/chihaya/storage"
|
||||
)
|
||||
|
||||
@@ -119,15 +119,13 @@ func (h *hook) updateKeys() error {
|
||||
log.Error("failed to fetch JWK Set", log.Err(err))
|
||||
return err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
var parsedJWKs gojwk.Key
|
||||
err = json.NewDecoder(resp.Body).Decode(&parsedJWKs)
|
||||
if err != nil {
|
||||
resp.Body.Close()
|
||||
log.Error("failed to decode JWK JSON", log.Err(err))
|
||||
return err
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
keys := map[string]crypto.PublicKey{}
|
||||
for _, parsedJWK := range parsedJWKs.Keys {
|
||||
|
||||
@@ -65,14 +65,14 @@ func New(name string, optionBytes []byte) (Hook, error) {
|
||||
return d.NewHook(optionBytes)
|
||||
}
|
||||
|
||||
// HookConfig is the generic configuration format used for all registered Hooks.
|
||||
type HookConfig struct {
|
||||
// Config is the generic configuration format used for all registered Hooks.
|
||||
type Config struct {
|
||||
Name string `yaml:"name"`
|
||||
Options map[string]interface{} `yaml:"options"`
|
||||
}
|
||||
|
||||
// HooksFromHookConfigs is a utility function for initializing Hooks in bulk.
|
||||
func HooksFromHookConfigs(cfgs []HookConfig) (hooks []Hook, err error) {
|
||||
func HooksFromHookConfigs(cfgs []Config) (hooks []Hook, err error) {
|
||||
for _, cfg := range cfgs {
|
||||
// Marshal the options back into bytes.
|
||||
var optionBytes []byte
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
//
|
||||
// Calling DeriveEntropyFromRequest multiple times yields the same values.
|
||||
func DeriveEntropyFromRequest(req *bittorrent.AnnounceRequest) (uint64, uint64) {
|
||||
v0 := binary.BigEndian.Uint64([]byte(req.InfoHash[:8])) + binary.BigEndian.Uint64([]byte(req.InfoHash[8:16]))
|
||||
v1 := binary.BigEndian.Uint64([]byte(req.Peer.ID[:8])) + binary.BigEndian.Uint64([]byte(req.Peer.ID[8:16]))
|
||||
v0 := binary.BigEndian.Uint64(req.InfoHash[:8]) + binary.BigEndian.Uint64(req.InfoHash[8:16])
|
||||
v1 := binary.BigEndian.Uint64(req.Peer.ID[:8]) + binary.BigEndian.Uint64(req.Peer.ID[8:16])
|
||||
return v0, v1
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ package random
|
||||
func GenerateAndAdvance(s0, s1 uint64) (v, newS0, newS1 uint64) {
|
||||
v = s0 + s1
|
||||
newS0 = s1
|
||||
s0 ^= (s0 << 23)
|
||||
s0 ^= s0 << 23
|
||||
newS1 = s0 ^ s1 ^ (s0 >> 18) ^ (s1 >> 5)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/chihaya/chihaya/bittorrent"
|
||||
"github.com/chihaya/chihaya/pkg/stop"
|
||||
"gopkg.in/yaml.v2"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Builder interface {
|
||||
New() (Container, error)
|
||||
}
|
||||
|
||||
var (
|
||||
buildersMU sync.Mutex
|
||||
builders = make(map[string]Builder)
|
||||
|
||||
ErrContainerDoesNotExist = errors.New("torrent hash container with that name does not exist")
|
||||
)
|
||||
|
||||
func Register(n string, c Builder) {
|
||||
if len(n) == 0 {
|
||||
panic("middleware: could not register a Container with an empty name")
|
||||
}
|
||||
if c == nil {
|
||||
panic("middleware: could not register a Container with nil builder")
|
||||
}
|
||||
|
||||
buildersMU.Lock()
|
||||
defer buildersMU.Unlock()
|
||||
builders[n] = c
|
||||
}
|
||||
|
||||
type Container interface {
|
||||
stop.Stopper
|
||||
Contains(bittorrent.InfoHash) bool
|
||||
}
|
||||
|
||||
func GetContainer(name string, confBytes []byte) (Container, error) {
|
||||
buildersMU.Lock()
|
||||
defer buildersMU.Unlock()
|
||||
var err error
|
||||
var cn Container
|
||||
if builder, exist := builders[name]; !exist {
|
||||
err = ErrContainerDoesNotExist
|
||||
} else {
|
||||
if err = yaml.Unmarshal(confBytes, &cn); err == nil {
|
||||
cn, err = builder.New()
|
||||
}
|
||||
}
|
||||
return cn, err
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package directory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/chihaya/chihaya/bittorrent"
|
||||
"github.com/chihaya/chihaya/middleware/torrentapproval/container"
|
||||
"github.com/chihaya/chihaya/middleware/torrentapproval/container/list"
|
||||
"github.com/chihaya/chihaya/pkg/log"
|
||||
"github.com/chihaya/chihaya/pkg/stop"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func init() {
|
||||
container.Register("list", builder{})
|
||||
}
|
||||
|
||||
type builder struct {
|
||||
WhitelistPath string `yaml:"whitelist_path"`
|
||||
BlacklistPath string `yaml:"blacklist_path"`
|
||||
}
|
||||
|
||||
func (b builder) New() (container.Container, error) {
|
||||
if len(b.WhitelistPath) > 0 && len(b.BlacklistPath) > 0 {
|
||||
return nil, fmt.Errorf("using both whitelist and blacklist is invalid")
|
||||
}
|
||||
var err error
|
||||
dirLister := &directory{
|
||||
List: list.List{
|
||||
Hashes: sync.Map{},
|
||||
Invert: len(b.WhitelistPath) == 0,
|
||||
},
|
||||
files: sync.Map{},
|
||||
root: b.WhitelistPath,
|
||||
watcher: nil,
|
||||
}
|
||||
if dirLister.Invert {
|
||||
dirLister.root = b.BlacklistPath
|
||||
}
|
||||
var w *fsnotify.Watcher
|
||||
if w, err = fsnotify.NewWatcher(); err != nil {
|
||||
return nil, fmt.Errorf("unable to initialize fsnotify mechanism")
|
||||
}
|
||||
if dirContent, err := os.ReadDir(dirLister.root); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
for _, f := range dirContent {
|
||||
if !f.IsDir() {
|
||||
if err = dirLister.processFile(f.Name(), false); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err = w.Add(dirLister.root); err != nil {
|
||||
_ = w.Close()
|
||||
dirLister = nil
|
||||
}
|
||||
return dirLister, err
|
||||
}
|
||||
|
||||
func (d *directory) watch() {
|
||||
go func() {
|
||||
for err := range d.watcher.Errors {
|
||||
log.Error(err)
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
for event := range d.watcher.Events {
|
||||
log.Debug(event.String())
|
||||
//todo: implement event type parsing
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (d *directory) processFile(name string, delete bool) error {
|
||||
fullName := filepath.Join(d.root, name)
|
||||
if delete {
|
||||
if hash, found := d.files.Load(fullName); found{
|
||||
d.Hashes.Delete(hash)
|
||||
}
|
||||
} else {
|
||||
var hashBytes []byte
|
||||
info := bittorrent.InfoHashFromBytes(hashBytes)
|
||||
d.files.Store(fullName, info)
|
||||
d.Hashes.Store(info, list.DUMMY)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type directory struct {
|
||||
list.List
|
||||
files sync.Map
|
||||
root string
|
||||
watcher *fsnotify.Watcher
|
||||
}
|
||||
|
||||
func (d *directory) Stop() stop.Result {
|
||||
ch := make(stop.Channel)
|
||||
go ch.Done(d.watcher.Close())
|
||||
return ch.Result()
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/chihaya/chihaya/bittorrent"
|
||||
"github.com/chihaya/chihaya/middleware/torrentapproval/container"
|
||||
"github.com/chihaya/chihaya/pkg/stop"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func init() {
|
||||
container.Register("list", Builder{})
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
Whitelist []string `yaml:"whitelist"`
|
||||
Blacklist []string `yaml:"blacklist"`
|
||||
}
|
||||
|
||||
var DUMMY struct{}
|
||||
|
||||
func (b Builder) New() (container.Container, error) {
|
||||
if len(b.Whitelist) > 0 && len(b.Blacklist) > 0 {
|
||||
return nil, fmt.Errorf("using both whitelist and blacklist is invalid")
|
||||
}
|
||||
l := &List{
|
||||
Hashes: sync.Map{},
|
||||
Invert: len(b.Whitelist) == 0,
|
||||
}
|
||||
|
||||
hashList := b.Whitelist
|
||||
if l.Invert {
|
||||
l.Invert = true
|
||||
hashList = b.Blacklist
|
||||
}
|
||||
|
||||
for _, hashString := range hashList {
|
||||
hashinfo, err := hex.DecodeString(hashString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("whitelist : invalid hash %s", hashString)
|
||||
}
|
||||
if len(hashinfo) != 20 {
|
||||
return nil, fmt.Errorf("whitelist : hash %s is not 20 byes", hashString)
|
||||
}
|
||||
l.Hashes.Store(bittorrent.InfoHashFromBytes(hashinfo), DUMMY)
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
type List struct {
|
||||
Invert bool
|
||||
Hashes sync.Map
|
||||
}
|
||||
|
||||
func (l *List) Stop() stop.Result {
|
||||
return stop.AlreadyStopped
|
||||
}
|
||||
|
||||
func (l *List) Contains(hash bittorrent.InfoHash) bool {
|
||||
_, result := l.Hashes.Load(hash)
|
||||
return result != l.Invert
|
||||
}
|
||||
@@ -4,10 +4,10 @@ package torrentapproval
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"github.com/chihaya/chihaya/middleware/torrentapproval/container"
|
||||
"github.com/chihaya/chihaya/pkg/stop"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/chihaya/chihaya/bittorrent"
|
||||
"github.com/chihaya/chihaya/middleware"
|
||||
@@ -20,90 +20,58 @@ func init() {
|
||||
middleware.RegisterDriver(Name, driver{})
|
||||
}
|
||||
|
||||
var _ middleware.Driver = driver{}
|
||||
|
||||
type driver struct{}
|
||||
|
||||
func (d driver) NewHook(optionBytes []byte) (middleware.Hook, error) {
|
||||
var cfg Config
|
||||
var cfg middleware.Config
|
||||
err := yaml.Unmarshal(optionBytes, &cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid options for middleware %s: %s", Name, err)
|
||||
}
|
||||
|
||||
return NewHook(cfg)
|
||||
if len(cfg.Name) == 0 {
|
||||
return nil, fmt.Errorf("invalid options for middleware %s: name not provided", Name)
|
||||
}
|
||||
|
||||
if cfg.Options == nil {
|
||||
return nil, fmt.Errorf("invalid options for middleware %s: options not provided", Name)
|
||||
}
|
||||
|
||||
var confBytes []byte
|
||||
if confBytes, err = yaml.Marshal(cfg.Options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c, err := container.GetContainer(cfg.Name, confBytes); err == nil{
|
||||
return &hook{c}, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// ErrTorrentUnapproved is the error returned when a torrent hash is invalid.
|
||||
var ErrTorrentUnapproved = bittorrent.ClientError("unapproved torrent")
|
||||
|
||||
// Config represents all the values required by this middleware to validate
|
||||
// torrents based on their hash value.
|
||||
type Config struct {
|
||||
Whitelist []string `yaml:"whitelist"`
|
||||
Blacklist []string `yaml:"blacklist"`
|
||||
}
|
||||
|
||||
type hook struct {
|
||||
approved map[bittorrent.InfoHash]struct{}
|
||||
unapproved map[bittorrent.InfoHash]struct{}
|
||||
hashContainer container.Container
|
||||
}
|
||||
|
||||
// NewHook returns an instance of the torrent approval middleware.
|
||||
func NewHook(cfg Config) (middleware.Hook, error) {
|
||||
h := &hook{
|
||||
approved: make(map[bittorrent.InfoHash]struct{}),
|
||||
unapproved: make(map[bittorrent.InfoHash]struct{}),
|
||||
}
|
||||
|
||||
if len(cfg.Whitelist) > 0 && len(cfg.Blacklist) > 0 {
|
||||
return nil, fmt.Errorf("using both whitelist and blacklist is invalid")
|
||||
}
|
||||
|
||||
for _, hashString := range cfg.Whitelist {
|
||||
hashinfo, err := hex.DecodeString(hashString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("whitelist : invalid hash %s", hashString)
|
||||
}
|
||||
if len(hashinfo) != 20 {
|
||||
return nil, fmt.Errorf("whitelist : hash %s is not 20 byes", hashString)
|
||||
}
|
||||
h.approved[bittorrent.InfoHashFromBytes(hashinfo)] = struct{}{}
|
||||
}
|
||||
|
||||
for _, hashString := range cfg.Blacklist {
|
||||
hashinfo, err := hex.DecodeString(hashString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("blacklist : invalid hash %s", hashString)
|
||||
}
|
||||
if len(hashinfo) != 20 {
|
||||
return nil, fmt.Errorf("blacklist : hash %s is not 20 byes", hashString)
|
||||
}
|
||||
h.unapproved[bittorrent.InfoHashFromBytes(hashinfo)] = struct{}{}
|
||||
}
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h *hook) HandleAnnounce(ctx context.Context, req *bittorrent.AnnounceRequest, resp *bittorrent.AnnounceResponse) (context.Context, error) {
|
||||
infohash := req.InfoHash
|
||||
var err error
|
||||
|
||||
if len(h.approved) > 0 {
|
||||
if _, found := h.approved[infohash]; !found {
|
||||
return ctx, ErrTorrentUnapproved
|
||||
}
|
||||
if !h.hashContainer.Contains(req.InfoHash){
|
||||
err = ErrTorrentUnapproved
|
||||
}
|
||||
|
||||
if len(h.unapproved) > 0 {
|
||||
if _, found := h.unapproved[infohash]; found {
|
||||
return ctx, ErrTorrentUnapproved
|
||||
}
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
func (h *hook) HandleScrape(ctx context.Context, req *bittorrent.ScrapeRequest, resp *bittorrent.ScrapeResponse) (context.Context, error) {
|
||||
// Scrapes don't require any protection.
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (h *hook) Stop() stop.Result {
|
||||
return h.hashContainer.Stop()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user