package server import ( "fmt" "net/http" "strconv" "strings" "github.com/gin-gonic/gin" "warpbox/lib/boxstore" "warpbox/lib/models" ) func (app *App) requireAPI(ctx *gin.Context) bool { if app.config.APIEnabled { return true } ctx.JSON(http.StatusForbidden, gin.H{"error": "API access is disabled"}) return false } func (app *App) requireGuestUploads(ctx *gin.Context) bool { if app.config.GuestUploadsEnabled { return true } ctx.JSON(http.StatusForbidden, gin.H{"error": "Guest uploads are disabled"}) return false } func (app *App) validateCreateBoxRequest(request *models.CreateBoxRequest) error { return app.validateCreateBoxRequestForActor(request, nil) } func (app *App) validateCreateBoxRequestForActor(request *models.CreateBoxRequest, actor *requestActor) error { if request == nil { return nil } if !app.retentionAllowed(request.RetentionKey) { return fmt.Errorf("Retention option is not allowed") } if !app.config.ZipDownloadsEnabled { allowZip := false request.AllowZip = &allowZip } if strings.TrimSpace(request.RetentionKey) == boxstore.OneTimeDownloadRetentionKey && !app.config.OneTimeDownloadsEnabled { return fmt.Errorf("One-time downloads are disabled") } totalSize := int64(0) for _, file := range request.Files { if err := app.validateFileSizeForActor(file.Size, actor); err != nil { return err } totalSize += file.Size } return app.validateBoxSizeForActor(totalSize, actor) } func (app *App) validateIncomingFile(boxID string, size int64) error { return app.validateIncomingFileForActor(boxID, size, nil) } func (app *App) validateIncomingFileForActor(boxID string, size int64, actor *requestActor) error { if err := app.validateFileSizeForActor(size, actor); err != nil { return err } if app.effectiveMaxBoxBytes(actor) <= 0 { return nil } files, err := boxstore.ListFiles(boxID) if err != nil { return nil } totalSize := size for _, file := range files { totalSize += file.Size } return app.validateBoxSizeForActor(totalSize, actor) } func (app *App) validateManifestFileUpload(boxID string, fileID string, size int64) error { return app.validateManifestFileUploadForActor(boxID, fileID, size, nil) } func (app *App) validateManifestFileUploadForActor(boxID string, fileID string, size int64, actor *requestActor) error { if err := app.validateFileSizeForActor(size, actor); err != nil { return err } manifest, err := boxstore.ReadManifest(boxID) if err != nil { return app.validateIncomingFileForActor(boxID, size, actor) } if boxstore.IsExpired(manifest) { _ = boxstore.DeleteBox(boxID) return fmt.Errorf("Box expired") } if app.effectiveMaxBoxBytes(actor) <= 0 { return nil } totalSize := int64(0) found := false for _, file := range manifest.Files { if file.ID == fileID { totalSize += size found = true continue } totalSize += file.Size } if !found { totalSize += size } return app.validateBoxSizeForActor(totalSize, actor) } func (app *App) validateFileSize(size int64) error { return app.validateFileSizeForActor(size, nil) } func (app *App) effectiveMaxFileBytes(actor *requestActor) int64 { if actor == nil { return app.config.GlobalMaxFileSizeBytes } return actor.User.Limits.MaxFileSizeBytes } func (app *App) effectiveMaxBoxBytes(actor *requestActor) int64 { if actor == nil { return app.config.GlobalMaxBoxSizeBytes } return actor.User.Limits.MaxBoxSizeBytes } func (app *App) validateFileSizeForActor(size int64, actor *requestActor) error { if size < 0 { return fmt.Errorf("File size cannot be negative") } limit := app.effectiveMaxFileBytes(actor) if limit > 0 && size > limit { if actor != nil { return fmt.Errorf("File exceeds this account's max file size") } return fmt.Errorf("File exceeds the global max file size") } return nil } func (app *App) validateBoxSize(size int64) error { return app.validateBoxSizeForActor(size, nil) } func (app *App) validateBoxSizeForActor(size int64, actor *requestActor) error { if size < 0 { return fmt.Errorf("Box size cannot be negative") } limit := app.effectiveMaxBoxBytes(actor) if limit > 0 && size > limit { if actor != nil { return fmt.Errorf("Box exceeds this account's max box size") } return fmt.Errorf("Box exceeds the global max box size") } return nil } func (app *App) rejectExpiredManifestBox(boxID string) error { manifest, err := boxstore.ReadManifest(boxID) if err != nil { return nil } if !boxstore.IsExpired(manifest) { return nil } _ = boxstore.DeleteBox(boxID) return fmt.Errorf("Box expired") } func (app *App) limitRequestBody(ctx *gin.Context) { app.limitRequestBodyForActor(ctx, nil) } func (app *App) limitRequestBodyForActor(ctx *gin.Context, actor *requestActor) { limit := app.maxRequestBodyBytesForActor(actor) if limit <= 0 { return } ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, limit) } func (app *App) maxRequestBodyBytes() int64 { return app.maxRequestBodyBytesForActor(nil) } func (app *App) maxRequestBodyBytesForActor(actor *requestActor) int64 { limit := app.effectiveMaxBoxBytes(actor) fileLimit := app.effectiveMaxFileBytes(actor) if limit <= 0 || fileLimit > limit { limit = fileLimit } if limit <= 0 { return 0 } return limit + 10*1024*1024 } func (app *App) enforceUploadRateLimit(ctx *gin.Context, size int64) bool { if !app.securityFeaturesEnabled() || app.securityGuard == nil { return true } ip := app.clientIP(ctx) if app.securityGuard.IsWhitelisted(ip) || app.securityGuard.IsAdminWhitelisted(ip) { return true } allowed, requestCount, totalBytes := app.securityGuard.AllowUpload( ip, size, app.config.SecurityUploadWindowSeconds, app.config.SecurityUploadMaxRequests, app.config.SecurityUploadMaxBytes, ) if allowed { return true } app.logActivity("security.upload_limit", "high", "Upload rate limit exceeded", ctx, map[string]string{ "requests": strconv.Itoa(requestCount), "bytes": strconv.FormatInt(totalBytes, 10), }) app.createAlert( "Upload rate limit triggered", "medium", "security", "430", "security.upload.rate_limit", "Per-IP upload rate limit blocked request.", map[string]string{"ip": ip, "requests": strconv.Itoa(requestCount)}, ) ctx.JSON(http.StatusTooManyRequests, gin.H{"error": "Too many uploads from this IP. Try again later."}) return false }