package spread import ( "encoding/json" "io" "net/http" "path/filepath" "strings" ) const maxUpload = 8 << 20 // 8 MiB // Handler is the shared HTTP handler for POST /api/analyze. It accepts either // a multipart form (field "bom") or a raw body with ?filename=. It is used // both by the self-host server and the Vercel serverless function. func Handler(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodOptions { writeCORS(w) w.WriteHeader(http.StatusNoContent) return } if r.Method != http.MethodPost { writeErr(w, http.StatusMethodNotAllowed, "POST a BOM file to this endpoint") return } writeCORS(w) filename, data, err := readUpload(r) if err != nil { writeErr(w, http.StatusBadRequest, err.Error()) return } items, err := ParseBOM(filename, data) if err != nil { writeErr(w, http.StatusUnprocessableEntity, err.Error()) return } rep := Analyze(bomRef(filename), items) w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(rep) } func readUpload(r *http.Request) (filename string, data []byte, err error) { ct := r.Header.Get("Content-Type") if strings.HasPrefix(ct, "multipart/form-data") { if err = r.ParseMultipartForm(maxUpload); err != nil { return "", nil, err } f, hdr, ferr := r.FormFile("bom") if ferr != nil { return "", nil, ferr } defer f.Close() data, err = io.ReadAll(io.LimitReader(f, maxUpload)) return hdr.Filename, data, err } // raw body data, err = io.ReadAll(io.LimitReader(r.Body, maxUpload)) filename = r.URL.Query().Get("filename") if filename == "" { filename = "upload.csv" } return filename, data, err } func bomRef(filename string) string { base := strings.TrimSuffix(filepath.Base(filename), filepath.Ext(filename)) base = strings.TrimSpace(base) if base == "" { return "BOM-UPLOAD" } return base } func writeCORS(w http.ResponseWriter) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") } func writeErr(w http.ResponseWriter, code int, msg string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) _ = json.NewEncoder(w).Encode(map[string]string{"error": msg}) }