err := r.ParseMultipartForm(32 << 20) // 32Mb
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
文件上传一般会采用 POST multipart/form-data
的形式,处理这类请求要调用 r.ParseMultipartForm
,无论是显式调用,还是在 r.FormFile
里面的隐式调用。
那 32Mb 是对文件上传大小的限制吗?不是,上传的文件们按顺序存入内存中,累加大小不得超出 32Mb ,最后累加超出的文件就存入系统的临时文件中。非文件字段部分不计入累加。所以这种情况,文件上传是没有任何限制的。
r.Body = http.MaxBytesReader(w, r.Body, 32<<20+512)
...
通过上面代码,可以把 POST Body 整体限制在 32.5Mb,否则就会返回 http: request body too large
的错误。从而可以防止一些恶意的大文件上传。
但如果想做到更精细的控制,比如:文件大小的限制、文件类型的限制等,r.ParseMultipartForm
就无能为力了。我打算做如下校验:
- 文件类型校验
- 文件大小校验
- 字段白名单
- 一旦校验失败,立即停止解析
var filesMax int64 = 4 << 20
var valuesMax int64 = 512
// 整体限制 4.5Mb
r.Body = http.MaxBytesReader(w, r.Body, filesMax+valuesMax)
reader, err := r.MultipartReader()
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// 白名单
files := map[string][]byte{"file": nil}
values := map[string]string{"text": ""}
for {
part, err := reader.NextPart()
if err == io.EOF {
break
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var buf bytes.Buffer
filename := part.FileName()
name := part.FormName()
// 非文件字段
if filename == "" {
if _, ok := values[name]; !ok {
http.Error(w, name+" is not expected", http.StatusBadRequest)
return
}
n, err := io.CopyN(&buf, part, valuesMax+1)
if err != nil && err != io.EOF {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
valuesMax -= n
if valuesMax < 0 {
http.Error(w, "multipart: message too large", http.StatusBadRequest)
return
}
values[name] = buf.String()
continue
}
// 文件字段
if _, ok := files[name]; !ok {
http.Error(w, name+" is not expected", http.StatusBadRequest)
return
}
n, err := io.CopyN(&buf, part, filesMax+1)
if err != nil && err != io.EOF {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
filesMax -= n
if filesMax < 0 {
http.Error(w, "file size over limit", http.StatusBadRequest)
return
}
files[name] = buf.Bytes()
contentType := http.DetectContentType(files[name])
if contentType != "application/zip" {
http.Error(w, "file type not allowed", http.StatusBadRequest)
return
}
}
上面代码主要做了这样几个事:
- 只处理
text
字段和file
文件字段 - 限定文件大小 4Mb、限定普通字段 512b
- 限定文件类型为
application/zip
- 一旦有校验错误立即返回错误
有疑问加站长微信联系(非本文作者)