package infra import ( "bytes" "fmt" "io" "mime/multipart" "net/http" "strings" ) // ParsedFile representa un archivo extraido de un multipart form, con su contenido // completo en memoria y los primeros bytes (Header) listos para validacion por // magic bytes. type ParsedFile struct { FormField string // nombre del field del form (ej: "file", "image") Filename string // nombre original reportado por el cliente Size int64 // tamano en bytes MIMEHint string // Content-Type segun el cliente (NO confiar — usar FileValidateType) Header []byte // primeros 512 bytes para magic byte detection Content io.Reader } // UploadParse parsea un request multipart/form-data y extrae todos los archivos // adjuntos. Aplica http.MaxBytesReader para limitar el tamano total a maxSize bytes. // // Retorna un slice de ParsedFile con el contenido cargado en memoria como bytes.Reader, // listo para ser pasado a FileSaveDisk o S3Upload. // // Para uploads muy grandes considerar streaming con multipart.Reader directamente // en vez de esta funcion (que carga todo en memoria). func UploadParse(r *http.Request, maxSize int64) ([]ParsedFile, error) { if r.Body == nil { return nil, fmt.Errorf("upload_parse: request sin body") } ct := r.Header.Get("Content-Type") if !strings.HasPrefix(ct, "multipart/") { return nil, fmt.Errorf("upload_parse: content-type %q no es multipart", ct) } r.Body = http.MaxBytesReader(nil, r.Body, maxSize) if err := r.ParseMultipartForm(maxSize); err != nil { return nil, fmt.Errorf("upload_parse: parse form: %w", err) } if r.MultipartForm == nil { return nil, fmt.Errorf("upload_parse: form vacio") } var out []ParsedFile for field, headers := range r.MultipartForm.File { for _, fh := range headers { pf, err := readMultipartFile(field, fh) if err != nil { return nil, err } out = append(out, pf) } } return out, nil } func readMultipartFile(field string, fh *multipart.FileHeader) (ParsedFile, error) { f, err := fh.Open() if err != nil { return ParsedFile{}, fmt.Errorf("upload_parse: open %s: %w", fh.Filename, err) } defer f.Close() buf, err := io.ReadAll(f) if err != nil { return ParsedFile{}, fmt.Errorf("upload_parse: read %s: %w", fh.Filename, err) } header := buf if len(header) > 512 { header = header[:512] } return ParsedFile{ FormField: field, Filename: fh.Filename, Size: int64(len(buf)), MIMEHint: fh.Header.Get("Content-Type"), Header: header, Content: bytes.NewReader(buf), }, nil }