88 lines
2.5 KiB
Go
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
|
|
}
|