Files

88 lines
2.5 KiB
Go

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
}