package server import ( "io" "net/http" "os" "strings" "github.com/gin-gonic/gin" "warpbox/lib/boxstore" "warpbox/lib/helpers" "warpbox/lib/models" ) func (app *App) handleCreateBox(ctx *gin.Context) { if !app.requireAPI(ctx) || !app.requireGuestUploads(ctx) { return } app.limitRequestBody(ctx) boxID, err := boxstore.NewBoxID() if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not create upload box"}) return } if err := os.MkdirAll(boxstore.BoxPath(boxID), 0755); err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not prepare upload box"}) return } var request models.CreateBoxRequest if err := ctx.ShouldBindJSON(&request); err != nil && err != io.EOF { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid box payload"}) return } if err := app.validateCreateBoxRequest(&request); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } files, err := boxstore.CreateManifest(boxID, request) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ctx.JSON(http.StatusOK, gin.H{"box_id": boxID, "box_url": "/box/" + boxID, "files": files}) } func (app *App) handleManifestFileUpload(ctx *gin.Context) { if !app.requireAPI(ctx) || !app.requireGuestUploads(ctx) { return } app.limitRequestBody(ctx) boxID := ctx.Param("id") fileID := ctx.Param("file_id") if !boxstore.ValidBoxID(boxID) { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid box id"}) return } file, err := ctx.FormFile("file") if err != nil { boxstore.MarkFileStatus(boxID, fileID, models.FileStatusFailed) ctx.JSON(http.StatusBadRequest, gin.H{"error": "No file received"}) return } if err := app.validateManifestFileUpload(boxID, fileID, file.Size); err != nil { boxstore.MarkFileStatus(boxID, fileID, models.FileStatusFailed) ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } savedFile, err := boxstore.SaveManifestUpload(boxID, fileID, file) if err != nil { boxstore.MarkFileStatus(boxID, fileID, models.FileStatusFailed) ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ctx.JSON(http.StatusOK, gin.H{"box_id": boxID, "box_url": "/box/" + boxID, "file": savedFile}) } func (app *App) handleFileStatusUpdate(ctx *gin.Context) { if !app.requireAPI(ctx) { return } app.limitRequestBody(ctx) boxID := ctx.Param("id") fileID := ctx.Param("file_id") if !boxstore.ValidBoxID(boxID) || !helpers.ValidLowerHexID(fileID, 16) { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid file"}) return } var request models.UpdateFileStatusRequest if err := ctx.ShouldBindJSON(&request); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid status payload"}) return } if request.Status == models.FileStatusReady { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Uploads must complete through the upload endpoint"}) return } if err := app.rejectExpiredManifestBox(boxID); err != nil { ctx.JSON(http.StatusGone, gin.H{"error": err.Error()}) return } file, err := boxstore.MarkFileStatus(boxID, fileID, request.Status) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ctx.JSON(http.StatusOK, gin.H{"file": file}) } func (app *App) handleDirectBoxUpload(ctx *gin.Context) { if !app.requireAPI(ctx) || !app.requireGuestUploads(ctx) { return } app.limitRequestBody(ctx) boxID := ctx.Param("id") if !boxstore.ValidBoxID(boxID) { ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid box id"}) return } file, err := ctx.FormFile("file") if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": "No file received"}) return } if err := app.validateIncomingFile(boxID, file.Size); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } savedFile, err := boxstore.SaveUpload(boxID, file) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ctx.JSON(http.StatusOK, gin.H{"box_id": boxID, "box_url": "/box/" + boxID, "file": savedFile}) } func (app *App) handleLegacyUpload(ctx *gin.Context) { if !app.requireAPI(ctx) || !app.requireGuestUploads(ctx) { return } app.limitRequestBody(ctx) form, err := ctx.MultipartForm() if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": "No files received"}) return } files := form.File["files"] if len(files) == 0 { ctx.JSON(http.StatusBadRequest, gin.H{"error": "No files received"}) return } totalSize := int64(0) for _, file := range files { if err := app.validateFileSize(file.Size); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } totalSize += file.Size } if err := app.validateBoxSize(totalSize); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } boxID, err := boxstore.NewBoxID() if err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not create upload box"}) return } if err := os.MkdirAll(boxstore.BoxPath(boxID), 0755); err != nil { ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Could not prepare upload box"}) return } retentionKey := strings.TrimSpace(ctx.PostForm("retention_key")) if retentionKey == "" { retentionKey = strings.TrimSpace(ctx.PostForm("retention")) } allowZip := true if strings.EqualFold(strings.TrimSpace(ctx.PostForm("allow_zip")), "false") { allowZip = false } request := models.CreateBoxRequest{ RetentionKey: retentionKey, Password: ctx.PostForm("password"), AllowZip: &allowZip, Files: make([]models.CreateBoxFileRequest, 0, len(files)), } for _, file := range files { request.Files = append(request.Files, models.CreateBoxFileRequest{Name: file.Filename, Size: file.Size}) } if err := app.validateCreateBoxRequest(&request); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } manifestFiles, err := boxstore.CreateManifest(boxID, request) if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } savedFiles := make([]models.BoxFile, 0, len(files)) for index, file := range files { savedFile, err := boxstore.SaveManifestUpload(boxID, manifestFiles[index].ID, file) if err != nil { _, _ = boxstore.MarkFileStatus(boxID, manifestFiles[index].ID, models.FileStatusFailed) ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } savedFiles = append(savedFiles, savedFile) } ctx.JSON(http.StatusOK, gin.H{"box_id": boxID, "box_url": "/box/" + boxID, "files": savedFiles}) }