up
This commit is contained in:
@@ -180,14 +180,14 @@ func PortalGetProyectoData(c *fiber.Ctx) error {
|
||||
documentos, _ := models.GetDocumentosByProyecto(proy.ID)
|
||||
|
||||
return c.JSON(fiber.Map{
|
||||
"proyecto": proy,
|
||||
"fases": fases,
|
||||
"avances": avances,
|
||||
"entregables": entregables,
|
||||
"documentos": documentos,
|
||||
"tickets": tickets,
|
||||
"facturas": facturas,
|
||||
"facturasNoLeidas": facturasNoLeidas,
|
||||
"proyecto": proy,
|
||||
"fases": fases,
|
||||
"avances": avances,
|
||||
"entregables": entregables,
|
||||
"documentos": documentos,
|
||||
"tickets": tickets,
|
||||
"facturas": facturas,
|
||||
"facturasNoLeidas": facturasNoLeidas,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -252,7 +252,9 @@ func PortalResponderTicket(c *fiber.Ctx) error {
|
||||
return c.Status(401).JSON(fiber.Map{"error": "no autenticado"})
|
||||
}
|
||||
ticketID, _ := c.ParamsInt("id")
|
||||
type Req struct{ Contenido string `json:"contenido"` }
|
||||
type Req struct {
|
||||
Contenido string `json:"contenido"`
|
||||
}
|
||||
var req Req
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(400).JSON(fiber.Map{"error": err.Error()})
|
||||
@@ -382,7 +384,7 @@ func PortalDownloadDocumento(c *fiber.Ctx) error {
|
||||
if !hasAccess {
|
||||
return c.Status(403).JSON(fiber.Map{"error": "Sin acceso"})
|
||||
}
|
||||
clean := doc.Archivo
|
||||
clean := filepath.Clean(doc.Archivo)
|
||||
if !strings.HasPrefix(clean, "uploads/") {
|
||||
return c.Status(403).JSON(fiber.Map{"error": "Acceso denegado"})
|
||||
}
|
||||
@@ -390,7 +392,13 @@ func PortalDownloadDocumento(c *fiber.Ctx) error {
|
||||
if nombre == "" {
|
||||
nombre = doc.Nombre
|
||||
}
|
||||
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, nombre))
|
||||
safe := strings.Map(func(r rune) rune {
|
||||
if r > 127 || r == '"' || r == '\\' || r == '/' || r == '\n' || r == '\r' {
|
||||
return '_'
|
||||
}
|
||||
return r
|
||||
}, nombre)
|
||||
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, safe, url.PathEscape(nombre)))
|
||||
return c.SendFile(clean)
|
||||
}
|
||||
|
||||
@@ -792,4 +800,3 @@ func searchTokenInUpdates(botToken, token string) (int64, bool) {
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"mime/multipart"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -491,10 +492,25 @@ func DownloadDocumento(c *fiber.Ctx) error {
|
||||
if !strings.HasPrefix(clean, "uploads/") {
|
||||
return c.Status(403).JSON(fiber.Map{"error": "Acceso denegado"})
|
||||
}
|
||||
c.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, item.OriginalName))
|
||||
nombre := item.OriginalName
|
||||
if nombre == "" {
|
||||
nombre = item.Nombre
|
||||
}
|
||||
c.Set("Content-Disposition", attachmentDisposition(nombre))
|
||||
return c.SendFile(clean)
|
||||
}
|
||||
|
||||
// attachmentDisposition genera un header Content-Disposition con fallback ASCII y encoding RFC 5987.
|
||||
func attachmentDisposition(name string) string {
|
||||
safe := strings.Map(func(r rune) rune {
|
||||
if r > 127 || r == '"' || r == '\\' || r == '/' || r == '\n' || r == '\r' {
|
||||
return '_'
|
||||
}
|
||||
return r
|
||||
}, name)
|
||||
return fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, safe, url.PathEscape(name))
|
||||
}
|
||||
|
||||
// ─── Tickets (admin) ──────────────────────────────────────────────────────────
|
||||
|
||||
func GetTickets(c *fiber.Ctx) error {
|
||||
|
||||
Reference in New Issue
Block a user