package handlers import ( "errors" "fmt" "mime/multipart" "path/filepath" "strings" "go_blog/models" ) // ErrUploadsDisabled is returned when the global upload switch is off. var ErrUploadsDisabled = errors.New("uploads are disabled") // FileValidationError describes why an uploaded file was rejected. type FileValidationError struct { Reason string } func (e *FileValidationError) Error() string { return e.Reason } // FileCheck is the outcome of validating an uploaded file header. type FileCheck struct { OK bool Type *models.UploadFileType // matched type, nil if not found MaxSize int64 // effective byte limit applied } // ValidateUpload checks a file header against the cached platform upload // policy: master switch, extension whitelist, and per-type size limit. The // reported MaxSize is the effective limit (per-type override, else default). func ValidateUpload(header *multipart.FileHeader) FileCheck { cfg := models.GetUploadConfig() if !cfg.Enabled { return FileCheck{OK: false} } ext := strings.ToLower(filepath.Ext(header.Filename)) def := cfg.DefaultMaxSize for i := range models.GetUploadFileTypes() { t := &models.GetUploadFileTypes()[i] if !t.Enabled { continue } if strings.EqualFold(t.Extension, ext) { max := t.EffectiveMaxSize(def) if header.Size > max { return FileCheck{OK: false, Type: t, MaxSize: max} } return FileCheck{OK: true, Type: t, MaxSize: max} } } // Extension not in the whitelist. return FileCheck{OK: false, MaxSize: def} } // formatSize renders a byte count as a human-readable string. func formatSize(b int64) string { const unit = 1024 if b < unit { return fmt.Sprintf("%d B", b) } div, exp := int64(unit), 0 for n := b / unit; n >= unit; n /= unit { div *= unit exp++ } return fmt.Sprintf("%.1f %ciB", float64(b)/float64(div), "KMGTPE"[exp]) }