Files
ReSendit/internal/file/handlers.go
2026-03-21 20:02:00 +01:00

218 lines
4.5 KiB
Go

package file
import (
"fmt"
"net/http"
"path/filepath"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
type Handler struct {
service *Service
}
func NewHandler(s *Service) *Handler {
return &Handler{service: s}
}
func (h *Handler) Upload(c *gin.Context) {
err := c.Request.ParseMultipartForm(0)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "missing file"})
return
}
f, err := file.Open()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "cannot open file"})
return
}
defer f.Close()
once := c.PostForm("once") == "true"
durationStr := c.PostForm("duration")
hours, err := strconv.Atoi(durationStr)
if err != nil || hours <= 0 {
hours = 24 // default
}
duration := time.Duration(hours) * time.Hour
record, err := h.service.UploadFile(
file.Filename,
f,
once,
duration,
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"id": record.ID,
"deletion_id": record.DeletionID,
"filename": record.Filename,
"size": record.Size,
"expires_at": record.ExpiresAt,
"view_key": record.ViewID,
})
}
func (h *Handler) View(c *gin.Context) {
id := c.Param("id")
record, err := h.service.DownloadFile(id)
if err != nil {
c.HTML(http.StatusOK, "fileNotFound.html", nil)
return
}
c.Header("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, record.Filename))
c.Header("X-Content-Type-Options", "nosniff")
c.File(record.Path)
}
func safeFilename(name string) string {
// keep it simple: drop control chars and quotes
out := make([]rune, 0, len(name))
for _, r := range name {
if r < 32 || r == 127 || r == '"' || r == '\\' {
continue
}
out = append(out, r)
}
if len(out) == 0 {
return "file"
}
return string(out)
}
func isXSSRisk(filename string) bool {
ext := filepath.Ext(filename)
switch ext {
case ".html", ".htm", ".js", ".css", ".svg":
return true
default:
return false
}
}
func (h *Handler) Download(c *gin.Context) {
id := c.Param("id")
record, err := h.service.DownloadFile(id)
if err != nil {
c.HTML(http.StatusOK, "fileNotFound.html", nil)
return
}
c.Header("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, record.Filename))
c.Header("X-Content-Type-Options", "nosniff")
//c.Header("Content-Security-Policy", "default-src 'none'; img-src 'self'; media-src 'self'; script-src 'none'; style-src 'none';")
//c.Header("Content-Type", "application/octet-stream")
c.File(record.Path)
}
func (h *Handler) Delete(c *gin.Context) {
id := c.Param("del_id")
_, err := h.service.DeleteFileByDeletionID(id)
if err != nil {
c.HTML(http.StatusOK, "fileNotFound.html", nil)
return
}
//c.JSON(http.StatusOK, gin.H{"status": "deleted"})
c.HTML(http.StatusOK, "deleted.html", nil)
}
func (h *Handler) AdminList(c *gin.Context) {
records, err := h.service.repo.GetAll()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, records)
}
func (h *Handler) AdminGet(c *gin.Context) {
id := c.Param("id")
record, err := h.service.repo.GetByID(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
return
}
c.File(record.Path)
}
func (h *Handler) AdminDelete(c *gin.Context) {
id := c.Param("id")
_, err := h.service.DeleteFileByID(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
return
}
c.Redirect(301, "/admin")
}
func (h *Handler) AdminForceDelete(c *gin.Context) {
id := c.Param("id")
_, err := h.service.GetFileByID(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "file not found"})
return
}
if _, err := h.service.ForceDelete(id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Redirect(301, "/admin")
}
func (h *Handler) Import(c *gin.Context) {
var records []ImportFileRecord
if err := c.ShouldBindJSON(&records); err != nil {
c.JSON(400, gin.H{"error": "invalid JSON"})
return
}
if err := h.service.ImportFiles(records); err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{
"imported": len(records),
})
}
func (h *Handler) Export(c *gin.Context) {
records, err := h.service.GetAllFiles()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, records)
}