From 7d15f57e8642bcd639a7a1c026c320ff97e3cbdf Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 3 Jun 2026 15:17:34 -0400 Subject: [PATCH] fix(server): cast token URL honors X-Forwarded-Proto / Host On-device test against Sonos showed SetAVTransportURI returning UPnP error 714 (IllegalMimeType). Logcat: POST /api/cast/stream-token -> 200 (token minted) SetAVTransportURI to http://minstrel.fabledsword.com/... <-- 500 from Sonos: SoapFaultException SOAP fault 714 The server is behind a TLS-terminating reverse proxy, so r.TLS is nil and the URL builder emitted http://. Sonos does a HEAD probe to detect the audio MIME type; against an http:// URL that 301s to https://, the probe finds no audio body and bails with 714. The Task 2 code-quality reviewer flagged this exact scenario at the time. Closing it now: honor X-Forwarded-Proto + X-Forwarded-Host before falling back to r.TLS + r.Host. Public URL the speaker fetches now matches the scheme/host the client used to reach the endpoint. --- internal/api/cast_token.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/internal/api/cast_token.go b/internal/api/cast_token.go index 5fdf14f8..37751150 100644 --- a/internal/api/cast_token.go +++ b/internal/api/cast_token.go @@ -56,11 +56,25 @@ func (h *handlers) handleCastStreamToken(w http.ResponseWriter, r *http.Request) exp := time.Now().Add(time.Duration(expSec) * time.Second).Unix() token := SignStreamToken(h.streamSecret, req.TrackID, exp) + // Behind a TLS-terminating reverse proxy, r.TLS is nil even though + // the public-facing URL is https://. UPnP devices (Sonos especially) + // reject SetAVTransportURI with error 714 (IllegalMimeType) when + // they hit an http:// URL that immediately 301s to https:// — the + // MIME probe fails to find an audio body. Honor X-Forwarded-Proto + + // X-Forwarded-Host first so the URL we hand to the speaker reaches + // it on the same scheme/host the client used. Falls back to + // r.TLS-based detection for direct (no-proxy) deployments. scheme := "http" - if r.TLS != nil { + if proto := r.Header.Get("X-Forwarded-Proto"); proto != "" { + scheme = proto + } else if r.TLS != nil { scheme = "https" } - url := scheme + "://" + r.Host + "/api/tracks/" + req.TrackID + + host := r.Host + if h := r.Header.Get("X-Forwarded-Host"); h != "" { + host = h + } + url := scheme + "://" + host + "/api/tracks/" + req.TrackID + "/stream?token=" + token + "&exp=" + strconv.FormatInt(exp, 10) writeJSON(w, http.StatusOK, castTokenResponse{Token: token, Exp: exp, URL: url})