必须用 file.Header.Header.Get("Content-Type") 获取文件真实 MIME 类型,并调用 http.DetectContentType 检测前 512 字节内容,二者缺一不可;c.Request.Header.Get("Content-Type") 返回的是 multipart 顶层类型,与文件类型无关。

用 c.FormFile 后立即读 Header.Header.Get("Content-Type")
c.FormFile 只返回 multipart.FileHeader,它本身不解析或校验 MIME 类型。真正的原始请求头藏在 file.Header.Header 里——注意是双 Header:第一个是 file.Header(类型为 *multipart.Header),第二个才是标准的 http.Header 映射。
常见错误是直接信 file.Header.Filename 推后缀,或跳过这步直接保存。但浏览器可随意伪造 Content-Type,比如把一个 .exe 改成 image/png 就能绕过第一道检查。
所以必须这样写:
file, err := c.FormFile("file")
if err != nil {
c.AbortWithStatusJSON(400, gin.H{"error": "no file provided"})
return
}
contentType := file.Header.Header.Get("Content-Type")
if contentType == "" || !strings.HasPrefix(contentType, "image/") {
c.AbortWithStatusJSON(400, gin.H{"error": "invalid content-type"})
return
}
必须用 http.DetectContentType 对前 512 字节做二次探测
Content-Type 头可被客户端任意篡改,仅靠它判断类型等于裸奔。Gin 不会自动帮你 sniff 文件内容,得手动读取文件流前段并调用标准库函数:
- 调用
file.Open()得到multipart.File(本质是io.ReadSeeker) - 用
io.ReadFull或io.LimitReader读前 512 字节到字节数组 - 传给
http.DetectContentType,它返回真实探测出的 MIME 字符串
如果返回的字符串不是 image/ 开头(如 application/x-executable),立刻拒绝。
注意:别用整个文件做 DetectContentType,太慢且没必要;也别漏掉 defer f.Close(),否则句柄泄漏。
不要混淆 c.Request.Header.Get("Content-Type") 和文件头
c.Request.Header.Get("Content-Type") 拿到的是整个 multipart 请求的顶层类型,通常是 multipart/form-data; boundary=----...,跟上传文件本身的类型完全无关。新手常在这里踩坑,拿错对象去判断图片类型。
正确路径只有一条:file, _ := c.FormFile("file") → file.Header.Header.Get("Content-Type") → 打开文件 → http.DetectContentType(前512字节)
其他任何组合都不可靠。
curl 测试时千万别加 -H "Content-Type: application/json"
这是高频误操作。一旦手动设置顶层 Content-Type 为 application/json,curl 就不会生成 multipart boundary,Gin 的 c.FormFile 必然返回 nil 或 panic。
正确 curl 命令只有这一种写法:
curl -X POST http://localhost:8080/upload \ -F "file=@/path/to/photo.jpg"
curl 会自动设置 Content-Type: multipart/form-data 并附带合法 boundary。加任何 -H "Content-Type: ..." 都会破坏 multipart 结构。
实际部署中,前端用 FormData.append("file", input.files[0]) 也是同理:浏览器自动生成 multipart,无需、也不该手动设 header。
文件类型校验链条里,http.DetectContentType 是最后一道防线,但它依赖你主动触发——没人替你读那 512 字节。











