Files
Lawrence Arryl Lopez 8531c006d4 feat: support http tunneled rtsp (#419)
* enhancement: supporting http tunneled rtsp

* refactor: simplify HTTP tunnel support per review feedback

- Extract streamCandidate() for nmap port classification
- Add isCommonHTTPPort() for masscan and nmap fallback
- Move URL building to Stream.String() and Stream.URL()
- Pass Stream directly to attack methods instead of individual args
- Add TLS config for HTTPS tunnel support
- Make auth detection non-fatal for tunneled streams
- Rename HTTPTunnel to UseHTTPTunnel

* - Testing the auth workflow for the tunneled streams is not blocking the rest of the pipeline since I changed the return values to Auth unknown and nil
- added extra ports in the test according to suggestions

* fixing some lint errors

* removing the unused buildrtspurl

* delayed the urlstream call for clarity

removed error messages

refactored the test that used the deprecated buildTRSPurl to use stream.URL and stream.String() methods

* extracting iscommonHTTP port to pkg/ports (package ports)

switching on u.scheme to create proper schemes for http and https

* refactor: replace HTTP tunnel bool with scheme-based detection; enable TLS only for HTTPS-tunneled streams

* chore: simnplify InferTunnelScheme and newRTSPClient

* fix: remove rendundant check in streamCandidate

* fix: typo in parseScheme

* tests: coverage for new schemes

* fix: use RTSP and not RTSPS for HTTPS URLs

* fix: tunneled RTSP scheme handling and auth detection fallback

* ui: render empty credentials as none in summary and TUI

* chore: ignore duplicate-string warning for none literal

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: rtsps probe headers

---------

Co-authored-by: Brendan Le Glaunec <brendan@glaulabs.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-18 06:41:20 +01:00

131 lines
3.2 KiB
Go

package ui_test
import (
"errors"
"net/netip"
"strings"
"testing"
"github.com/Ullaakut/cameradar/v6"
"github.com/Ullaakut/cameradar/v6/internal/ui"
"github.com/stretchr/testify/assert"
)
func TestFormatSummary(t *testing.T) {
tests := []struct {
name string
streams []cameradar.Stream
err error
wantContains []string
wantNotContains []string
orderedPairs [][2]string
}{
{
name: "empty",
streams: nil,
wantContains: []string{
"Accessible streams: 0",
"• None",
},
wantNotContains: []string{
"Other discovered streams",
"Error:",
},
},
{
name: "mixed streams with error",
streams: []cameradar.Stream{
{
Device: "Model B",
Address: netip.MustParseAddr("10.0.0.2"),
Port: 554,
Available: true,
AuthenticationType: cameradar.AuthNone,
},
{
Device: "Model A",
Address: netip.MustParseAddr("10.0.0.1"),
Port: 8554,
Available: true,
Routes: []string{"stream1", "stream2"},
RouteFound: true,
CredentialsFound: true,
Username: "user",
Password: "pass",
AuthenticationType: cameradar.AuthBasic,
},
{
Address: netip.MustParseAddr("10.0.0.3"),
Port: 554,
Available: false,
AuthenticationType: cameradar.AuthDigest,
},
},
err: errors.New("boom"),
wantContains: []string{
"Accessible streams: 2",
"Other discovered streams: 1",
"• 10.0.0.1:8554 (Model A)",
"• 10.0.0.2:554 (Model B)",
"• 10.0.0.3:554",
"Authentication: basic",
"Authentication: none",
"Authentication: digest",
"Routes: stream1, stream2",
"Credentials: user:pass",
"Credentials: none",
"RTSP URL: rtsp://user:pass@10.0.0.1:8554/stream1",
"Admin panel: http://10.0.0.1/",
"Admin panel: http://10.0.0.2/",
},
wantNotContains: []string{
"Error:",
},
orderedPairs: [][2]string{
{"• 10.0.0.1:8554", "• 10.0.0.2:554"},
},
},
{
name: "empty discovered credentials render as none",
streams: []cameradar.Stream{
{
Address: netip.MustParseAddr("10.0.0.4"),
Port: 554,
Available: true,
RouteFound: true,
Routes: []string{"stream"},
CredentialsFound: true,
AuthenticationType: cameradar.AuthNone,
},
},
wantContains: []string{
"Accessible streams: 1",
"Credentials: none",
"RTSP URL: rtsp://10.0.0.4:554/stream",
},
wantNotContains: []string{
"Credentials: :",
"rtsp://:@10.0.0.4:554/stream",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got := ui.FormatSummary(test.streams, test.err)
for _, expected := range test.wantContains {
assert.Contains(t, got, expected)
}
for _, unexpected := range test.wantNotContains {
assert.NotContains(t, got, unexpected)
}
for _, pair := range test.orderedPairs {
first := strings.Index(got, pair[0])
second := strings.Index(got, pair[1])
assert.True(t, first >= 0 && second >= 0 && first < second)
}
})
}
}