Files
mochi/middleware/torrentapproval/torrentapproval.go
2024-06-27 20:27:20 +03:00

112 lines
3.0 KiB
Go

// Package torrentapproval implements a Hook that fails an Announce based on a
// whitelist or blacklist of torrent hash.
package torrentapproval
import (
"context"
"errors"
"fmt"
"io"
"github.com/sot-tech/mochi/bittorrent"
"github.com/sot-tech/mochi/middleware"
"github.com/sot-tech/mochi/middleware/torrentapproval/container"
"github.com/sot-tech/mochi/pkg/conf"
"github.com/sot-tech/mochi/storage"
// import directory watcher to enable appropriate support
_ "github.com/sot-tech/mochi/middleware/torrentapproval/container/directory"
// import static list to enable appropriate support
_ "github.com/sot-tech/mochi/middleware/torrentapproval/container/list"
)
// Name is the name by which this middleware is registered with Conf.
const Name = "torrent approval"
const internalStore = "internal"
func init() {
middleware.RegisterBuilder(Name, build)
}
type baseConfig struct {
// Source - name of container for initial values
Source string `cfg:"initial_source"`
// Deprecated: use Storage parameter
Preserve bool
// Storage where to hold provided data by Source
Storage conf.NamedMapConfig
// Configuration depends on used container
Configuration conf.MapConfig
}
func build(config conf.MapConfig, st storage.PeerStorage) (h middleware.Hook, err error) {
var cfg baseConfig
if err = config.Unmarshal(&cfg); err != nil {
return nil, fmt.Errorf("middleware %s: %w", Name, err)
}
if len(cfg.Source) == 0 {
return nil, fmt.Errorf("invalid config for middleware %s: source not provided", Name)
}
if cfg.Configuration == nil {
return nil, fmt.Errorf("invalid config for middleware %s: config not provided", Name)
}
if cfg.Preserve {
return nil, errors.New("preserve option is deprecated, use store parameter")
}
var ds, dsc storage.DataStorage
if len(cfg.Storage.Name) == 0 || cfg.Storage.Name == internalStore {
ds = st
} else if ds, err = storage.NewDataStorage(cfg.Storage); err == nil {
dsc = ds
} else {
return
}
var c container.Container
if c, err = container.GetContainer(cfg.Source, cfg.Configuration, ds); err == nil {
h = &hook{c, dsc}
}
return h, err
}
// ErrTorrentUnapproved is the error returned when a torrent hash is invalid.
var ErrTorrentUnapproved = bittorrent.ClientError("torrent not allowed by mochi")
type hook struct {
hashContainer container.Container
providedStorage storage.DataStorage
}
func (h *hook) HandleAnnounce(ctx context.Context, req *bittorrent.AnnounceRequest, _ *bittorrent.AnnounceResponse) (context.Context, error) {
var err error
if !h.hashContainer.Approved(ctx, req.InfoHash) {
err = ErrTorrentUnapproved
}
return ctx, err
}
func (h *hook) HandleScrape(ctx context.Context, _ *bittorrent.ScrapeRequest, _ *bittorrent.ScrapeResponse) (context.Context, error) {
// Scrapes don't require any protection.
return ctx, nil
}
func (h *hook) Close() (err error) {
if cl, isOk := h.hashContainer.(io.Closer); isOk {
err = cl.Close()
}
if h.providedStorage != nil {
if stErr := h.providedStorage.Close(); stErr != nil {
err = errors.Join(err, stErr)
}
}
return err
}