Prevent loops for proxied requests on unknown endpoints (#166)

Follow-up for https://github.com/gesellix/Bose-SoundTouch/issues/161
This commit is contained in:
Tobias Gesellchen
2026-04-17 18:20:46 +02:00
committed by GitHub
parent 76bb19ebcb
commit ffe61dd7a6
2 changed files with 69 additions and 0 deletions
+16
View File
@@ -15,6 +15,13 @@ import (
// HandleProxyRequest handles requests to the logging proxy.
func (s *Server) HandleProxyRequest(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-Bose-Proxy-Hop") != "" {
log.Printf("[PROXY_LOOP] Loop detected for %s %s, breaking loop", r.Method, r.URL.Path)
http.Error(w, "Loop detected", http.StatusNotFound)
return
}
targetURLStr := strings.TrimPrefix(r.URL.Path, "/proxy/")
if targetURLStr == "" {
http.Error(w, "Target URL is required", http.StatusBadRequest)
@@ -65,6 +72,8 @@ func (s *Server) ServeProxy(target *url.URL) http.HandlerFunc {
pr.Out.URL.Path = target.Path
}
pr.Out.Header.Set("X-Bose-Proxy-Hop", "1")
lp.LogRequest(pr.Out)
},
Transport: &http.Transport{
@@ -101,6 +110,13 @@ func (s *Server) HandleNotFound(w http.ResponseWriter, r *http.Request) {
// HandleBoseProxy proxies the request to the Bose upstream.
func (s *Server) HandleBoseProxy(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("X-Bose-Proxy-Hop") != "" {
log.Printf("[PROXY_LOOP] Loop detected for %s %s, breaking loop", r.Method, r.URL.Path)
http.Error(w, "Loop detected", http.StatusNotFound)
return
}
host := r.Host
if host == "" {
host = "streaming.bose.com"
@@ -0,0 +1,53 @@
package handlers
import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/gesellix/bose-soundtouch/pkg/service/datastore"
)
func TestHandleBoseProxy_LoopPrevention(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "proxy-loop-test")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tmpDir)
ds := datastore.NewDataStore(filepath.Join(tmpDir, "test.db"))
server := NewServer(ds, nil, "http://localhost", false, false, false)
t.Run("first hop should be allowed", func(t *testing.T) {
req := httptest.NewRequest("GET", "/unknown-endpoint", nil)
req.Host = "localhost"
w := httptest.NewRecorder()
server.HandleBoseProxy(w, req)
if w.Code == http.StatusNotFound {
t.Errorf("Expected first hop to be allowed (even if it fails later), but got 404")
}
})
t.Run("second hop should be blocked", func(t *testing.T) {
req := httptest.NewRequest("GET", "/unknown-endpoint", nil)
req.Host = "localhost"
req.Header.Set("X-Bose-Proxy-Hop", "1")
w := httptest.NewRecorder()
server.HandleBoseProxy(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("Expected second hop to be blocked with 404, but got %d", w.Code)
}
})
t.Run("HandleProxyRequest loop detection", func(t *testing.T) {
req := httptest.NewRequest("GET", "/proxy/http://example.com", nil)
req.Header.Set("X-Bose-Proxy-Hop", "1")
w := httptest.NewRecorder()
server.HandleProxyRequest(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("Expected HandleProxyRequest loop to be blocked with 404, but got %d", w.Code)
}
})
}