File Uploads and Downloads
File Uploads and Downloads
Overview
Uploads and downloads stress memory, disk, and trust boundaries. Stream, bound sizes, and never trust client filenames.
Upload (multipart)
func upload(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 32<<20) // 32MiB
if err := r.ParseMultipartForm(8 << 20); err != nil {
http.Error(w, "too large or bad multipart", http.StatusBadRequest)
return
}
file, hdr, err := r.FormFile("file")
if err != nil {
http.Error(w, "missing file", http.StatusBadRequest)
return
}
defer file.Close()
name := filepath.Base(hdr.Filename) // strip paths
dstPath := filepath.Join(uploadDir, name)
// better: random name + store original in DB
dst, err := os.Create(dstPath)
if err != nil {
http.Error(w, "storage", 500)
return
}
defer dst.Close()
if _, err := io.Copy(dst, io.LimitReader(file, 32<<20)); err != nil {
http.Error(w, "write", 500)
return
}
fmt.Fprintln(w, "stored", name)
}Sniff type: http.DetectContentType on first 512 bytes.
Download
func download(w http.ResponseWriter, r *http.Request) {
// resolve id → path inside jail (chapter 142 path jail)
f, err := os.Open(safePath)
if err != nil {
http.NotFound(w, r)
return
}
defer f.Close()
st, _ := f.Stat()
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", `attachment; filename="report.bin"`)
http.ServeContent(w, r, st.Name(), st.ModTime(), f) // supports Range
}Static files
mux.Handle("GET /static/", http.StripPrefix("/static/",
http.FileServer(http.Dir("static"))))For embed: http.FileServer(http.FS(embedFS)). Disable directory listing in production if needed (custom FS).
Rules of thumb
| Do | Don’t |
|---|---|
| MaxBytesReader | Buffer whole upload in memory blindly |
filepath.Base + jail |
Use raw hdr.Filename paths |
ServeContent for Range |
Hand-roll partial downloads first |
Try next
- Upload form + size limit test.
- Download with
curl -C -resume via ServeContent.
- Store under random UUID names.