File Uploads and Downloads

Updated

September 8, 2026

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

  1. Upload form + size limit test.
  2. Download with curl -C - resume via ServeContent.
  3. Store under random UUID names.