diff --git a/internal/config/types.go b/internal/config/types.go index 7a3e923..3a08290 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -11,6 +11,8 @@ type ConfigInterface interface { EnableRepair() bool GetHost() string GetPort() string + GetUsername() string + GetPassword() string GetDirectories() []string MeetsConditions(directory, torrentName string, torrentSize int64, torrentIDs, fileNames []string, fileSizes []int64) bool GetOnLibraryUpdate() string @@ -34,6 +36,8 @@ type ZurgConfig struct { Host string `yaml:"host" json:"host"` Port string `yaml:"port" json:"port"` + Username string `yaml:"username" json:"username"` + Password string `yaml:"password" json:"password"` NumOfWorkers int `yaml:"concurrent_workers" json:"concurrent_workers"` RefreshEverySeconds int `yaml:"check_for_changes_every_secs" json:"check_for_changes_every_secs"` @@ -78,6 +82,14 @@ func (z *ZurgConfig) GetPort() string { return z.Port } +func (z *ZurgConfig) GetUsername() string { + return z.Username +} + +func (z *ZurgConfig) GetPassword() string { + return z.Password +} + func (z *ZurgConfig) GetNumOfWorkers() int { if z.NumOfWorkers == 0 { return 50 diff --git a/internal/handlers/basicauth.go b/internal/handlers/basicauth.go new file mode 100644 index 0000000..a6f0268 --- /dev/null +++ b/internal/handlers/basicauth.go @@ -0,0 +1,17 @@ +package handlers + +import "net/http" + +func (hs *Handlers) basicAuth(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + username, password, ok := r.BasicAuth() + if !ok || username != hs.cfg.GetUsername() || password != hs.cfg.GetPassword() { + w.Header().Set("WWW-Authenticate", `Basic realm="restricted"`) + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(http.StatusText(http.StatusUnauthorized))) + return + } + + next.ServeHTTP(w, r) + }) +} diff --git a/internal/handlers/home.go b/internal/handlers/home.go index 657d4b5..51ffc65 100644 --- a/internal/handlers/home.go +++ b/internal/handlers/home.go @@ -151,7 +151,7 @@ func (zr *Handlers) handleHome(resp http.ResponseWriter, req *http.Request) { Premium - %d seconds + %d days Expiration @@ -267,28 +267,28 @@ func (zr *Handlers) handleHome(resp http.ResponseWriter, req *http.Request) { response.UserInfo.Points, response.UserInfo.Locale, response.UserInfo.Type, - response.UserInfo.Premium, - response.UserInfo.Expiration, + response.UserInfo.Premium/86400, + strings.Replace(response.UserInfo.Expiration, "Z", "+01:00", 1), response.Config.Version, strings.Replace(response.Config.Token, response.Config.Token[len(response.Config.Token)-48:], "*****", 1), - response.Config.Host, - response.Config.Port, - response.Config.NumOfWorkers, - response.Config.RefreshEverySeconds, - response.Config.RetainRDTorrentName, - response.Config.RetainFolderNameExtension, - response.Config.CanRepair, - response.Config.DeleteRarFiles, - response.Config.RealDebridTimeout, - response.Config.UseDownloadCache, - response.Config.RateLimitSleepSeconds, - response.Config.RetriesUntilFailed, + response.Config.GetHost(), + response.Config.GetPort(), + response.Config.GetNumOfWorkers(), + response.Config.GetRefreshEverySeconds(), + response.Config.EnableRetainRDTorrentName(), + response.Config.EnableRetainFolderNameExtension(), + response.Config.EnableRepair(), + response.Config.ShouldDeleteRarFiles(), + response.Config.GetRealDebridTimeout(), + response.Config.EnableDownloadCache(), + response.Config.GetRateLimitSleepSeconds(), + response.Config.GetRetriesUntilFailed(), strings.Join(response.Config.PreferredHosts, ", "), - response.Config.NetworkBufferSize, - response.Config.ServeFromRclone, - response.Config.VerifyDownloadLink, - response.Config.ForceIPv6, - response.Config.OnLibraryUpdate, + response.Config.GetNetworkBufferSize(), + response.Config.ShouldServeFromRclone(), + response.Config.ShouldVerifyDownloadLink(), + response.Config.ShouldForceIPv6(), + response.Config.GetOnLibraryUpdate(), ) fmt.Fprint(resp, out) diff --git a/internal/handlers/options.go b/internal/handlers/options.go new file mode 100644 index 0000000..1f714de --- /dev/null +++ b/internal/handlers/options.go @@ -0,0 +1,13 @@ +package handlers + +import "net/http" + +func (hs *Handlers) options(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return + } + next.ServeHTTP(w, r) + }) +} diff --git a/internal/handlers/router.go b/internal/handlers/router.go index 0d34c5c..799902b 100644 --- a/internal/handlers/router.go +++ b/internal/handlers/router.go @@ -39,7 +39,10 @@ func AttachHandlers(router *chi.Mux, downloader *universal.Downloader, torMgr *t log: log, } - router.Use(optionsMiddleware) + if cfg.GetUsername() != "" { + router.Use(hs.basicAuth) + } + router.Use(hs.options) router.Get("/", hs.handleHome) // version @@ -283,16 +286,6 @@ func (hs *Handlers) moveTorrentHandler(resp http.ResponseWriter, req *http.Reque resp.WriteHeader(http.StatusNoContent) } -func optionsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method == "OPTIONS" { - w.WriteHeader(http.StatusOK) - return - } - next.ServeHTTP(w, r) - }) -} - // universal handlers func (hs *Handlers) handleDownloadFile(resp http.ResponseWriter, req *http.Request) { diff --git a/internal/torrent/repair.go b/internal/torrent/repair.go index 3843b02..f462cda 100644 --- a/internal/torrent/repair.go +++ b/internal/torrent/repair.go @@ -133,11 +133,11 @@ func (t *TorrentManager) repair(torrent *Torrent) { if t.reinsertTorrent(torrent, "") { t.log.Infof("Successfully downloaded torrent %s to repair it", torrent.AccessKey) return - } else if !torrent.Unfixable { - t.log.Warnf("Failed to repair by reinserting torrent %s, will only redownload broken files...", torrent.AccessKey) - } else { + } else if torrent.Unfixable { t.log.Warnf("Cannot repair torrent %s", torrent.AccessKey) return + } else { + t.log.Warnf("Failed to repair by reinserting torrent %s, will only redownload broken files...", torrent.AccessKey) } } else { t.log.Warnf("Torrent %s is not older than %d hours to be repaired by reinsertion, will only redownload broken files...", torrent.AccessKey, EXPIRED_LINK_TOLERANCE_HOURS) @@ -210,9 +210,7 @@ func (t *TorrentManager) repair(torrent *Torrent) { }) t.log.Debugf("During repair, zurg found %d broken files for torrent %s", len(brokenFiles), torrent.AccessKey) - // todo: to verify removed logic when there's only 1 selected file selected and it's broken - - if len(brokenFiles) == 1 && torrent.SelectedFiles.Count() > 1 { + if len(brokenFiles) == 1 && torrent.SelectedFiles.Count() > 2 { // if we download a single file, it will be named differently // so we need to download 1 extra file to preserve the name // this is only relevant if we enable retain_rd_torrent_name @@ -227,7 +225,7 @@ func (t *TorrentManager) repair(torrent *Torrent) { } if len(brokenFiles) > 0 { - t.log.Infof("Redownloading the %d broken files for torrent %s", len(brokenFiles), torrent.AccessKey) + t.log.Infof("Redownloading %dof%d files for torrent %s", len(brokenFiles), torrent.SelectedFiles.Count(), torrent.AccessKey) brokenFileIDs := strings.Join(getFileIDs(brokenFiles), ",") if t.reinsertTorrent(torrent, brokenFileIDs) { t.log.Infof("Successfully downloaded torrent %s to repair it", torrent.AccessKey) diff --git a/pkg/http/client.go b/pkg/http/client.go index cafebe5..d4218b8 100644 --- a/pkg/http/client.go +++ b/pkg/http/client.go @@ -153,12 +153,13 @@ func (r *HTTPClient) Do(req *http.Request) (*http.Response, error) { attempt := 0 for { resp, err = r.client.Do(req) - if resp != nil && resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent { + if resp != nil && (resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent) { body, _ := io.ReadAll(resp.Body) if body != nil { var errResp ErrorResponse jsonErr := json.Unmarshal(body, &errResp) if jsonErr == nil { + errResp.Message += fmt.Sprintf(" (status code: %d)", resp.StatusCode) err = &errResp } }