feat: file_save_disk, file_delete, file_serve, upload_parse, upload_handler, thumbnail_generate (issue 0014 fase 3)
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user