package server import ( "net/http" "net/http/httptest" "os" "strings" "testing" "time" "github.com/gin-gonic/gin" "warpbox/lib/boxstore" "warpbox/lib/config" "warpbox/lib/models" ) const downloadTestBoxID = "abcdefabcdefabcdefabcdefabcdefab" func TestDownloadFileServesEmbeddableMediaInlineWithRangeSupport(t *testing.T) { app := setupDownloadFileTest(t, "clip.mp4", []byte("0123456789")) response := performDownloadFile(app, http.MethodGet, "/box/"+downloadTestBoxID+"/files/clip.mp4", map[string]string{ "Range": "bytes=0-3", }) if response.Code != http.StatusPartialContent { t.Fatalf("expected ranged download to return 206, got %d", response.Code) } if got := response.Header().Get("Content-Disposition"); !strings.HasPrefix(got, "inline;") || !strings.Contains(got, "filename=clip.mp4") { t.Fatalf("expected inline content disposition for embeddable media, got %q", got) } if got := response.Header().Get("Content-Type"); !strings.HasPrefix(got, "video/mp4") { t.Fatalf("expected video content type, got %q", got) } if got := response.Header().Get("Content-Range"); got != "bytes 0-3/10" { t.Fatalf("expected byte range header, got %q", got) } if got := response.Body.String(); got != "0123" { t.Fatalf("expected ranged body, got %q", got) } } func TestDownloadFileServesUnsafeInlineTypesAsAttachments(t *testing.T) { app := setupDownloadFileTest(t, "page.html", []byte("")) response := performDownloadFile(app, http.MethodGet, "/box/"+downloadTestBoxID+"/files/page.html", nil) if response.Code != http.StatusOK { t.Fatalf("expected download to return 200, got %d", response.Code) } if got := response.Header().Get("Content-Disposition"); !strings.HasPrefix(got, "attachment;") || !strings.Contains(got, "filename=page.html") { t.Fatalf("expected attachment content disposition for html, got %q", got) } } func TestDownloadFileSupportsHeadRequests(t *testing.T) { app := setupDownloadFileTest(t, "clip.mp4", []byte("0123456789")) response := performDownloadFile(app, http.MethodHead, "/box/"+downloadTestBoxID+"/files/clip.mp4", nil) if response.Code != http.StatusOK { t.Fatalf("expected HEAD download to return 200, got %d", response.Code) } if got := response.Header().Get("Content-Disposition"); !strings.HasPrefix(got, "inline;") { t.Fatalf("expected inline content disposition for HEAD request, got %q", got) } if response.Body.Len() != 0 { t.Fatalf("expected HEAD response body to be empty, got %d bytes", response.Body.Len()) } } func setupDownloadFileTest(t *testing.T, filename string, body []byte) *App { t.Helper() gin.SetMode(gin.TestMode) restoreUploadRoot := boxstore.UploadRoot() t.Cleanup(func() { boxstore.SetUploadRoot(restoreUploadRoot) }) boxstore.SetUploadRoot(t.TempDir()) if err := os.MkdirAll(boxstore.BoxPath(downloadTestBoxID), 0755); err != nil { t.Fatalf("MkdirAll returned error: %v", err) } path, ok := boxstore.SafeBoxFilePath(downloadTestBoxID, filename) if !ok { t.Fatal("SafeBoxFilePath rejected test file") } if err := os.WriteFile(path, body, 0644); err != nil { t.Fatalf("WriteFile returned error: %v", err) } manifest := models.BoxManifest{ Files: []models.BoxFile{{ ID: "0123456789abcdef", Name: filename, Size: int64(len(body)), MimeType: "", Status: models.FileStatusReady, }}, CreatedAt: time.Now().UTC(), } if err := boxstore.WriteManifest(downloadTestBoxID, manifest); err != nil { t.Fatalf("WriteManifest returned error: %v", err) } return &App{config: &config.Config{}} } func performDownloadFile(app *App, method string, path string, headers map[string]string) *httptest.ResponseRecorder { router := gin.New() router.GET("/box/:id/files/:filename", app.handleDownloadFile) router.HEAD("/box/:id/files/:filename", app.handleDownloadFile) request := httptest.NewRequest(method, path, nil) for key, value := range headers { request.Header.Set(key, value) } response := httptest.NewRecorder() router.ServeHTTP(response, request) return response }