package handlers import ( "errors" "fmt" "mime/multipart" "path/filepath" "strings" "github.com/gabriel-vasile/mimetype" "go_blog/models" ) // ErrUploadsDisabled 在全局上传开关关闭时返回。 var ErrUploadsDisabled = errors.New("uploads are disabled") // FileValidationError 描述上传文件被拒绝的原因。 type FileValidationError struct { Reason string } func (e *FileValidationError) Error() string { return e.Reason } // FileCheck 是验证上传文件头的结果。 type FileCheck struct { OK bool Type *models.UploadFileType // 匹配的类型,未匹配时为 nil MaxSize int64 // 生效的字节限制 } // ValidateUpload 依据缓存的平台上传策略校验文件头: // 总开关、扩展名白名单以及按类型的单文件大小限制。 // 报告的 MaxSize 是生效限制(按类型覆盖,否则使用默认值)。 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} } } // 扩展名不在白名单内。 return FileCheck{OK: false, MaxSize: def} } // formatSize 将字节数渲染为人类可读的字符串。 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]) } // contentMatchesType 将上传字节的魔数与匹配扩展名配置的 MIME 类型比对 // (SECURITY_TODO #14)。它有意保持宽松:空/未知的 MIME 策略和无法识别的 // 内容均可通过(扩展名白名单仍是主要关卡);声称是 .txt 却携带 PNG 字节 // 的文件会被拒绝。 func contentMatchesType(t *models.UploadFileType, content []byte) bool { if t == nil || len(content) == 0 { return true } expected := strings.ToLower(strings.TrimSpace(t.MimeType)) if expected == "" || expected == "application/octet-stream" { // 无具体策略,或管理员明确允许任意二进制内容。 return true } // 去除管理员可能复制过来的 charset 参数。 if i := strings.Index(expected, ";"); i >= 0 { expected = strings.TrimSpace(expected[:i]) } if expected == "" { return true } det := mimetype.Detect(content) if det == nil || det.String() == "" { // 内容无法识别(如特殊的 Unicode 文本);仅由头部策略把关。 return true } return det.Is(expected) }