50

如何限制文件上传的大小

 4 years ago
source link: https://www.tuicool.com/articles/qmeUneM
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

如何限制文件上传的大小

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
  • 一旦有校验错误立即返回错误

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK