From 09a160a7292d997d97b8a8478633f36cfcaac42b Mon Sep 17 00:00:00 2001 From: Ludvig Lundgren Date: Mon, 19 Aug 2024 19:55:04 +0200 Subject: [PATCH] feat: validate hashes (#98) * feat: validate hashes * chore: update .gitignore --- .gitignore | 3 ++- cmd/torrent_category.go | 10 ++++++++++ cmd/torrent_list.go | 8 ++++++++ cmd/torrent_pause.go | 10 +++++++++- cmd/torrent_recheck.go | 8 +++++++- cmd/torrent_remove.go | 8 ++++++++ cmd/torrent_resume.go | 8 ++++++++ cmd/torrent_tracker.go | 43 ++++++++++++++++++++++++++--------------- pkg/utils/utils.go | 25 ++++++++++++++++++++++++ pkg/utils/utils_test.go | 28 +++++++++++++++++++++++++++ 10 files changed, 132 insertions(+), 19 deletions(-) create mode 100644 pkg/utils/utils.go create mode 100644 pkg/utils/utils_test.go diff --git a/.gitignore b/.gitignore index 20b2e2a..57ba287 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ .DS_Store .qbt.toml dist -bin \ No newline at end of file +bin +testdata \ No newline at end of file diff --git a/cmd/torrent_category.go b/cmd/torrent_category.go index b5d1331..f536a3e 100644 --- a/cmd/torrent_category.go +++ b/cmd/torrent_category.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/ludviglundgren/qbittorrent-cli/pkg/utils" "log" "os" "strings" @@ -54,6 +55,15 @@ func RunTorrentCategorySet() *cobra.Command { command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Torrent hashes, as comma separated list") command.RunE = func(cmd *cobra.Command, args []string) error { + if len(hashes) == 0 { + log.Println("No hashes supplied!") + } + + err := utils.ValidateHash(hashes) + if err != nil { + log.Fatalf("Invalid hashes supplied: %v", err) + } + config.InitConfig() qbtSettings := qbittorrent.Config{ diff --git a/cmd/torrent_list.go b/cmd/torrent_list.go index 5156c93..1566367 100644 --- a/cmd/torrent_list.go +++ b/cmd/torrent_list.go @@ -3,6 +3,7 @@ package cmd import ( "encoding/json" "fmt" + "github.com/ludviglundgren/qbittorrent-cli/pkg/utils" "log" "os" "strings" @@ -39,6 +40,13 @@ func RunTorrentList() *cobra.Command { command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Filter by hashes. Separated by comma: \"hash1,hash2\".") command.Run = func(cmd *cobra.Command, args []string) { + if len(hashes) > 0 { + err := utils.ValidateHash(hashes) + if err != nil { + log.Fatalf("Invalid hashes supplied: %v", err) + } + } + config.InitConfig() qbtSettings := qbittorrent.Config{ diff --git a/cmd/torrent_pause.go b/cmd/torrent_pause.go index 90709c6..5436b92 100644 --- a/cmd/torrent_pause.go +++ b/cmd/torrent_pause.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/ludviglundgren/qbittorrent-cli/pkg/utils" "log" "os" @@ -30,6 +31,13 @@ func RunTorrentPause() *cobra.Command { command.Flags().BoolVar(&names, "names", false, "Provided arguments will be read as torrent names") command.Run = func(cmd *cobra.Command, args []string) { + if len(hashes) > 0 { + err := utils.ValidateHash(hashes) + if err != nil { + log.Fatalf("Invalid hashes supplied: %v", err) + } + } + config.InitConfig() qbtSettings := qbittorrent.Config{ @@ -54,7 +62,7 @@ func RunTorrentPause() *cobra.Command { } if len(hashes) == 0 { - log.Printf("No torrents found to pause with provided search terms") + log.Printf("No torrents found to pause with provided hashes. Use --all to pause all torrents.") return } diff --git a/cmd/torrent_recheck.go b/cmd/torrent_recheck.go index 82b23c8..801e34a 100644 --- a/cmd/torrent_recheck.go +++ b/cmd/torrent_recheck.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/ludviglundgren/qbittorrent-cli/pkg/utils" "log" "os" @@ -34,6 +35,11 @@ func RunTorrentRecheck() *cobra.Command { return } + err := utils.ValidateHash(hashes) + if err != nil { + log.Fatalf("Invalid hashes supplied: %v", err) + } + config.InitConfig() qbtSettings := qbittorrent.Config{ @@ -53,7 +59,7 @@ func RunTorrentRecheck() *cobra.Command { os.Exit(1) } - err := batchRequests(hashes, func(start, end int) error { + err = batchRequests(hashes, func(start, end int) error { return qb.RecheckCtx(ctx, hashes[start:end]) }) if err != nil { diff --git a/cmd/torrent_remove.go b/cmd/torrent_remove.go index 195c846..350fecd 100644 --- a/cmd/torrent_remove.go +++ b/cmd/torrent_remove.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/ludviglundgren/qbittorrent-cli/pkg/utils" "log" "os" @@ -40,6 +41,13 @@ func RunTorrentRemove() *cobra.Command { command.Flags().StringSliceVar(&excludeTags, "exclude-tags", []string{}, "Exclude torrents with provided tags") command.Run = func(cmd *cobra.Command, args []string) { + if len(hashes) > 0 { + err := utils.ValidateHash(hashes) + if err != nil { + log.Fatalf("Invalid hashes supplied: %v", err) + } + } + config.InitConfig() qbtSettings := qbittorrent.Config{ diff --git a/cmd/torrent_resume.go b/cmd/torrent_resume.go index c6a91f0..9d3e3c4 100644 --- a/cmd/torrent_resume.go +++ b/cmd/torrent_resume.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/ludviglundgren/qbittorrent-cli/pkg/utils" "log" "os" "time" @@ -29,6 +30,13 @@ func RunTorrentResume() *cobra.Command { command.Flags().StringSliceVar(&hashes, "hashes", []string{}, "Add hashes as comma separated list") command.Run = func(cmd *cobra.Command, args []string) { + if len(hashes) > 0 { + err := utils.ValidateHash(hashes) + if err != nil { + log.Fatalf("Invalid hashes supplied: %v", err) + } + } + config.InitConfig() qbtSettings := qbittorrent.Config{ diff --git a/cmd/torrent_tracker.go b/cmd/torrent_tracker.go index 2213303..d2ccaa5 100644 --- a/cmd/torrent_tracker.go +++ b/cmd/torrent_tracker.go @@ -44,6 +44,9 @@ func RunTorrentTrackerEdit() *cobra.Command { command.Flags().StringVar(&oldURL, "old", "", "Old tracker URL to replace") command.Flags().StringVar(&newURL, "new", "", "New tracker URL") + command.MarkFlagRequired("old") + command.MarkFlagRequired("new") + command.RunE = func(cmd *cobra.Command, args []string) error { config.InitConfig() @@ -64,31 +67,39 @@ func RunTorrentTrackerEdit() *cobra.Command { os.Exit(1) } - if dry { - log.Printf("dry-run: successfully updated tracker on torrents\n") + torrents, err := qb.GetTorrentsCtx(ctx, qbittorrent.TorrentFilterOptions{}) + if err != nil { + log.Fatalf("could not get torrents err: %q\n", err) + } - return nil - } else { - torrents, err := qb.GetTorrentsCtx(ctx, qbittorrent.TorrentFilterOptions{}) - if err != nil { - log.Fatalf("could not get torrents err: %q\n", err) + var torrentsToUpdate []qbittorrent.Torrent + + for _, torrent := range torrents { + if strings.Contains(torrent.Tracker, oldURL) { + torrentsToUpdate = append(torrentsToUpdate, torrent) } + } - matches := 0 + if len(torrentsToUpdate) == 0 { + log.Printf("found no torrents with tracker %q\n", oldURL) + return nil + } + + for i, torrent := range torrentsToUpdate { + if dry { + log.Printf("dry-run: [%d/%d] updating tracker for torrent %s %q\n", i+1, len(torrentsToUpdate), torrent.Hash, torrent.Name) - for _, torrent := range torrents { - if strings.Contains(torrent.Tracker, oldURL) { - if err := qb.EditTrackerCtx(ctx, torrent.Hash, torrent.Tracker, newURL); err != nil { - log.Fatalf("could not edit tracker for torrent: %s\n", torrent.Hash) - } + } else { + log.Printf("[%d/%d] updating tracker for torrent %s %q\n", i+1, len(torrentsToUpdate), torrent.Hash, torrent.Name) - matches++ + if err := qb.EditTrackerCtx(ctx, torrent.Hash, torrent.Tracker, newURL); err != nil { + log.Fatalf("could not edit tracker for torrent: %s\n", torrent.Hash) } } - - log.Printf("successfully updated tracker for (%d) torrents\n", matches) } + log.Printf("successfully updated tracker for (%d) torrents\n", len(torrentsToUpdate)) + return nil } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go new file mode 100644 index 0000000..eb14570 --- /dev/null +++ b/pkg/utils/utils.go @@ -0,0 +1,25 @@ +package utils + +import ( + "fmt" + "regexp" + "strings" +) + +var hashRegex = regexp.MustCompile("^[a-fA-F0-9]{40}$") + +func ValidateHash(hashes []string) error { + var invalid []string + + for _, hash := range hashes { + if !hashRegex.MatchString(hash) { + invalid = append(invalid, hash) + } + } + + if len(invalid) > 0 { + return fmt.Errorf("invalid hashes: %s", strings.Join(invalid, ",")) + } + + return nil +} diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go new file mode 100644 index 0000000..331759a --- /dev/null +++ b/pkg/utils/utils_test.go @@ -0,0 +1,28 @@ +package utils + +import "testing" + +func TestValidateHash(t *testing.T) { + type args struct { + hashes []string + } + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + {name: "ok", args: args{hashes: []string{"6957bf5272f5b994132458a557864e3ea747489f"}}, want: true, wantErr: false}, + {name: "invalid", args: args{hashes: []string{"6957bf5272f5b994132458a557864e3ea747489"}}, want: false, wantErr: true}, + {name: "invalid_2", args: args{hashes: []string{"11111"}}, want: false, wantErr: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateHash(tt.args.hashes) + if (err != nil) != tt.wantErr { + t.Errorf("ValidateHash() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +}