diff --git a/Dockerfile b/Dockerfile index cafb269..ed49449 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ ARG GOARCH=amd64 FROM golang:1-alpine AS builder WORKDIR /app COPY . . -RUN CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags="-s -w" -o zurg cmd/zurg/main.go +RUN CGO_ENABLED=0 GOOS=${GOOS} GOARCH=${GOARCH} go build -ldflags="-s -w" -o zurg cmd/zurg/zurg.go # Obfuscation stage FROM alpine:3 AS obfuscator diff --git a/cmd/zurg/main.go b/cmd/zurg/main.go deleted file mode 100644 index 57652b2..0000000 --- a/cmd/zurg/main.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "fmt" - "log" - "net/http" - "time" - - "github.com/debridmediamanager.com/zurg/internal/config" - "github.com/debridmediamanager.com/zurg/internal/net" - "github.com/debridmediamanager.com/zurg/internal/torrent" - "github.com/hashicorp/golang-lru/v2/expirable" -) - -func main() { - config, configErr := config.LoadZurgConfig("./config.yml") - if configErr != nil { - log.Panicf("Config failed to load: %v", configErr) - } - - cache := expirable.NewLRU[string, string](1e4, nil, time.Hour) - - t := torrent.NewTorrentManager(config, cache) - - mux := http.NewServeMux() - net.Router(mux, config, t, cache) - - addr := fmt.Sprintf(":%s", config.GetPort()) - log.Printf("Starting server on %s\n", addr) - err := http.ListenAndServe(addr, mux) - if err != nil { - log.Panicf("Failed to start server: %v", err) - } -} diff --git a/internal/dav/propfind.go b/internal/dav/listing.go similarity index 98% rename from internal/dav/propfind.go rename to internal/dav/listing.go index f982f3b..5637b56 100644 --- a/internal/dav/propfind.go +++ b/internal/dav/listing.go @@ -38,6 +38,7 @@ func HandlePropfindRequest(w http.ResponseWriter, r *http.Request, t *torrent.To case len(filteredSegments) == 2: output, err = handleSingleTorrent(requestPath, w, r, t) default: + log.Println("Not Found", r.Method, requestPath) writeHTTPError(w, "Not Found", http.StatusNotFound) return } @@ -105,6 +106,5 @@ func handleSingleTorrent(requestPath string, w http.ResponseWriter, r *http.Requ } func writeHTTPError(w http.ResponseWriter, errorMessage string, statusCode int) { - log.Println(errorMessage) http.Error(w, errorMessage, statusCode) } diff --git a/internal/http/get.go b/internal/http/get.go deleted file mode 100644 index e066389..0000000 --- a/internal/http/get.go +++ /dev/null @@ -1,278 +0,0 @@ -package http - -import ( - "fmt" - "log" - "net/http" - "net/url" - "path" - "path/filepath" - "strings" - - "github.com/debridmediamanager.com/zurg/internal/config" - "github.com/debridmediamanager.com/zurg/internal/torrent" - "github.com/debridmediamanager.com/zurg/pkg/davextra" - "github.com/debridmediamanager.com/zurg/pkg/realdebrid" - "github.com/hashicorp/golang-lru/v2/expirable" -) - -func HandleHeadRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) { - requestPath := path.Clean(r.URL.Path) - requestPath = strings.Replace(requestPath, "/http", "", 1) - 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) < 4 { - log.Println("Method not implemented", r.Method, r.URL.Path) - http.Error(w, "Method not implemented", http.StatusMethodNotAllowed) - return - } - - if data, exists := cache.Get("head:" + requestPath); exists { - splits := strings.Split(data, " ") - contentType := splits[0] - contentLength := splits[1] - w.Header().Set("Content-Type", contentType) - w.Header().Set("Content-Length", contentLength) - w.WriteHeader(http.StatusOK) - return - } - - baseDirectory := segments[len(segments)-3] - torrentName := segments[len(segments)-2] - filename := segments[len(segments)-1] - - torrents := t.FindAllTorrentsWithName(baseDirectory, torrentName) - if torrents == nil { - log.Println("Cannot find torrent", requestPath) - http.Error(w, "Cannot find file", http.StatusNotFound) - return - } - - filenameV2, linkFragment := davextra.ExtractLinkFragment(filename) - _, file := getFile(torrents, filenameV2, linkFragment) - if file == nil { - log.Println("Cannot find file (head)", requestPath) - http.Error(w, "Cannot find file", http.StatusNotFound) - return - } - if file.Link == "" { - log.Println("Link not found (head)", filename) - http.Error(w, "Cannot find file", http.StatusNotFound) - return - } - contentType := getContentMimeType(filename) - contentLength := fmt.Sprintf("%d", file.Bytes) - w.Header().Set("Content-Type", contentType) - w.Header().Set("Content-Length", contentLength) - cache.Add("head:"+requestPath, contentType+" "+contentLength) - w.WriteHeader(http.StatusOK) -} - -func getContentMimeType(filePath string) string { - switch filepath.Ext(filePath) { - case ".mkv": - return "video/x-matroska" - case ".mp4": - return "video/mp4" - case ".avi": - return "video/x-msvideo" - case ".mp3": - return "audio/mpeg" - case ".rar": - return "application/x-rar-compressed" - case ".zip": - return "application/zip" - case ".7z": - return "application/x-7z-compressed" - case ".srt": - return "text/plain" - default: - return "application/octet-stream" - } -} - -func HandleGetRequest(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) { - requestPath := path.Clean(r.URL.Path) - requestPath = strings.Replace(requestPath, "/http", "/", 1) - 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) < 4 { - HandleDirectoryListing(w, r, t, c, cache) - return - } - if data, exists := cache.Get(requestPath); exists { - http.Redirect(w, r, data, http.StatusFound) - return - } - - baseDirectory := segments[len(segments)-3] - torrentName := segments[len(segments)-2] - filename := segments[len(segments)-1] - - torrents := t.FindAllTorrentsWithName(baseDirectory, torrentName) - if torrents == nil { - log.Println("Cannot find torrent", requestPath) - http.Error(w, "Cannot find file", http.StatusNotFound) - return - } - - filenameV2, linkFragment := davextra.ExtractLinkFragment(filename) - _, file := getFile(torrents, filenameV2, linkFragment) - if file == nil { - log.Println("Cannot find file (get)", requestPath) - http.Error(w, "Cannot find file", http.StatusNotFound) - return - } - if file.Link == "" { - log.Println("Link not found (get)", filename) - http.Error(w, "Cannot find file", http.StatusNotFound) - return - } - link := file.Link - - unrestrictFn := func() (*realdebrid.UnrestrictResponse, error) { - return realdebrid.UnrestrictLink(c.GetToken(), link) - } - resp := realdebrid.RetryUntilOk(unrestrictFn) - if resp == nil { - log.Println("Cannot unrestrict link", link, filenameV2) - // t.HideTheFile(torrent, file) - // http.Error(w, "Cannot find file", http.StatusNotFound) - http.Redirect(w, r, "https://send.nukes.wtf/tDeTd0", http.StatusFound) - return - } - if resp.Filename != filenameV2 { - actualExt := filepath.Ext(resp.Filename) - expectedExt := filepath.Ext(filenameV2) - if actualExt != expectedExt { - log.Println("File extension mismatch", resp.Filename, filenameV2) - } else { - log.Println("Filename mismatch", resp.Filename, filenameV2) - } - } - cache.Add(requestPath, resp.Download) - http.Redirect(w, r, resp.Download, http.StatusFound) -} - -// getFile finds a link by a fragment, it might be wrong -func getFile(torrents []torrent.Torrent, filename, fragment string) (*torrent.Torrent, *torrent.File) { - for t := range torrents { - for f, file := range torrents[t].SelectedFiles { - fname := filepath.Base(file.Path) - if filename == fname && strings.Contains(file.Link, fragment) { - return &torrents[t], &torrents[t].SelectedFiles[f] - } - } - } - return nil, nil -} - -func HandleDirectoryListing(w http.ResponseWriter, r *http.Request, t *torrent.TorrentManager, c config.ConfigInterface, cache *expirable.LRU[string, string]) { - requestPath := path.Clean(r.URL.Path) - - if data, exists := cache.Get(requestPath); exists { - w.Header().Set("Content-Type", "text/html; charset=\"utf-8\"") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, data) - return - } - - var output *string - var err error - - filteredSegments := removeEmptyStrings(strings.Split(requestPath, "/")) - switch { - case len(filteredSegments) == 1: - output, err = handleRoot(w, r, c) - case len(filteredSegments) == 2: - output, err = handleListOfTorrents(requestPath, w, r, t, c) - case len(filteredSegments) == 3: - output, err = handleSingleTorrent(requestPath, w, r, t) - case len(filteredSegments) > 3: - HandleGetRequest(w, r, t, c, cache) - default: - writeHTTPError(w, "Not Found", http.StatusNotFound) - return - } - - if err != nil { - log.Printf("Error processing request: %v\n", err) - writeHTTPError(w, "Server error", http.StatusInternalServerError) - return - } - - if output != nil { - cache.Add(requestPath, *output) - - w.Header().Set("Content-Type", "text/html; charset=\"utf-8\"") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, *output) - } -} - -func handleRoot(w http.ResponseWriter, r *http.Request, c config.ConfigInterface) (*string, error) { - htmlDoc := "