From d16443109a8c0ca2405b7cf1208b59691c1ee06d Mon Sep 17 00:00:00 2001 From: Brendan Le Glaunec Date: Tue, 3 Feb 2026 10:12:23 +0100 Subject: [PATCH] fix: always add leading slash to routes (#397) --- internal/attack/rtsp.go | 5 +- internal/attack/rtsp_url_test.go | 86 ++++++++++++++++++++++++++++++++ internal/output/m3u.go | 5 +- internal/ui/summary.go | 5 +- 4 files changed, 89 insertions(+), 12 deletions(-) create mode 100644 internal/attack/rtsp_url_test.go diff --git a/internal/attack/rtsp.go b/internal/attack/rtsp.go index 64c4b73..7686205 100644 --- a/internal/attack/rtsp.go +++ b/internal/attack/rtsp.go @@ -166,10 +166,7 @@ func headerValues(header base.Header, name string) base.HeaderValue { func buildRTSPURL(stream cameradar.Stream, route, username, password string) (*base.URL, string, error) { host := net.JoinHostPort(stream.Address.String(), strconv.Itoa(int(stream.Port))) - path := strings.TrimSpace(route) - if path != "" && !strings.HasPrefix(path, "/") { - path = "/" + path - } + path := "/" + strings.TrimLeft(strings.TrimSpace(route), "/") // Ensure path starts with a single "/" u := &url.URL{ Scheme: "rtsp", diff --git a/internal/attack/rtsp_url_test.go b/internal/attack/rtsp_url_test.go new file mode 100644 index 0000000..ebd13d1 --- /dev/null +++ b/internal/attack/rtsp_url_test.go @@ -0,0 +1,86 @@ +package attack + +import ( + "net/netip" + "testing" + + "github.com/Ullaakut/cameradar/v6" + "github.com/stretchr/testify/require" +) + +func TestBuildRTSPURL(t *testing.T) { + stream := cameradar.Stream{ + Address: netip.MustParseAddr("192.168.0.10"), + Port: 554, + } + + tests := []struct { + name string + route string + username string + password string + wantURL string + }{ + { + name: "empty route", + wantURL: "rtsp://192.168.0.10:554/", + }, + { + name: "root route", + route: "/", + wantURL: "rtsp://192.168.0.10:554/", + }, + { + name: "multiple leading slashes", + route: "////", + wantURL: "rtsp://192.168.0.10:554/", + }, + { + name: "route with no leading slash", + route: "stream", + wantURL: "rtsp://192.168.0.10:554/stream", + }, + { + name: "route with leading slash", + route: "/stream", + wantURL: "rtsp://192.168.0.10:554/stream", + }, + { + name: "route with trailing slash", + route: "stream/", + wantURL: "rtsp://192.168.0.10:554/stream/", + }, + { + name: "route with spaces", + route: " /stream ", + wantURL: "rtsp://192.168.0.10:554/stream", + }, + { + name: "username and password", + route: "stream", + username: "admin", + password: "admin123", + wantURL: "rtsp://admin:admin123@192.168.0.10:554/stream", + }, + { + name: "empty username with password", + route: "stream", + password: "pass", + wantURL: "rtsp://:pass@192.168.0.10:554/stream", + }, + { + name: "username only", + route: "stream", + username: "user", + wantURL: "rtsp://user:@192.168.0.10:554/stream", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, gotURL, err := buildRTSPURL(stream, test.route, test.username, test.password) + require.NoError(t, err) + require.Equal(t, test.wantURL, gotURL) + }) + } +} diff --git a/internal/output/m3u.go b/internal/output/m3u.go index 07723ba..cb4f616 100644 --- a/internal/output/m3u.go +++ b/internal/output/m3u.go @@ -112,10 +112,7 @@ func formatStreamLabel(stream cameradar.Stream) string { } func formatRTSPURL(stream cameradar.Stream) string { - path := strings.TrimSpace(stream.Route()) - if path != "" && !strings.HasPrefix(path, "/") { - path = "/" + path - } + path := "/" + strings.TrimLeft(strings.TrimSpace(stream.Route()), "/") credentials := "" if stream.CredentialsFound && (stream.Username != "" || stream.Password != "") { diff --git a/internal/ui/summary.go b/internal/ui/summary.go index cf82b3a..9278146 100644 --- a/internal/ui/summary.go +++ b/internal/ui/summary.go @@ -119,10 +119,7 @@ func formatStream(stream cameradar.Stream) string { } func formatRTSPURL(stream cameradar.Stream) string { - path := stream.Route() - if path != "" && !strings.HasPrefix(path, "/") { - path = "/" + path - } + path := "/" + strings.TrimLeft(strings.TrimSpace(stream.Route()), "/") credentials := "" if stream.Username != "" || stream.Password != "" {