package universal import ( "io" "net/http" "net/url" "path" "path/filepath" "strings" "github.com/debridmediamanager.com/zurg/internal/config" "github.com/debridmediamanager.com/zurg/internal/dav" intHttp "github.com/debridmediamanager.com/zurg/internal/http" intTor "github.com/debridmediamanager.com/zurg/internal/torrent" zurghttp "github.com/debridmediamanager.com/zurg/pkg/http" "github.com/debridmediamanager.com/zurg/pkg/logutil" "github.com/hashicorp/golang-lru/v2/expirable" "go.uber.org/zap" ) type GetFile struct { client *zurghttp.HTTPClient } func NewGetFile(client *zurghttp.HTTPClient) *GetFile { return &GetFile{client: client} } // HandleGetRequest handles a GET request universally for both WebDAV and HTTP func (gf *GetFile) HandleGetRequest(w http.ResponseWriter, r *http.Request, t *intTor.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) { log := logutil.NewLogger().Named("file") requestPath := path.Clean(r.URL.Path) isDav := true if strings.Contains(requestPath, "/http") { requestPath = strings.Replace(requestPath, "/http", "/", 1) isDav = false } if requestPath == "/favicon.ico" { return } segments := strings.Split(requestPath, "/") // If there are less than 3 segments, return an error or adjust as needed if len(segments) <= 3 { if isDav { dav.HandlePropfindRequest(w, r, t, log) } else { intHttp.HandleDirectoryListing(w, r, t) } return } baseDirectory := segments[len(segments)-3] accessKey := segments[len(segments)-2] filename := segments[len(segments)-1] torrents, ok := t.DirectoryMap.Get(baseDirectory) if !ok { log.Warnf("Cannot find directory %s", baseDirectory) http.Error(w, "File not found", http.StatusNotFound) return } torrent, ok := torrents.Get(accessKey) if !ok { log.Warnf("Cannot find torrent %s in the directory %s", accessKey, baseDirectory) http.Error(w, "File not found", http.StatusNotFound) return } file, ok := torrent.SelectedFiles.Get(filename) if !ok { log.Warnf("Cannot find file from path %s", requestPath) http.Error(w, "File not found", http.StatusNotFound) return } if url, exists := cache.Get(requestPath); exists { if c.ShouldServeFromRclone() { redirect(w, r, url, c) } else { gf.streamFileToResponse(file, url, w, r, t, c, log) } return } if !strings.HasPrefix(file.Link, "http") { // This is a dead file, serve an alternate file log.Warnf("File %s is not yet available, zurg is repairing the torrent", filename) gf.playErrorVideo("https://www.youtube.com/watch?v=bGTqwt6vdcY", w, r, t, c, log) return } link := file.Link resp := t.UnrestrictUntilOk(link) if resp == nil { log.Warnf("File %s is no longer available", file.Path) file.Link = "repair" t.SetChecksum("") // force a recheck gf.playErrorVideo("https://www.youtube.com/watch?v=gea_FJrtFVA", w, r, t, c, log) } else { if resp.Filename != filename { // this is possible if there's only 1 streamable file in the torrent // and then suddenly it's a rar file actualExt := filepath.Ext(resp.Filename) expectedExt := filepath.Ext(filename) if actualExt != expectedExt && resp.Streamable != 1 { log.Warnf("File was changed and is not streamable: %s and %s", filename, resp.Filename) gf.playErrorVideo("https://www.youtube.com/watch?v=t9VgOriBHwE", w, r, t, c, log) return } else { log.Warnf("Filename mismatch: %s and %s", filename, resp.Filename) } } cache.Add(requestPath, resp.Download) if c.ShouldServeFromRclone() { redirect(w, r, resp.Download, c) } else { gf.streamFileToResponse(file, resp.Download, w, r, t, c, log) } } } func (gf *GetFile) streamFileToResponse(file *intTor.File, url string, w http.ResponseWriter, r *http.Request, torMgr *intTor.TorrentManager, cfg config.ConfigInterface, log *zap.SugaredLogger) { // Create a new request for the file download. req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { if file != nil { log.Errorf("Error creating new request for file %s: %v", file.Path, err) } gf.playErrorVideo("https://www.youtube.com/watch?v=H3NSrObyAxM", w, r, torMgr, cfg, log) return } // copy range header if it exists if r.Header.Get("Range") != "" { req.Header.Add("Range", r.Header.Get("Range")) } resp, err := gf.client.Do(req) if err != nil { if file != nil { log.Warnf("Cannot download file %s: %v", file.Path, err) file.Link = "repair" torMgr.SetChecksum("") // force a recheck } gf.playErrorVideo("https://www.youtube.com/watch?v=FSSd8cponAA", w, r, torMgr, cfg, log) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent { if file != nil { log.Warnf("Received a %s status code for file %s", resp.Status, file.Path) file.Link = "repair" torMgr.SetChecksum("") // force a recheck } gf.playErrorVideo("https://www.youtube.com/watch?v=BcseUxviVqE", w, r, torMgr, cfg, log) return } for k, vv := range resp.Header { for _, v := range vv { w.Header().Add(k, v) } } buf := make([]byte, cfg.GetNetworkBufferSize()) io.CopyBuffer(w, resp.Body, buf) } func (gf *GetFile) playErrorVideo(link string, w http.ResponseWriter, r *http.Request, t *intTor.TorrentManager, c config.ConfigInterface, log *zap.SugaredLogger) { resp := t.UnrestrictUntilOk(link) if resp == nil { http.Error(w, "REAL-DEBRID IS DOWN", http.StatusInternalServerError) return } if c.ShouldServeFromRclone() { redirect(w, r, resp.Download, c) } gf.streamFileToResponse(nil, resp.Download, w, r, t, c, log) } func redirect(w http.ResponseWriter, r *http.Request, url string, c config.ConfigInterface) { prefHost := c.GetRandomPreferredHost() if prefHost != "" { url = replaceHostInURL(url, prefHost) } http.Redirect(w, r, url, http.StatusFound) } func replaceHostInURL(inputURL string, newHost string) string { u, err := url.Parse(inputURL) if err != nil { return "" } u.Host = newHost return u.String() }