From feba5d6b93012903c381cec10fb1a506b0ee6807 Mon Sep 17 00:00:00 2001 From: Gilles Soulier Date: Fri, 22 May 2026 12:18:25 +0200 Subject: [PATCH] =?UTF-8?q?fix(server):=20race=20WebSocket,=20SVG=20refus?= =?UTF-8?q?=C3=A9,=20rows.Err,=20time.NewTicker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- server/db/db.go | 6 ++++++ server/handlers/icons.go | 14 ++++++-------- server/main.go | 4 +++- server/websocket/hub.go | 37 ++++++++++++++++++++++++++++++------- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/server/db/db.go b/server/db/db.go index 1716064..4afcf46 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -117,6 +117,9 @@ func (d *DB) GetAgents() ([]models.Agent, error) { } agents = append(agents, a) } + if err := rows.Err(); err != nil { + return nil, err + } return agents, nil } @@ -143,6 +146,9 @@ func (d *DB) GetMetricsHistory(agentID string, from, to int64) ([]map[string]int "hdd_used": hddUsed, "hdd_total": hddTotal, }) } + if err := rows.Err(); err != nil { + return nil, err + } return result, nil } diff --git a/server/handlers/icons.go b/server/handlers/icons.go index 5709554..97e01a0 100644 --- a/server/handlers/icons.go +++ b/server/handlers/icons.go @@ -5,6 +5,7 @@ import ( "image" _ "image/jpeg" "image/png" + "io" "net/http" "strings" @@ -40,18 +41,15 @@ func IconUploadHandler(database *db.DB) http.HandlerFunc { mime = "image/png" } + // SVG refusé (risque XSS) if strings.Contains(mime, "svg") { - var buf bytes.Buffer - buf.ReadFrom(file) - if err := database.SaveIcon(agentID, buf.Bytes(), "image/svg+xml"); err != nil { - http.Error(w, err.Error(), 500) - return - } - w.WriteHeader(http.StatusNoContent) + http.Error(w, "SVG non supporté — utilisez PNG, JPG ou WEBP", 400) return } - img, _, err := image.Decode(file) + // Limite de taille + limited := io.LimitReader(file, 2<<20) + img, _, err := image.Decode(limited) if err != nil { http.Error(w, "image invalide", 400) return diff --git a/server/main.go b/server/main.go index f269448..b39bd27 100644 --- a/server/main.go +++ b/server/main.go @@ -60,7 +60,9 @@ func main() { } go func() { - for range time.Tick(time.Minute) { + ticker := time.NewTicker(time.Minute) + defer ticker.Stop() + for range ticker.C { srvCfg, _ := database.GetServerConfig() _ = database.PruneOldMetrics(srvCfg.RetentionDays) _ = database.MarkOffline(30) diff --git a/server/websocket/hub.go b/server/websocket/hub.go index 6fac820..7a43307 100644 --- a/server/websocket/hub.go +++ b/server/websocket/hub.go @@ -8,24 +8,35 @@ import ( "github.com/gorilla/websocket" ) +type client struct { + conn *websocket.Conn + send chan []byte + mu sync.Mutex +} + type Hub struct { mu sync.RWMutex - clients map[*websocket.Conn]struct{} + clients map[*websocket.Conn]*client } func NewHub() *Hub { - return &Hub{clients: make(map[*websocket.Conn]struct{})} + return &Hub{clients: make(map[*websocket.Conn]*client)} } func (h *Hub) Register(conn *websocket.Conn) { + c := &client{conn: conn, send: make(chan []byte, 64)} h.mu.Lock() - h.clients[conn] = struct{}{} + h.clients[conn] = c h.mu.Unlock() + go c.writePump() } func (h *Hub) Unregister(conn *websocket.Conn) { h.mu.Lock() - delete(h.clients, conn) + if c, ok := h.clients[conn]; ok { + close(c.send) + delete(h.clients, conn) + } h.mu.Unlock() } @@ -37,9 +48,11 @@ func (h *Hub) Broadcast(msg interface{}) { } h.mu.RLock() defer h.mu.RUnlock() - for conn := range h.clients { - if err := conn.WriteMessage(websocket.TextMessage, data); err != nil { - log.Printf("[ws] write: %v", err) + for _, c := range h.clients { + select { + case c.send <- data: + default: + // canal plein, client lent — on skip } } } @@ -49,3 +62,13 @@ func (h *Hub) Count() int { defer h.mu.RUnlock() return len(h.clients) } + +func (c *client) writePump() { + defer c.conn.Close() + for data := range c.send { + if err := c.conn.WriteMessage(websocket.TextMessage, data); err != nil { + log.Printf("[ws] write: %v", err) + return + } + } +}