From bc7f9c0f79e29280e916d9088912437189f6dcb6 Mon Sep 17 00:00:00 2001 From: Sergey Krashevich Date: Sun, 1 Feb 2026 05:46:20 +0300 Subject: [PATCH] feat: implement read-only mode enforcement in API handlers and add corresponding tests --- internal/http/http.go | 8 +++++++ internal/http/http_readonly_test.go | 30 +++++++++++++++++++++++++ internal/webrtc/server.go | 8 +++++++ internal/webrtc/server_readonly_test.go | 30 +++++++++++++++++++++++++ 4 files changed, 76 insertions(+) create mode 100644 internal/http/http_readonly_test.go create mode 100644 internal/webrtc/server_readonly_test.go diff --git a/internal/http/http.go b/internal/http/http.go index 4b0560c1..76437827 100644 --- a/internal/http/http.go +++ b/internal/http/http.go @@ -111,6 +111,14 @@ func handleTCP(rawURL string) (core.Producer, error) { } func apiStream(w http.ResponseWriter, r *http.Request) { + if api.IsReadOnly() { + switch r.Method { + case "PUT", "PATCH", "POST", "DELETE": + api.ReadOnlyError(w) + return + } + } + dst := r.URL.Query().Get("dst") stream := streams.Get(dst) if stream == nil { diff --git a/internal/http/http_readonly_test.go b/internal/http/http_readonly_test.go new file mode 100644 index 00000000..3bf992fe --- /dev/null +++ b/internal/http/http_readonly_test.go @@ -0,0 +1,30 @@ +package http + +import ( + stdhttp "net/http" + "net/http/httptest" + "testing" + + "github.com/AlexxIT/go2rtc/internal/api" + "github.com/stretchr/testify/require" +) + +func TestApiStreamReadOnly(t *testing.T) { + prevReadOnly := api.ReadOnly + t.Cleanup(func() { + api.ReadOnly = prevReadOnly + }) + + api.ReadOnly = true + + for _, method := range []string{"PUT", "PATCH", "POST", "DELETE"} { + t.Run(method, func(t *testing.T) { + req := httptest.NewRequest(method, "/api/stream?dst=test", nil) + w := httptest.NewRecorder() + + apiStream(w, req) + + require.Equal(t, stdhttp.StatusForbidden, w.Code) + }) + } +} diff --git a/internal/webrtc/server.go b/internal/webrtc/server.go index 48bd5380..584e5f8c 100644 --- a/internal/webrtc/server.go +++ b/internal/webrtc/server.go @@ -21,6 +21,14 @@ const MimeSDP = "application/sdp" var sessions = map[string]*webrtc.Conn{} func syncHandler(w http.ResponseWriter, r *http.Request) { + if api.IsReadOnly() { + switch r.Method { + case "POST", "PATCH", "DELETE": + api.ReadOnlyError(w) + return + } + } + switch r.Method { case "POST": query := r.URL.Query() diff --git a/internal/webrtc/server_readonly_test.go b/internal/webrtc/server_readonly_test.go new file mode 100644 index 00000000..ff975ef3 --- /dev/null +++ b/internal/webrtc/server_readonly_test.go @@ -0,0 +1,30 @@ +package webrtc + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/AlexxIT/go2rtc/internal/api" + "github.com/stretchr/testify/require" +) + +func TestSyncHandlerReadOnly(t *testing.T) { + prevReadOnly := api.ReadOnly + t.Cleanup(func() { + api.ReadOnly = prevReadOnly + }) + + api.ReadOnly = true + + for _, method := range []string{"POST", "PATCH", "DELETE"} { + t.Run(method, func(t *testing.T) { + req := httptest.NewRequest(method, "/api/webrtc?dst=test", nil) + w := httptest.NewRecorder() + + syncHandler(w, req) + + require.Equal(t, http.StatusForbidden, w.Code) + }) + } +}