From 5ef5623c8d90fc4f1725b1c1233d21262b859844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6sche?= Date: Sun, 11 Jan 2026 14:38:03 +0100 Subject: [PATCH] feat: update WebDAV download handler to support range requests and improve response handling --- go_cloud/internal/http/routes.go | 48 +++++++++++++++++++++-------- go_cloud/internal/storage/webdav.go | 17 +++++----- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/go_cloud/internal/http/routes.go b/go_cloud/internal/http/routes.go index b2af84e..6512894 100644 --- a/go_cloud/internal/http/routes.go +++ b/go_cloud/internal/http/routes.go @@ -1418,13 +1418,13 @@ func downloadOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database // Download from user's Nextcloud space under /orgs// rel := strings.TrimPrefix(filePath, "/") remotePath := path.Join("/orgs", orgID.String(), rel) - reader, size, err := storageClient.Download(r.Context(), remotePath) + resp, err := storageClient.Download(r.Context(), remotePath, r.Header.Get("Range")) if err != nil { errors.LogError(r, err, "Failed to download from Nextcloud") errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound) return } - defer reader.Close() + defer resp.Body.Close() // Set appropriate headers for inline viewing fileName := path.Base(filePath) @@ -1438,13 +1438,25 @@ func downloadOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database contentType = "image/jpeg" } w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName)) - w.Header().Set("Content-Type", contentType) - if size > 0 { - w.Header().Set("Content-Length", fmt.Sprintf("%d", size)) + if ct := resp.Header.Get("Content-Type"); ct != "" { + w.Header().Set("Content-Type", ct) + } else { + w.Header().Set("Content-Type", contentType) + } + w.Header().Set("Accept-Ranges", "bytes") + if cr := resp.Header.Get("Content-Range"); cr != "" { + w.Header().Set("Content-Range", cr) + } + if cl := resp.Header.Get("Content-Length"); cl != "" { + w.Header().Set("Content-Length", cl) + } + + if resp.StatusCode == http.StatusPartialContent { + w.WriteHeader(http.StatusPartialContent) } // Stream the file - io.Copy(w, reader) + io.Copy(w, resp.Body) } @@ -1481,13 +1493,13 @@ func downloadUserFileHandler(w http.ResponseWriter, r *http.Request, db *databas remotePath := strings.TrimPrefix(filePath, "/") fmt.Printf("[DEBUG] Downloading from user WebDAV: /%s\n", remotePath) - reader, size, err := storageClient.Download(r.Context(), "/"+remotePath) + resp, err := storageClient.Download(r.Context(), "/"+remotePath, r.Header.Get("Range")) if err != nil { errors.LogError(r, err, "Failed to download from Nextcloud") errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound) return } - defer reader.Close() + defer resp.Body.Close() // Set appropriate headers for inline viewing fileName := path.Base(filePath) @@ -1501,12 +1513,24 @@ func downloadUserFileHandler(w http.ResponseWriter, r *http.Request, db *databas contentType = "image/jpeg" } w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName)) - w.Header().Set("Content-Type", contentType) - if size > 0 { - w.Header().Set("Content-Length", fmt.Sprintf("%d", size)) + if ct := resp.Header.Get("Content-Type"); ct != "" { + w.Header().Set("Content-Type", ct) + } else { + w.Header().Set("Content-Type", contentType) + } + w.Header().Set("Accept-Ranges", "bytes") + if cr := resp.Header.Get("Content-Range"); cr != "" { + w.Header().Set("Content-Range", cr) + } + if cl := resp.Header.Get("Content-Length"); cl != "" { + w.Header().Set("Content-Length", cl) + } + + if resp.StatusCode == http.StatusPartialContent { + w.WriteHeader(http.StatusPartialContent) } // Stream the file - io.Copy(w, reader) + io.Copy(w, resp.Body) } diff --git a/go_cloud/internal/storage/webdav.go b/go_cloud/internal/storage/webdav.go index ad69773..533eb85 100644 --- a/go_cloud/internal/storage/webdav.go +++ b/go_cloud/internal/storage/webdav.go @@ -124,9 +124,9 @@ func (c *WebDAVClient) Upload(ctx context.Context, remotePath string, r io.Reade } // Download retrieves a file from the remotePath using HTTP GET (WebDAV). -func (c *WebDAVClient) Download(ctx context.Context, remotePath string) (io.ReadCloser, int64, error) { +func (c *WebDAVClient) Download(ctx context.Context, remotePath string, rangeHeader string) (*http.Response, error) { if c == nil { - return nil, 0, fmt.Errorf("no webdav client configured") + return nil, fmt.Errorf("no webdav client configured") } rel := strings.TrimLeft(remotePath, "/") @@ -146,24 +146,27 @@ func (c *WebDAVClient) Download(ctx context.Context, remotePath string) (io.Read req, err := http.NewRequestWithContext(ctx, "GET", full, nil) if err != nil { - return nil, 0, err + return nil, err } if c.user != "" { req.SetBasicAuth(c.user, c.pass) } + if rangeHeader != "" { + req.Header.Set("Range", rangeHeader) + } resp, err := c.httpClient.Do(req) if err != nil { - return nil, 0, err + return nil, err } if resp.StatusCode >= 200 && resp.StatusCode < 300 { - return resp.Body, resp.ContentLength, nil + return resp, nil } - defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) - return nil, 0, fmt.Errorf("webdav download failed: %d %s", resp.StatusCode, string(body)) + resp.Body.Close() + return nil, fmt.Errorf("webdav download failed: %d %s", resp.StatusCode, string(body)) } // Delete removes a file or collection from the remotePath using HTTP DELETE (WebDAV).