header.get("content-type") 对文件上传不可靠,因 multipart/form-data 请求的顶层 content-type 仅为 boundary 标识,与文件真实类型无关;须用 c.formfile() 获取文件后读取前512字节,通过 http.detectcontenttype() 基于 magic number 检测真实 mime 类型。

为什么 Header.Get("Content-Type") 对文件上传不可靠
在 Gin 中,客户端上传文件时,HTTP 请求体是 multipart/form-data 格式,整个请求的顶层 Content-Type 是类似 multipart/form-data; boundary=----WebKitFormBoundary... 的值,和具体上传的文件内容类型完全无关。直接读取请求头的 Content-Type 拿到的永远是这个 multipart 边界标识,不是图片、PDF 或 Excel 的真实类型。
真正需要的是文件**实际内容**推导出的 MIME 类型,必须从文件字节流中检测。
用 c.FormFile() 获取文件并读取前几百字节做 magic number 检测
Gin 提供 c.FormFile("file") 获取上传的 *multipart.FileHeader,但它只包含文件名、大小、Header(含客户端声称的 Content-Type,可被伪造),不包含内容。必须手动打开文件流并读取开头部分字节(通常 512 字节足够)来比对 magic number。
- 调用
fileHeader.Open()得到multipart.File(本质是io.ReadCloser) - 用
io.LimitReader或io.ReadFull读取前 512 字节到[]byte - 用标准库
net/http.DetectContentType()检测 —— 它基于 magic number,不依赖扩展名或 Header - 务必
defer file.Close(),否则句柄泄漏
fileHeader, err := c.FormFile("file")
if err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": "no file provided"})
return
}
file, err := fileHeader.Open()
if err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": "cannot open file"})
return
}
defer file.Close()
buf := make([]byte, 512)
_, _ = file.Read(buf) // 忽略 error,DetectContentType 能处理不完整读取
realType := http.DetectContentType(buf)
// realType 示例: "image/jpeg", "application/pdf", "text/plain; charset=utf-8"
注意 fileHeader.Header.Get("Content-Type") 是客户端伪造的
浏览器或任意 HTTP 客户端可在表单字段中随意设置 Content-Type,例如把一个 .exe 文件标为 image/png。Gin 的 fileHeader.Header 就是解析这部分得到的,完全不可信。
- 它来自 multipart boundary 内部的
Content-Type: ...行,非服务端检测结果 - 仅适合做快速初筛(比如拒绝明显非法值如空字符串或
application/octet-stream),但不能作为鉴权或存储依据 - 真实业务中,若需严格校验(如只允许 JPG/PNG),必须结合
http.DetectContentType()+ 白名单判断
大文件上传时避免内存爆炸:不要用 c.SaveUploadedFile() 再读一次
常见错误是先调用 c.SaveUploadedFile() 把文件落地,再用 os.ReadFile() 读取全量内容检测类型——这对 100MB 文件意味着两遍磁盘 IO 和一次完整内存加载。
正确做法是:在 fileHeader.Open() 后立即读取前 512 字节检测,确认类型合法后再决定是否保存(或流式处理)。如果后续还需完整内容(如转存、压缩),可复用同一个 file 句柄,seek 回头或重新打开均可。
另外,Gin 默认限制单个请求体最大 32MB,若需上传更大文件,必须提前设置 gin.SetMode(gin.ReleaseMode); r.MaxMultipartMemory = 128 (单位字节)。











