diff --git a/cmd/soundtouch-web/static/js/app.js b/cmd/soundtouch-web/static/js/app.js index 48895ec..5276126 100644 --- a/cmd/soundtouch-web/static/js/app.js +++ b/cmd/soundtouch-web/static/js/app.js @@ -284,7 +284,7 @@ function handleWebSocketMessage(data) { renderDeviceList(); break; case "status_update": - if (devices[data.deviceId]) { + if (Object.hasOwn(devices, data.deviceId)) { devices[data.deviceId].status = data.data; if (currentDeviceId === data.deviceId) { updateDeviceStatus(data.data); @@ -406,12 +406,12 @@ function renderDeviceList() {
- ${device.info?.Name || "Unknown Device"} + ${escapeHtml(device.info?.Name || "Unknown Device")}

- Type: ${device.info?.Type || "Unknown"}
+ Type: ${escapeHtml(device.info?.Type || "Unknown")}
Status: ${statusText}
Last Seen: ${lastSeen}

@@ -468,8 +468,8 @@ function renderDeviceControl(deviceId, device) { const html = `
-

${info.Name || "Unknown Device"}

-

${info.Type || "Unknown Type"}

+

${escapeHtml(info.Name || "Unknown Device")}

+

${escapeHtml(info.Type || "Unknown Type")}

-
${nowPlaying.Track || nowPlaying.track || "No track playing"}
-

${nowPlaying.Artist || nowPlaying.artist || "Unknown artist"}

- ${nowPlaying.Album || nowPlaying.album || "Unknown album"} +
${escapeHtml(nowPlaying.Track || nowPlaying.track || "No track playing")}
+

${escapeHtml(nowPlaying.Artist || nowPlaying.artist || "Unknown artist")}

+ ${escapeHtml(nowPlaying.Album || nowPlaying.album || "Unknown album")}
@@ -890,10 +890,10 @@ function showToast(title, message, type = "info") { `; diff --git a/pkg/service/bmx/bmx.go b/pkg/service/bmx/bmx.go index 06a4758..38dd045 100644 --- a/pkg/service/bmx/bmx.go +++ b/pkg/service/bmx/bmx.go @@ -27,7 +27,26 @@ const ( var tuneInClient = &http.Client{Timeout: 10 * time.Second} +// allowedTuneInHosts restricts outbound fetches to known TuneIn domains. +var allowedTuneInHosts = map[string]bool{ + "opml.radiotime.com": true, + "api.radiotime.com": true, +} + +func isTuneInURL(rawURL string) bool { + u, err := url.Parse(rawURL) + if err != nil { + return false + } + + return allowedTuneInHosts[u.Hostname()] +} + func fetchJSON(fetchURL string) (map[string]interface{}, error) { + if !isTuneInURL(fetchURL) { + return nil, fmt.Errorf("URL not in allowed list: %s", fetchURL) + } + resp, err := tuneInClient.Get(fetchURL) if err != nil { return nil, err