不能直接信任c.formfile返回的filename和size字段:filename由客户端控制,需用filepath.base截取并过滤非法字符;size仅是客户端声明值,须通过io.copyn校验实际可读字节数,且必须判空file避免panic。

c.FormFile("file") 返回的 *multipart.FileHeader 结构体里,Filename 和 Size 字段就是原始文件名和大小——但直接用它们有严重风险,必须处理边界情况。
为什么不能直接信任 file.Filename
它由客户端完全控制,可能含 ../、空字节、Unicode 零宽字符等,用于路径遍历或绕过校验。比如传 ../../../etc/passwd 或 avatar.jpg%00.php,不做清理就拼进 os.Create() 会出安全问题。
- 永远用
filepath.Base(file.Filename)截掉路径部分 - 再用正则或白名单过滤非法字符(如只保留字母、数字、下划线、点)
- 别依赖
file.Header.Get("Content-Type"),浏览器可伪造
file.Size 是准确的,但仅反映客户端声称的大小
file.Size 来自 multipart boundary 解析时读到的 Content-Length 或 size 字段,不是实际读取后的校验值。攻击者可伪造一个超大 Size 值(比如 2GB),但实际只传几个字节,导致后端误判。
- 真正可靠的方式是:调用
file.Open()后用io.CopyN(io.Discard, src, file.Size)校验是否真能读满 - 若提前设置了
router.MaxMultipartMemory,Gin 会在解析阶段拒绝超限上传,此时file.Size不会超过该阈值 - 对大文件分片场景,
file.Size只表示当前分块大小,不是原始文件总大小
前端没传文件时 c.FormFile 返回 nil,别忽略这个判断
常见错误是写 file, _ := c.FormFile("file") 然后直接访问 file.Filename,一旦前端字段名错、编码不对或压根没传,file 就是 nil,触发 panic。
- 必须显式判空:
if file == nil { c.AbortWithStatusJSON(400, gin.H{"error": "no file provided"}) } - 检查表单是否用了
enctype="multipart/form-data" - 用
c.MultipartForm()打日志看files.File里有没有对应 key,确认是不是前端根本没发过来
真正麻烦的不是拿到这两个字段,而是拿到之后怎么安全地用——Filename 要清洗,Size 要验证,且两者都不能脱离 file.Open() 的上下文单独使用。











