package api
import (
"io"
"net/http"
"os"
)
func Upload(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, 20<<20) // 20 MiB
if err := r.ParseMultipartForm(20 << 20); err != nil {
http.Error(w, "invalid upload", http.StatusBadRequest)
return
}
f, _, err := r.FormFile("file")
if err != nil {
http.Error(w, "missing file", http.StatusBadRequest)
return
}
defer f.Close()
tmp, err := os.CreateTemp("", "upload-*")
if err != nil {
http.Error(w, "server error", http.StatusInternalServerError)
return
}
defer os.Remove(tmp.Name())
defer tmp.Close()
if _, err := io.Copy(tmp, f); err != nil {
http.Error(w, "failed to save", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
}
Multipart uploads are a common DOS vector if you let them allocate unbounded memory. I cap the request with http.MaxBytesReader, keep ParseMultipartForm bounded, and copy the file stream into a temp file using io.Copy. This avoids holding the whole file in RAM and gives you an artifact you can scan, validate, and then push to object storage. The other detail is cleanup: if any validation fails, the temp file should be removed, and file descriptors must be closed. In production I also validate MIME type by sniffing the first bytes rather than trusting the client header, and I track upload metrics (bytes, errors) so abuse is visible. This pattern is pragmatic and safe for most services.