upload-service/internal/handler/download.go

150 lines
3.8 KiB
Go

package handler
import (
"database/sql"
"html/template"
"log"
"net/http"
"strings"
"time"
)
var downloadTmpl *template.Template
// isPreviewable returns true if the content type can be viewed inline in a browser.
func isPreviewable(contentType string) bool {
if strings.HasPrefix(contentType, "image/") ||
strings.HasPrefix(contentType, "video/") ||
strings.HasPrefix(contentType, "audio/") ||
strings.HasPrefix(contentType, "text/") ||
contentType == "application/pdf" {
return true
}
return false
}
// Download shows a file info page with view/download buttons.
func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
if id == "" {
http.NotFound(w, r)
return
}
rec, err := h.store.Get(id)
if err == sql.ErrNoRows {
http.NotFound(w, r)
return
}
if err != nil {
log.Printf("db get error: %v", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
// Check expiry
if rec.ExpiresAt != nil && rec.ExpiresAt.Before(time.Now().UTC()) {
http.Error(w, "this file has expired", http.StatusGone)
return
}
// Check password
if rec.PasswordHash != nil {
cookie, err := r.Cookie("auth_" + id)
if err != nil || cookie.Value != "granted" {
http.Redirect(w, r, "/f/"+id+"/auth", http.StatusSeeOther)
return
}
}
// Determine preview type and generate presigned URL for inline rendering
thumbType := "" // "image", "video", "audio", "pdf", or ""
previewURL := ""
if strings.HasPrefix(rec.ContentType, "image/") {
thumbType = "image"
} else if strings.HasPrefix(rec.ContentType, "video/") {
thumbType = "video"
} else if strings.HasPrefix(rec.ContentType, "audio/") {
thumbType = "audio"
} else if rec.ContentType == "application/pdf" {
thumbType = "pdf"
}
if thumbType != "" {
if url, err := h.r2.PresignGet(r.Context(), rec.R2Key, rec.Filename, true); err == nil {
previewURL = url
}
}
data := map[string]any{
"ID": rec.ID,
"Filename": rec.Filename,
"Size": rec.SizeBytes,
"ContentType": rec.ContentType,
"UploadedAt": rec.UploadedAt.Format("Jan 2, 2006 at 3:04 PM"),
"Downloads": rec.DownloadCount,
"Previewable": isPreviewable(rec.ContentType),
"ViewURL": "/f/" + id + "/view",
"DownloadURL": "/f/" + id + "/dl",
"ThumbType": thumbType,
"PreviewURL": previewURL,
}
if rec.ExpiresAt != nil {
data["ExpiresAt"] = rec.ExpiresAt.Format("Jan 2, 2006 at 3:04 PM")
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
downloadTmpl.Execute(w, data)
}
// DirectDownload generates a presigned URL and redirects (attachment disposition).
func (h *Handler) DirectDownload(w http.ResponseWriter, r *http.Request) {
h.serveFile(w, r, false)
}
// ViewFile generates a presigned URL and redirects (inline disposition).
func (h *Handler) ViewFile(w http.ResponseWriter, r *http.Request) {
h.serveFile(w, r, true)
}
func (h *Handler) serveFile(w http.ResponseWriter, r *http.Request, inline bool) {
id := r.PathValue("id")
if id == "" {
http.NotFound(w, r)
return
}
rec, err := h.store.Get(id)
if err == sql.ErrNoRows {
http.NotFound(w, r)
return
}
if err != nil {
log.Printf("db get error: %v", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
if rec.ExpiresAt != nil && rec.ExpiresAt.Before(time.Now().UTC()) {
http.Error(w, "this file has expired", http.StatusGone)
return
}
if rec.PasswordHash != nil {
cookie, err := r.Cookie("auth_" + id)
if err != nil || cookie.Value != "granted" {
http.Redirect(w, r, "/f/"+id+"/auth", http.StatusSeeOther)
return
}
}
url, err := h.r2.PresignGet(r.Context(), rec.R2Key, rec.Filename, inline)
if err != nil {
log.Printf("presign error: %v", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
h.store.IncrementDownloads(id)
http.Redirect(w, r, url, http.StatusFound)
}