diff --git a/rest/controllers/api/oss_helpers.go b/rest/controllers/api/oss_helpers.go index 59f6ecb..b9e991f 100644 --- a/rest/controllers/api/oss_helpers.go +++ b/rest/controllers/api/oss_helpers.go @@ -2,6 +2,7 @@ package controllers import ( "encoding/json" + "strings" "github.com/gofiber/fiber/v2" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" @@ -38,3 +39,30 @@ func getOSSFromQuery(c *fiber.Ctx) (services.OSSProvider, error) { } return services.NewOSSProvider(lastActive) } + +// claveObjetoSegura arma una clave de objeto a partir de texto que viene del +// cliente. Sin esto, un nombre con "/" o con caracteres de control produce una +// clave que OSS rechaza — y el que llama solo ve "error subiendo a OSS". +func claveObjetoSegura(nombre string) string { + nombre = strings.TrimSpace(nombre) + limpio := strings.Map(func(r rune) rune { + switch { + case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9': + return r + case r == '-', r == '_', r == '.': + return r + case r == ' ': + return '_' + default: + return -1 + } + }, nombre) + limpio = strings.Trim(limpio, "._-") + if limpio == "" { + return "sin-nombre" + } + if len(limpio) > 80 { + limpio = limpio[:80] + } + return limpio +} diff --git a/rest/controllers/api/oss_helpers_test.go b/rest/controllers/api/oss_helpers_test.go new file mode 100644 index 0000000..408aea9 --- /dev/null +++ b/rest/controllers/api/oss_helpers_test.go @@ -0,0 +1,26 @@ +package controllers + +import "testing" + +// La clave de objeto se arma con texto que manda el cliente. Antes iba tal +// cual: un nombre con "/" o con acentos raros producía una clave que OSS +// rechaza, y del otro lado solo se veía "error subiendo archivo a OSS". +func TestClaveObjetoSegura(t *testing.T) { + casos := map[string]string{ + "Juan Pérez": "Juan_Prez", + "Ana/María": "AnaMara", + "../../etc/pass": "etcpass", + " ": "sin-nombre", + "": "sin-nombre", + "ok-nombre_1.2": "ok-nombre_1.2", + "emoji 🚀 fin": "emoji__fin", + } + for in, want := range casos { + if got := claveObjetoSegura(in); got != want { + t.Errorf("claveObjetoSegura(%q) = %q, want %q", in, got, want) + } + } + if got := claveObjetoSegura(string(make([]byte, 0)) + "a" + string(rune(0))); got != "a" { + t.Errorf("caracter de control no filtrado: %q", got) + } +} diff --git a/rest/controllers/api/vcard_qr.go b/rest/controllers/api/vcard_qr.go index b5aee17..74ee954 100755 --- a/rest/controllers/api/vcard_qr.go +++ b/rest/controllers/api/vcard_qr.go @@ -7,7 +7,6 @@ import ( "fmt" "image/png" "log" - "os" "strings" "github.com/chai2010/webp" @@ -53,29 +52,24 @@ func CreateQr(c *fiber.Ctx) error { } random := helpers.RandomString(4) - fileName := fmt.Sprintf("qrs/qr-%s-%s.webp", strings.ReplaceAll(data.FirstName, " ", "_"), random) - tmpPath := fmt.Sprintf("/tmp/%s.webp", data.FirstName) + fileName := fmt.Sprintf("qrs/qr-%s-%s.webp", claveObjetoSegura(data.FirstName), random) - // 4. Guardar archivo temporal - if err := os.WriteFile(tmpPath, webpBuf.Bytes(), 0644); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "error": "No se pudo guardar el QR temporalmente", - }) - } - defer os.Remove(tmpPath) - - // 5. Subir a OSS + // 4. Subir a OSS directo desde memoria: el archivo temporal solo agregaba + // una forma más de fallar (y el nombre venía del cliente, sin limpiar). ossProvider, err := getOSSFromBody(c) if err != nil { + log.Printf("[QR] No se pudo resolver la config de OSS: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "error": "Error de conexión con OSS", + "error": "Error de conexión con OSS", "detalle": err.Error(), }) } - if err := ossProvider.UploadFile(fileName, tmpPath); err != nil { - log.Printf("Error subiendo archivo a OSS: %v", err) + if err := ossProvider.UploadFromReader(fileName, "image/webp", bytes.NewReader(webpBuf.Bytes())); err != nil { + log.Printf("Error subiendo archivo a OSS (%s): %v", fileName, err) + // El detalle viaja al que llama: es una API entre servidores y sin esto + // el otro lado solo ve "error subiendo a OSS" y no puede hacer nada. return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "error": "Error subiendo archivo a OSS", + "error": "Error subiendo archivo a OSS", "detalle": err.Error(), }) } @@ -172,7 +166,7 @@ func CreateQrTmp(c *fiber.Ctx) error { func CreateUrlQr(c *fiber.Ctx) error { var data struct { - URL string `json:"url"` + URL string `json:"url"` Nombre string `json:"unico"` } if err := json.Unmarshal(c.Body(), &data); err != nil { @@ -187,14 +181,14 @@ func CreateUrlQr(c *fiber.Ctx) error { "error": "No se pudo generar el QR", }) } - + img, err := png.Decode(bytes.NewReader(qrBytes)) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "No se pudo procesar la imagen", }) } - + // Codificar a WebP var webpBuffer bytes.Buffer if err := webp.Encode(&webpBuffer, img, &webp.Options{Lossless: true}); err != nil { @@ -202,7 +196,7 @@ func CreateUrlQr(c *fiber.Ctx) error { "error": "No se pudo convertir a WebP", }) } - + // 3. Convertir a WebP var webpBuf bytes.Buffer if err := webp.Encode(&webpBuf, img, nil); err != nil { @@ -211,40 +205,29 @@ func CreateUrlQr(c *fiber.Ctx) error { }) } + fileName := fmt.Sprintf("qrs/url/qr-%s.webp", claveObjetoSegura(data.Nombre)) - fileName := fmt.Sprintf("qrs/url/qr-%s.webp", strings.ReplaceAll(data.Nombre, " ", "_") ) - tmpPath := fmt.Sprintf("/tmp/%s.webp", data.Nombre) - - // 4. Guardar archivo temporal - if err := os.WriteFile(tmpPath, webpBuf.Bytes(), 0644); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "error": "No se pudo guardar el QR temporalmente", - }) - } - defer os.Remove(tmpPath) - - // 5. Subir a OSS + // 4. Subir a OSS directo desde memoria (ver el comentario en CreateQr). ossProvider, err := getOSSFromBody(c) if err != nil { + log.Printf("[QR URL] No se pudo resolver la config de OSS: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "error": "Error de conexión con OSS", + "error": "Error de conexión con OSS", "detalle": err.Error(), }) } - if err := ossProvider.UploadFile(fileName, tmpPath); err != nil { - log.Printf("Error subiendo archivo a OSS: %v", err) + if err := ossProvider.UploadFromReader(fileName, "image/webp", bytes.NewReader(webpBuf.Bytes())); err != nil { + log.Printf("Error subiendo archivo a OSS (%s): %v", fileName, err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "error": "Error subiendo archivo a OSS", + "error": "Error subiendo archivo a OSS", "detalle": err.Error(), }) } // 6. URL del archivo url := ossProvider.PublicURL(fileName) - // 8. Retornar la URL return c.JSON(fiber.Map{ "url": url, }) } - diff --git a/rest/controllers/api/vcard_vcf.go b/rest/controllers/api/vcard_vcf.go index a4ce691..1376cd6 100755 --- a/rest/controllers/api/vcard_vcf.go +++ b/rest/controllers/api/vcard_vcf.go @@ -1,12 +1,11 @@ package controllers import ( + "bytes" "encoding/json" "errors" "fmt" "log" - "os" - "strings" "github.com/gofiber/fiber/v2" "github.com/sujit-baniya/fiber-boilerplate/app" @@ -34,29 +33,21 @@ func CreateVcf(c *fiber.Ctx) error { } random := helpers.RandomString(4) - fileName := fmt.Sprintf("vcf/vcf-%s-%s.vcf", strings.ReplaceAll(data.FirstName, " ", "_"), random) - tmpPath := fmt.Sprintf("/tmp/vcf-%s-%s.vcf", strings.ReplaceAll(data.FirstName, " ", "_"), random) + fileName := fmt.Sprintf("vcf/vcf-%s-%s.vcf", claveObjetoSegura(data.FirstName), random) - // 2. Guardar archivo temporal - if err := os.WriteFile(tmpPath, vcfContent, 0644); err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "error": "No se pudo guardar el VCF temporalmente", - }) - } - defer os.Remove(tmpPath) - - // 3. Subir a OSS + // 2. Subir a OSS directo desde memoria (ver el comentario en CreateQr). ossProvider, err := getOSSFromBody(c) if err != nil { + log.Printf("[VCF] No se pudo resolver la config de OSS: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "error": "Error de conexión con OSS", + "error": "Error de conexión con OSS", "detalle": err.Error(), }) } - if err := ossProvider.UploadFile(fileName, tmpPath); err != nil { - log.Printf("Error subiendo archivo a OSS: %v", err) + if err := ossProvider.UploadFromReader(fileName, "text/vcard; charset=utf-8", bytes.NewReader(vcfContent)); err != nil { + log.Printf("Error subiendo archivo a OSS (%s): %v", fileName, err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "error": "Error subiendo archivo a OSS", + "error": "Error subiendo archivo a OSS", "detalle": err.Error(), }) }