Files
go_blog/handlers/upload_validator.go
T
kevin f307781f58 docs: 全部 Go 代码注释汉化
- 48 个 Go 文件所有注释(行注释/块注释/行尾注释,含 _test.go)翻译为中文
- 保留技术标识符:SECURITY_TODO(n)、unsafe-inline、sqlite/mysql、路由参数等
- 代码、字符串字面量、日志消息保持英文原文,零逻辑改动
- go build/vet 通过,go test -count=1 ./... 全绿
2026-08-27 19:03:03 +08:00

103 lines
2.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}