diff --git a/pkg/debrid/debrid/cache.go b/pkg/debrid/debrid/cache.go index bf6306d..aa0f727 100644 --- a/pkg/debrid/debrid/cache.go +++ b/pkg/debrid/debrid/cache.go @@ -5,6 +5,14 @@ import ( "context" "errors" "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + "github.com/go-co-op/gocron/v2" "github.com/goccy/go-json" "github.com/puzpuzpuz/xsync/v3" @@ -13,13 +21,6 @@ import ( "github.com/sirrobot01/decypharr/internal/logger" "github.com/sirrobot01/decypharr/internal/utils" "github.com/sirrobot01/decypharr/pkg/debrid/types" - "os" - path "path/filepath" - "strconv" - "strings" - "sync" - "sync/atomic" - "time" ) type WebDavFolderNaming string @@ -115,8 +116,8 @@ func New(dc config.Debrid, client types.Client) *Cache { autoExpiresLinksAfter = 48 * time.Hour } return &Cache{ - dir: path.Join(cfg.Path, "cache", dc.Name), // path to save cache files - torrents: xsync.NewMapOf[string, string](), + dir: filepath.Join(cfg.Path, "cache", dc.Name), // path to save cache files + torrents: xsync.NewMapOf[string, *CachedTorrent](), torrentsNames: xsync.NewMapOf[string, *CachedTorrent](), invalidDownloadLinks: xsync.NewMapOf[string, string](), client: client, @@ -178,7 +179,7 @@ func (c *Cache) load() (map[string]*CachedTorrent, error) { // Get only json files var jsonFiles []os.DirEntry for _, file := range files { - if !file.IsDir() && path.Ext(file.Name()) == ".json" { + if !file.IsDir() && filepath.Ext(file.Name()) == ".json" { jsonFiles = append(jsonFiles, file) } } @@ -206,7 +207,7 @@ func (c *Cache) load() (map[string]*CachedTorrent, error) { } fileName := file.Name() - filePath := path.Join(c.dir, fileName) + filePath := filepath.Join(c.dir, fileName) data, err := os.ReadFile(filePath) if err != nil { c.logger.Error().Err(err).Msgf("Failed to read file: %s", filePath) @@ -238,8 +239,8 @@ func (c *Cache) load() (map[string]*CachedTorrent, error) { ct.AddedOn = addedOn } ct.IsComplete = true - ct.Name = path.Clean(ct.Name) ct.Files = fs + ct.Name = filepath.Clean(ct.Name) results.Store(ct.Id, &ct) } } @@ -393,19 +394,19 @@ func (c *Cache) sync(torrents []*types.Torrent) error { func (c *Cache) GetTorrentFolder(torrent *types.Torrent) string { switch c.folderNaming { case WebDavUseFileName: - return path.Clean(torrent.Filename) + return filepath.Clean(torrent.Filename) case WebDavUseOriginalName: - return path.Clean(torrent.OriginalFilename) + return filepath.Clean(torrent.OriginalFilename) case WebDavUseFileNameNoExt: - return path.Clean(utils.RemoveExtension(torrent.Filename)) + return filepath.Clean(utils.RemoveExtension(torrent.Filename)) case WebDavUseOriginalNameNoExt: - return path.Clean(utils.RemoveExtension(torrent.OriginalFilename)) + return filepath.Clean(utils.RemoveExtension(torrent.OriginalFilename)) case WebDavUseID: return torrent.Id case WebdavUseHash: return strings.ToLower(torrent.InfoHash) default: - return path.Clean(torrent.Filename) + return filepath.Clean(torrent.Filename) } } @@ -514,7 +515,7 @@ func (c *Cache) SaveTorrent(ct *CachedTorrent) { func (c *Cache) saveTorrent(id string, data []byte) { fileName := id + ".json" - filePath := path.Join(c.dir, fileName) + filePath := filepath.Join(c.dir, fileName) // Use a unique temporary filename for concurrent safety tmpFile := filePath + ".tmp." + strconv.FormatInt(time.Now().UnixNano(), 10) @@ -675,7 +676,7 @@ func (c *Cache) DeleteTorrents(ids []string) { func (c *Cache) removeFromDB(torrentId string) { // Moves the torrent file to the trash - filePath := path.Join(c.dir, torrentId+".json") + filePath := filepath.Join(c.dir, torrentId+".json") // Check if the file exists if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) { @@ -683,8 +684,8 @@ func (c *Cache) removeFromDB(torrentId string) { } // Move the file to the trash - trashPath := path.Join(c.dir, "trash", torrentId+".json") - if err := os.MkdirAll(path.Dir(trashPath), 0755); err != nil { + trashPath := filepath.Join(c.dir, "trash", torrentId+".json") + if err := os.MkdirAll(filepath.Dir(trashPath), 0755); err != nil { return } if err := os.Rename(filePath, trashPath); err != nil { diff --git a/pkg/debrid/debrid/xml.go b/pkg/debrid/debrid/xml.go index fa1e93c..6a2cbb6 100644 --- a/pkg/debrid/debrid/xml.go +++ b/pkg/debrid/debrid/xml.go @@ -2,12 +2,14 @@ package debrid import ( "fmt" - "github.com/beevik/etree" - "github.com/sirrobot01/decypharr/internal/request" "net/http" "os" - path "path/filepath" + "path" + "path/filepath" "time" + + "github.com/beevik/etree" + "github.com/sirrobot01/decypharr/internal/request" ) // resetPropfindResponse resets the propfind response cache for the specified parent directories. @@ -64,15 +66,15 @@ func (c *Cache) refreshParentXml(torrents []os.FileInfo, clientName, parent stri currentTime := time.Now().UTC().Format(http.TimeFormat) // Add the parent directory - baseUrl := path.Clean(fmt.Sprintf("/webdav/%s/%s", clientName, parent)) - parentPath := fmt.Sprintf("%s/", baseUrl) + baseUrl := path.Clean(path.Join("webdav", clientName, parent)) + parentPath := path.Join(baseUrl) addDirectoryResponse(multistatus, parentPath, parent, currentTime) // Add torrents to the XML for _, torrent := range torrents { name := torrent.Name() // Note the path structure change - parent first, then torrent name - torrentPath := fmt.Sprintf("/webdav/%s/%s/%s/", + torrentPath := filepath.Join("webdav", clientName, parent, name, diff --git a/pkg/server/server.go b/pkg/server/server.go index 2e7ac31..2dd84f5 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -4,19 +4,20 @@ import ( "context" "errors" "fmt" + "io" + "net/http" + "net/url" + "os" + "os/signal" + "runtime" + "syscall" + "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/rs/zerolog" "github.com/sirrobot01/decypharr/internal/config" "github.com/sirrobot01/decypharr/internal/logger" "github.com/sirrobot01/decypharr/internal/request" - "io" - "net/http" - "os" - "os/signal" - path "path/filepath" - "runtime" - "syscall" ) type Server struct { @@ -34,9 +35,9 @@ func New(handlers map[string]http.Handler) *Server { s := &Server{ logger: l, } - - r.Handle(path.Join(cfg.URLBase, "static")+"/*", - http.StripPrefix(path.Join(cfg.URLBase, "static"), http.FileServer(http.Dir("static"))), + staticPath, _ := url.JoinPath(cfg.URLBase, "static") + r.Handle(staticPath+"/*", + http.StripPrefix(staticPath, http.FileServer(http.Dir("static"))), ) r.Route(cfg.URLBase, func(r chi.Router) { diff --git a/pkg/webdav/handler.go b/pkg/webdav/handler.go index 98e2af0..e9efc71 100644 --- a/pkg/webdav/handler.go +++ b/pkg/webdav/handler.go @@ -4,6 +4,18 @@ import ( "bytes" "context" "fmt" + "html/template" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path" + "path/filepath" + "slices" + "strings" + "time" + "github.com/rs/zerolog" "github.com/sirrobot01/decypharr/internal/request" "github.com/sirrobot01/decypharr/internal/utils" @@ -11,16 +23,6 @@ import ( "github.com/sirrobot01/decypharr/pkg/debrid/types" "github.com/sirrobot01/decypharr/pkg/version" "golang.org/x/net/webdav" - "html/template" - "io" - "net/http" - "net/http/httptest" - "net/url" - "os" - path "path/filepath" - "slices" - "strings" - "time" ) type Handler struct { @@ -47,9 +49,12 @@ func (h *Handler) Mkdir(ctx context.Context, name string, perm os.FileMode) erro // RemoveAll implements webdav.FileSystem func (h *Handler) RemoveAll(ctx context.Context, name string) error { - name = path.Clean("/" + name) + if name[0] != '/' { + name = "/" + name + } + name = filepath.Clean(name) - rootDir := h.getRootPath() + rootDir := filepath.Clean(h.getRootPath()) if name == rootDir { return os.ErrPermission @@ -72,7 +77,7 @@ func (h *Handler) Rename(ctx context.Context, oldName, newName string) error { } func (h *Handler) getRootPath() string { - return fmt.Sprintf("/webdav/%s", h.Name) + return fmt.Sprintf(filepath.Join(string(os.PathSeparator), "webdav", "%s"), h.Name) } func (h *Handler) getTorrentsFolders() []os.FileInfo { @@ -104,8 +109,11 @@ func (h *Handler) getParentFiles() []os.FileInfo { } func (h *Handler) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) { - name = utils.UnescapePath(path.Clean("/" + name)) - rootDir := h.getRootPath() + if name[0] != '/' { + name = "/" + name + } + name = utils.UnescapePath(filepath.Clean(name)) + rootDir := filepath.Clean(h.getRootPath()) metadataOnly := ctx.Value("metadataOnly") != nil @@ -118,11 +126,11 @@ func (h *Handler) OpenFile(ctx context.Context, name string, flag int, perm os.F cache: h.cache, isDir: true, children: h.getParentFiles(), - name: "/", + name: string(os.PathSeparator), metadataOnly: true, modTime: now, }, nil - case path.Join(rootDir, "version.txt"): + case filepath.Join(rootDir, "version.txt"): versionInfo := version.GetInfo().String() return &File{ cache: h.cache, @@ -138,7 +146,7 @@ func (h *Handler) OpenFile(ctx context.Context, name string, flag int, perm os.F // Single check for top-level folders if h.isParentPath(name) { folderName := strings.TrimPrefix(name, rootDir) - folderName = strings.TrimPrefix(folderName, "/") + folderName = strings.TrimPrefix(folderName, string(os.PathSeparator)) // Only fetcher the torrent folders once children := h.getTorrentsFolders() @@ -155,7 +163,7 @@ func (h *Handler) OpenFile(ctx context.Context, name string, flag int, perm os.F } _path := strings.TrimPrefix(name, rootDir) - parts := strings.Split(strings.TrimPrefix(_path, "/"), "/") + parts := strings.Split(strings.TrimPrefix(_path, string(os.PathSeparator)), string(os.PathSeparator)) if len(parts) >= 2 && (slices.Contains(h.getParentItems(), parts[0])) { @@ -181,7 +189,7 @@ func (h *Handler) OpenFile(ctx context.Context, name string, flag int, perm os.F } // Torrent file level - filename := strings.Join(parts[2:], "/") + filename := filepath.Join(parts[2:]...) if file, ok := cachedTorrent.Files[filename]; ok { return &File{ cache: h.cache, @@ -239,7 +247,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Set metadata only ctx := context.WithValue(r.Context(), "metadataOnly", true) r = r.WithContext(ctx) - cleanPath := path.Clean(r.URL.Path) + cleanPath := filepath.Clean(r.URL.Path) if r.Header.Get("Depth") == "" { r.Header.Set("Depth", "1") } @@ -399,7 +407,7 @@ func (h *Handler) isParentPath(_path string) bool { rootPath := h.getRootPath() parents := h.getParentItems() for _, p := range parents { - if path.Clean(_path) == path.Clean(path.Join(rootPath, p)) { + if filepath.Clean(_path) == filepath.Clean(filepath.Join(rootPath, p)) { return true } } diff --git a/pkg/webdav/misc.go b/pkg/webdav/misc.go index b900403..08f7f4a 100644 --- a/pkg/webdav/misc.go +++ b/pkg/webdav/misc.go @@ -3,6 +3,7 @@ package webdav import ( "net/http" "net/url" + "os" "strings" ) @@ -10,11 +11,11 @@ import ( // /webdav/alldebrid/__all__/TorrentName func getName(rootDir, path string) (string, string) { path = strings.TrimPrefix(path, rootDir) - parts := strings.Split(strings.TrimPrefix(path, "/"), "/") + parts := strings.Split(strings.TrimPrefix(path, string(os.PathSeparator)), string(os.PathSeparator)) if len(parts) < 2 { return "", "" } - return parts[1], strings.Join(parts[2:], "/") // Note the change from [0] to [1] + return parts[1], strings.Join(parts[2:], string(os.PathSeparator)) // Note the change from [0] to [1] } func acceptsGzip(r *http.Request) bool {