|
7 | 7 | "html" |
8 | 8 | "net" |
9 | 9 | "net/http" |
| 10 | + "net/url" |
10 | 11 | "os" |
11 | 12 | "runtime" |
12 | 13 | "sort" |
@@ -192,14 +193,12 @@ func NewMonitoringUI(proxy *Proxy) *MonitoringUI { |
192 | 193 | if origin == "" { |
193 | 194 | return true // Allow requests without Origin header (direct connections) |
194 | 195 | } |
195 | | - host := r.Host |
196 | | - if host == "" { |
| 196 | + originURL, err := url.Parse(origin) |
| 197 | + if err != nil || originURL.User != nil || originURL.Host == "" || |
| 198 | + originURL.Path != "" || originURL.RawQuery != "" || originURL.Fragment != "" { |
197 | 199 | return false |
198 | 200 | } |
199 | | - // Allow same-origin requests and localhost variations |
200 | | - return origin == "http://"+host || origin == "https://"+host || |
201 | | - origin == "http://localhost:8080" || origin == "https://localhost:8080" || |
202 | | - origin == "http://127.0.0.1:8080" || origin == "https://127.0.0.1:8080" |
| 201 | + return originURL.Scheme == requestScheme(r) && strings.EqualFold(originURL.Host, r.Host) |
203 | 202 | }, |
204 | 203 | }, |
205 | 204 | clients: make(map[*websocket.Conn]bool), |
@@ -1021,11 +1020,24 @@ func (mc *MetricsCollector) GetMetrics() map[string]any { |
1021 | 1020 | return metrics |
1022 | 1021 | } |
1023 | 1022 |
|
1024 | | -// setCORSHeaders - Sets standard CORS headers for all responses |
1025 | | -func setCORSHeaders(w http.ResponseWriter) { |
1026 | | - w.Header().Set("Access-Control-Allow-Origin", "*") |
1027 | | - w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") |
1028 | | - w.Header().Set("Access-Control-Allow-Headers", "Content-Type") |
| 1023 | +// requestScheme - Returns the scheme the client used, which a TLS-terminating |
| 1024 | +// proxy reports in X-Forwarded-Proto. Browsers cannot forge that header on a |
| 1025 | +// WebSocket handshake. |
| 1026 | +func requestScheme(r *http.Request) string { |
| 1027 | + forwarded := r.Header.Get("X-Forwarded-Proto") |
| 1028 | + if comma := strings.IndexByte(forwarded, ','); comma >= 0 { |
| 1029 | + forwarded = forwarded[:comma] |
| 1030 | + } |
| 1031 | + switch strings.ToLower(strings.TrimSpace(forwarded)) { |
| 1032 | + case "http": |
| 1033 | + return "http" |
| 1034 | + case "https": |
| 1035 | + return "https" |
| 1036 | + } |
| 1037 | + if r.TLS != nil { |
| 1038 | + return "https" |
| 1039 | + } |
| 1040 | + return "http" |
1029 | 1041 | } |
1030 | 1042 |
|
1031 | 1043 | // setDynamicCacheHeaders - Sets cache headers for dynamic content (metrics, API) |
@@ -1070,9 +1082,6 @@ func (ui *MonitoringUI) handleTestQuery(w http.ResponseWriter, r *http.Request) |
1070 | 1082 |
|
1071 | 1083 | // handleRoot - Handles the root path |
1072 | 1084 | func (ui *MonitoringUI) handleRoot(w http.ResponseWriter, r *http.Request) { |
1073 | | - // Set CORS headers |
1074 | | - setCORSHeaders(w) |
1075 | | - |
1076 | 1085 | // Handle preflight OPTIONS request |
1077 | 1086 | if r.Method == "OPTIONS" { |
1078 | 1087 | w.WriteHeader(http.StatusOK) |
@@ -1127,9 +1136,6 @@ func (ui *MonitoringUI) handleMetrics(w http.ResponseWriter, r *http.Request) { |
1127 | 1136 |
|
1128 | 1137 | // handleWebSocket - Handles WebSocket connections |
1129 | 1138 | func (ui *MonitoringUI) handleWebSocket(w http.ResponseWriter, r *http.Request) { |
1130 | | - // Set CORS headers for WebSocket |
1131 | | - setCORSHeaders(w) |
1132 | | - |
1133 | 1139 | // Handle preflight OPTIONS request |
1134 | 1140 | if r.Method == "OPTIONS" { |
1135 | 1141 | w.WriteHeader(http.StatusOK) |
@@ -1223,7 +1229,6 @@ func (ui *MonitoringUI) handleStatic(w http.ResponseWriter, r *http.Request) { |
1223 | 1229 |
|
1224 | 1230 | // handleStaticJS - Serves the JavaScript for the monitoring UI |
1225 | 1231 | func (ui *MonitoringUI) handleStaticJS(w http.ResponseWriter, r *http.Request) { |
1226 | | - setCORSHeaders(w) |
1227 | 1232 | // JavaScript is static - cache for 1 hour |
1228 | 1233 | setStaticCacheHeaders(w, 3600) |
1229 | 1234 | w.Header().Set("Content-Type", "application/javascript") |
|
0 commit comments