From 68801366d3b7f31cd273d07e2fec9390bd12045c Mon Sep 17 00:00:00 2001 From: Pulse Monitor Date: Sun, 31 Aug 2025 16:24:08 +0000 Subject: [PATCH] fix: properly handle alert IDs with special characters in acknowledge/clear endpoints (addresses #380) Alert IDs like 'pve1:qemu/101-cpu' contain slashes which were breaking the URL path parsing. Fixed by finding the /acknowledge or /clear suffix and extracting everything before it, rather than trying to split by slashes. --- internal/api/alerts.go | 53 ++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/internal/api/alerts.go b/internal/api/alerts.go index 86209f748..c88c2d076 100644 --- a/internal/api/alerts.go +++ b/internal/api/alerts.go @@ -119,21 +119,28 @@ func (h *AlertHandlers) ClearAlertHistory(w http.ResponseWriter, r *http.Request // AcknowledgeAlert acknowledges an alert func (h *AlertHandlers) AcknowledgeAlert(w http.ResponseWriter, r *http.Request) { // Extract alert ID from URL path: /api/alerts/{id}/acknowledge - // The path comes in as /api/alerts/{id}/acknowledge, and HandleAlerts strips the /api/alerts prefix - // So we get /{id}/acknowledge here - path := strings.TrimPrefix(r.URL.Path, "/api/alerts") - path = strings.TrimPrefix(path, "/") - parts := strings.Split(path, "/") - if len(parts) < 2 || parts[1] != "acknowledge" { + // Alert IDs can contain slashes (e.g., "pve1:qemu/101-cpu") + // So we need to find the /acknowledge suffix and extract everything before it + path := strings.TrimPrefix(r.URL.Path, "/api/alerts/") + + const suffix = "/acknowledge" + if !strings.HasSuffix(path, suffix) { log.Error(). Str("path", r.URL.Path). - Str("trimmedPath", path). - Int("parts", len(parts)). - Msg("Invalid acknowledge URL format") + Msg("Path does not end with /acknowledge") + http.Error(w, "Invalid URL", http.StatusBadRequest) + return + } + + // Extract alert ID by removing the suffix + alertID := strings.TrimSuffix(path, suffix) + if alertID == "" { + log.Error(). + Str("path", r.URL.Path). + Msg("Empty alert ID") http.Error(w, "Invalid URL", http.StatusBadRequest) return } - alertID := parts[0] // Log the acknowledge attempt log.Debug(). @@ -172,16 +179,28 @@ func (h *AlertHandlers) AcknowledgeAlert(w http.ResponseWriter, r *http.Request) // ClearAlert manually clears an alert func (h *AlertHandlers) ClearAlert(w http.ResponseWriter, r *http.Request) { // Extract alert ID from URL path: /api/alerts/{id}/clear - // The path comes in as /api/alerts/{id}/clear, and HandleAlerts strips the /api/alerts prefix - // So we get /{id}/clear here - path := strings.TrimPrefix(r.URL.Path, "/api/alerts") - path = strings.TrimPrefix(path, "/") - parts := strings.Split(path, "/") - if len(parts) < 2 || parts[1] != "clear" { + // Alert IDs can contain slashes (e.g., "pve1:qemu/101-cpu") + // So we need to find the /clear suffix and extract everything before it + path := strings.TrimPrefix(r.URL.Path, "/api/alerts/") + + const suffix = "/clear" + if !strings.HasSuffix(path, suffix) { + log.Error(). + Str("path", r.URL.Path). + Msg("Path does not end with /clear") + http.Error(w, "Invalid URL", http.StatusBadRequest) + return + } + + // Extract alert ID by removing the suffix + alertID := strings.TrimSuffix(path, suffix) + if alertID == "" { + log.Error(). + Str("path", r.URL.Path). + Msg("Empty alert ID") http.Error(w, "Invalid URL", http.StatusBadRequest) return } - alertID := parts[0] h.monitor.GetAlertManager().ClearAlert(alertID)