package handlers import ( "errors" "fmt" "net/http" "os" "path/filepath" "strings" "time" "warpbox.dev/backend/libs/helpers" "warpbox.dev/backend/libs/services" "warpbox.dev/backend/libs/web" ) type downloadPageData struct { Box boxView Files []fileView ZipURL string Locked bool Obfuscated bool CanPreview bool DownloadCount int MaxDownloads int ExpiresLabel string } type boxView struct { ID string } type fileView struct { ID string Name string Size string ContentType string PreviewKind string URL string DownloadURL string ThumbnailURL string } type previewPageData struct { Box boxView File fileView Locked bool DownloadURL string } func (a *App) DownloadPage(w http.ResponseWriter, r *http.Request) { box, err := a.uploadService.GetBox(r.PathValue("boxID")) if err != nil { http.NotFound(w, r) return } if err := a.uploadService.CanDownload(box); err != nil { a.renderPage(w, r, http.StatusForbidden, "download.html", web.PageData{ Title: "Download unavailable", Description: "This Warpbox link is no longer available.", Data: downloadPageData{ Box: boxView{ID: box.ID}, ExpiresLabel: err.Error(), }, }) return } locked := a.uploadService.IsProtected(box) && !a.isBoxUnlocked(r, box) files := make([]fileView, 0, len(box.Files)) if !(locked && box.Obfuscate) { for _, file := range box.Files { files = append(files, a.fileView(box, file)) } } a.renderPage(w, r, http.StatusOK, "download.html", web.PageData{ Title: "Download files", Description: "Download files shared through Warpbox.", Data: downloadPageData{ Box: boxView{ID: box.ID}, Files: files, ZipURL: fmt.Sprintf("/d/%s/zip", box.ID), Locked: locked, Obfuscated: box.Obfuscate, DownloadCount: box.DownloadCount, MaxDownloads: box.MaxDownloads, ExpiresLabel: box.ExpiresAt.Format("Jan 2, 2006 15:04 MST"), }, }) } func (a *App) DownloadFile(w http.ResponseWriter, r *http.Request) { box, file, ok := a.loadFileForRequest(w, r) if !ok { return } locked := a.uploadService.IsProtected(box) && !a.isBoxUnlocked(r, box) view := a.fileView(box, file) title := file.Name description := fmt.Sprintf("%s shared via Warpbox", helpers.FormatBytes(file.Size)) imageURL := absoluteURL(r, view.ThumbnailURL) if locked && box.Obfuscate { title = "Protected Warpbox file" description = "This shared file is password protected." imageURL = absoluteURL(r, "/static/img/file-placeholder.webp") } a.renderPage(w, r, http.StatusOK, "preview.html", web.PageData{ Title: title, Description: description, ImageURL: imageURL, Data: previewPageData{ Box: boxView{ID: box.ID}, File: view, Locked: locked, DownloadURL: view.DownloadURL, }, }) } func (a *App) DownloadFileContent(w http.ResponseWriter, r *http.Request) { box, file, ok := a.loadFileForRequest(w, r) if !ok { return } if a.uploadService.IsProtected(box) && !a.isBoxUnlocked(r, box) { http.Error(w, "password required", http.StatusUnauthorized) return } a.serveFileContent(w, r, box, file, r.URL.Query().Get("inline") != "1") } func (a *App) Thumbnail(w http.ResponseWriter, r *http.Request) { box, file, ok := a.loadFileForRequest(w, r) if !ok { return } if a.uploadService.IsProtected(box) && box.Obfuscate && !a.isBoxUnlocked(r, box) { http.ServeFile(w, r, filepath.Join(a.cfg.StaticDir, "img", "file-placeholder.webp")) return } path := a.uploadService.ThumbnailPath(box, file) if path == "" { http.ServeFile(w, r, filepath.Join(a.cfg.StaticDir, "img", "file-placeholder.webp")) return } http.ServeFile(w, r, path) } func (a *App) UnlockBox(w http.ResponseWriter, r *http.Request) { box, err := a.uploadService.GetBox(r.PathValue("boxID")) if err != nil { http.NotFound(w, r) return } if err := r.ParseForm(); err != nil { http.Redirect(w, r, fmt.Sprintf("/d/%s", box.ID), http.StatusSeeOther) return } if !a.uploadService.VerifyPassword(box, r.FormValue("password")) { a.logger.Warn("box unlock failed", "source", "user_activity", "severity", "warn", "code", 4011, "box_id", box.ID) http.Redirect(w, r, fmt.Sprintf("/d/%s", box.ID), http.StatusSeeOther) return } http.SetCookie(w, &http.Cookie{ Name: unlockCookieName(box.ID), Value: a.uploadService.UnlockToken(box), Path: "/d/" + box.ID, HttpOnly: true, SameSite: http.SameSiteLaxMode, Secure: r.TLS != nil, Expires: box.ExpiresAt, }) a.logger.Info("box unlocked", "source", "user_activity", "severity", "user_activity", "code", 2002, "box_id", box.ID) http.Redirect(w, r, fmt.Sprintf("/d/%s", box.ID), http.StatusSeeOther) } func (a *App) loadFileForRequest(w http.ResponseWriter, r *http.Request) (services.Box, services.File, bool) { box, err := a.uploadService.GetBox(r.PathValue("boxID")) if err != nil { http.NotFound(w, r) return services.Box{}, services.File{}, false } if err := a.uploadService.CanDownload(box); err != nil { http.Error(w, err.Error(), statusForDownloadError(err)) return services.Box{}, services.File{}, false } file, err := a.uploadService.FindFile(box, r.PathValue("fileID")) if err != nil { http.NotFound(w, r) return services.Box{}, services.File{}, false } return box, file, true } func (a *App) serveFileContent(w http.ResponseWriter, r *http.Request, box services.Box, file services.File, attachment bool) { path := a.uploadService.FilePath(box, file) source, err := os.Open(path) if err != nil { http.NotFound(w, r) return } defer source.Close() stat, err := source.Stat() if err != nil { http.NotFound(w, r) return } w.Header().Set("Content-Type", file.ContentType) if attachment { w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", file.Name)) } http.ServeContent(w, r, file.Name, stat.ModTime(), source) if err := a.uploadService.RecordDownload(box.ID); err != nil && !errors.Is(err, os.ErrNotExist) { a.logger.Warn("failed to record file download", "source", "download", "severity", "warn", "code", 4002, "box_id", box.ID, "error", err.Error()) } } func (a *App) DownloadZip(w http.ResponseWriter, r *http.Request) { box, err := a.uploadService.GetBox(r.PathValue("boxID")) if err != nil { http.NotFound(w, r) return } if err := a.uploadService.CanDownload(box); err != nil { http.Error(w, err.Error(), statusForDownloadError(err)) return } if a.uploadService.IsProtected(box) && !a.isBoxUnlocked(r, box) { http.Error(w, "password required", http.StatusUnauthorized) return } w.Header().Set("Content-Type", "application/zip") w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", "warpbox-"+box.ID+".zip")) w.Header().Set("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) if err := a.uploadService.WriteZip(w, box); err != nil { a.logger.Error("zip download failed", "source", "download", "severity", "error", "code", 5002, "box_id", box.ID, "error", err.Error()) return } if err := a.uploadService.RecordDownload(box.ID); err != nil && !errors.Is(err, os.ErrNotExist) { a.logger.Warn("failed to record zip download", "source", "download", "severity", "warn", "code", 4003, "box_id", box.ID, "error", err.Error()) } } func (a *App) fileView(box services.Box, file services.File) fileView { return fileView{ ID: file.ID, Name: file.Name, Size: helpers.FormatBytes(file.Size), ContentType: file.ContentType, PreviewKind: file.PreviewKind, URL: fmt.Sprintf("/d/%s/f/%s", box.ID, file.ID), DownloadURL: fmt.Sprintf("/d/%s/f/%s/download", box.ID, file.ID), ThumbnailURL: fmt.Sprintf("/d/%s/thumb/%s", box.ID, file.ID), } } func (a *App) isBoxUnlocked(r *http.Request, box services.Box) bool { if !a.uploadService.IsProtected(box) { return true } cookie, err := r.Cookie(unlockCookieName(box.ID)) if err != nil { return false } return cookie.Value == a.uploadService.UnlockToken(box) } func unlockCookieName(boxID string) string { return "warpbox_unlock_" + strings.NewReplacer("-", "_", ".", "_").Replace(boxID) } func absoluteURL(r *http.Request, path string) string { if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") { return path } scheme := "http" if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { scheme = "https" } return fmt.Sprintf("%s://%s%s", scheme, r.Host, path) }