在Go语言中实现表单文件上传限制,需从文件大小、类型、数量三个核心维度进行控制,并结合安全校验措施。以下是具体实现方案:
一、限制文件大小通过r.ParseMultipartForm(maxMemory)设置内存缓存阈值,超出部分写入临时文件。需双重校验:
- 内存+磁盘总限制:解析时设置内存上限(如10MB),整体请求不超过20MB。
err := r.ParseMultipartForm(10 << 20) // 内存中最多10MBif err != nil { if err == http.ErrContentLengthExceeded { http.Error(w, "上传文件过大", http.StatusBadRequest) return } http.Error(w, "解析表单失败", http.StatusInternalServerError) return}- 单个文件大小校验:从handler.Size获取实际大小。
file, handler, err := r.FormFile("uploadFile")if err != nil { http.Error(w, "获取文件失败", http.StatusBadRequest) return}if handler.Size > 20<<20 { // 20MB http.Error(w, "文件不能超过20MB", http.StatusBadRequest) return}二、限制文件类型(MIME检测)避免依赖扩展名,通过读取文件前512字节检测真实类型:
- 读取文件头部数据。
- 使用http.DetectContentType推断类型。
- 比对允许的类型列表(如仅允许图片)。
fileBytes := make([]byte, 512)_, err = file.Read(fileBytes)if err != nil { http.Error(w, "读取文件出错", http.StatusInternalServerError) return}contentType := http.DetectContentType(fileBytes)allowedTypes := map[string]bool{ "image/jpeg": true, "image/png": true, "image/gif": true,}if !allowedTypes[contentType] { http.Error(w, "不支持的文件类型", http.StatusBadRequest) return}file.Seek(0, 0) // 重置文件指针三、限制上传文件数量通过r.MultipartForm.File字段长度控制:
- 获取文件列表并计数。
- 超过阈值(如5个)时拒绝请求。
files := r.MultipartForm.File["uploadFiles"] // 多文件字段名if len(files) > 5 { http.Error(w, "最多上传5个文件", http.StatusBadRequest) return}// 循环校验每个文件的大小和类型for _, fileHeader := range files { file, err := fileHeader.Open() // ...校验逻辑同单文件}四、其他安全建议- Content-Length预校验:在解析表单前检查请求头,快速拒绝明显超限的请求(需注意客户端可能伪造)。contentLength := r.ContentLengthif contentLength > 20<<20 { http.Error(w, "请求体过大", http.StatusBadRequest) return}
- 随机文件名:使用uuid或crypto/rand生成随机文件名,防止路径遍历攻击。import "github.com/google/uuid"newFilename := uuid.New().String() + filepath.Ext(handler.Filename)
- 图像有效性验证:对图片文件调用image.DecodeConfig确认是否为有效图像。import "image"_, _, err := image.DecodeConfig(file)if err != nil { http.Error(w, "无效的图像文件", http.StatusBadRequest) return}
五、完整示例代码func uploadHandler(w http.ResponseWriter, r *http.Request) { // 1. 限制总大小(内存10MB,整体20MB) err := r.ParseMultipartForm(10 << 20) if err != nil { if err == http.ErrContentLengthExceeded { http.Error(w, "上传文件过大", http.StatusBadRequest) return } http.Error(w, "解析表单失败", http.StatusInternalServerError) return } // 2. 限制文件数量(单文件示例) file, handler, err := r.FormFile("uploadFile") if err != nil { http.Error(w, "获取文件失败", http.StatusBadRequest) return } defer file.Close() // 3. 校验文件大小 if handler.Size > 20<<20 { http.Error(w, "文件不能超过20MB", http.StatusBadRequest) return } // 4. 校验MIME类型 fileBytes := make([]byte, 512) _, err = file.Read(fileBytes) if err != nil { http.Error(w, "读取文件出错", http.StatusInternalServerError) return } contentType := http.DetectContentType(fileBytes) allowedTypes := map[string]bool{ "image/jpeg": true, "image/png": true, } if !allowedTypes[contentType] { http.Error(w, "不支持的文件类型", http.StatusBadRequest) return } file.Seek(0, 0) // 5. 保存文件(使用随机文件名) dstPath := "/tmp/" + uuid.New().String() + filepath.Ext(handler.Filename) dst, err := os.Create(dstPath) if err != nil { http.Error(w, "创建文件失败", http.StatusInternalServerError) return } defer dst.Close() io.Copy(dst, file) w.Write([]byte("上传成功"))}关键点总结- 大小限制:结合ParseMultipartForm内存阈值和handler.Size校验。
- 类型安全:通过文件头检测MIME类型,而非依赖扩展名。
- 数量控制:遍历MultipartForm.File字段计数。
- 防御深化:随机文件名、图像头解析、Content-Length预检。
通过组合上述措施,可有效防御大文件攻击、恶意文件上传和资源耗尽风险。