8531c006d4
* 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>
154 lines
4.0 KiB
Go
154 lines
4.0 KiB
Go
package ui
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/Ullaakut/cameradar/v6"
|
|
)
|
|
|
|
// FormatSummary builds a human-readable summary of discovered streams.
|
|
func FormatSummary(streams []cameradar.Stream, _ error) string {
|
|
accessible, others := partitionStreams(streams)
|
|
|
|
var builder strings.Builder
|
|
builder.WriteString(fmt.Sprintf("Accessible streams: %d\n", len(accessible)))
|
|
if len(accessible) == 0 {
|
|
builder.WriteString("• None\n")
|
|
} else {
|
|
for _, stream := range accessible {
|
|
builder.WriteString(formatStream(stream))
|
|
}
|
|
}
|
|
|
|
if len(others) > 0 {
|
|
builder.WriteString("\n")
|
|
builder.WriteString(fmt.Sprintf("Other discovered streams: %d\n", len(others)))
|
|
for _, stream := range others {
|
|
builder.WriteString(formatStream(stream))
|
|
}
|
|
}
|
|
|
|
return builder.String()
|
|
}
|
|
|
|
func partitionStreams(streams []cameradar.Stream) ([]cameradar.Stream, []cameradar.Stream) {
|
|
var accessible []cameradar.Stream
|
|
var others []cameradar.Stream
|
|
for _, stream := range streams {
|
|
if stream.Available {
|
|
accessible = append(accessible, stream)
|
|
} else {
|
|
others = append(others, stream)
|
|
}
|
|
}
|
|
|
|
// Sort streams by address and port.
|
|
sort.Slice(accessible, func(i, j int) bool {
|
|
if accessible[i].Address.String() == accessible[j].Address.String() {
|
|
return accessible[i].Port < accessible[j].Port
|
|
}
|
|
return accessible[i].Address.String() < accessible[j].Address.String()
|
|
})
|
|
sort.Slice(others, func(i, j int) bool {
|
|
if others[i].Address.String() == others[j].Address.String() {
|
|
return others[i].Port < others[j].Port
|
|
}
|
|
return others[i].Address.String() < others[j].Address.String()
|
|
})
|
|
|
|
return accessible, others
|
|
}
|
|
|
|
func formatStream(stream cameradar.Stream) string {
|
|
var builder strings.Builder
|
|
builder.WriteString("• ")
|
|
builder.WriteString(stream.Address.String())
|
|
builder.WriteString(":")
|
|
builder.WriteString(strconv.FormatUint(uint64(stream.Port), 10))
|
|
|
|
if stream.Device != "" {
|
|
builder.WriteString(" (")
|
|
builder.WriteString(stream.Device)
|
|
builder.WriteString(")")
|
|
}
|
|
builder.WriteString("\n")
|
|
|
|
builder.WriteString(" Authentication: ")
|
|
builder.WriteString(authTypeLabel(stream.AuthenticationType))
|
|
builder.WriteString("\n")
|
|
|
|
if len(stream.Routes) > 0 {
|
|
builder.WriteString(" Routes: ")
|
|
builder.WriteString(strings.Join(stream.Routes, ", "))
|
|
builder.WriteString("\n")
|
|
} else {
|
|
builder.WriteString(" Routes: not found\n")
|
|
}
|
|
|
|
if stream.CredentialsFound || stream.AuthenticationType == cameradar.AuthNone {
|
|
builder.WriteString(" Credentials: ")
|
|
builder.WriteString(formatCredentials(stream))
|
|
builder.WriteString("\n")
|
|
} else {
|
|
builder.WriteString(" Credentials: not found\n")
|
|
}
|
|
|
|
builder.WriteString(" Availability: ")
|
|
if stream.Available {
|
|
builder.WriteString("yes\n")
|
|
} else {
|
|
builder.WriteString("no\n")
|
|
}
|
|
|
|
if stream.RouteFound && (stream.CredentialsFound || stream.AuthenticationType == cameradar.AuthNone) {
|
|
builder.WriteString(" RTSP URL: ")
|
|
builder.WriteString(formatRTSPURL(stream))
|
|
builder.WriteString("\n")
|
|
}
|
|
|
|
builder.WriteString(" Admin panel: ")
|
|
builder.WriteString(formatAdminPanelURL(stream))
|
|
builder.WriteString("\n")
|
|
|
|
return builder.String()
|
|
}
|
|
|
|
func formatRTSPURL(stream cameradar.Stream) string {
|
|
path := "/" + strings.TrimLeft(strings.TrimSpace(stream.Route()), "/")
|
|
|
|
credentials := ""
|
|
if stream.Username != "" || stream.Password != "" {
|
|
credentials = stream.Username + ":" + stream.Password + "@"
|
|
}
|
|
|
|
return fmt.Sprintf("rtsp://%s%s:%d%s", credentials, stream.Address.String(), stream.Port, path)
|
|
}
|
|
|
|
func formatAdminPanelURL(stream cameradar.Stream) string {
|
|
return fmt.Sprintf("http://%s/", stream.Address.String())
|
|
}
|
|
|
|
func formatCredentials(stream cameradar.Stream) string {
|
|
if stream.Username == "" && stream.Password == "" {
|
|
return "none"
|
|
}
|
|
|
|
return stream.Username + ":" + stream.Password
|
|
}
|
|
|
|
func authTypeLabel(auth cameradar.AuthType) string {
|
|
switch auth {
|
|
case cameradar.AuthNone:
|
|
return "none"
|
|
case cameradar.AuthBasic:
|
|
return "basic"
|
|
case cameradar.AuthDigest:
|
|
return "digest"
|
|
default:
|
|
return fmt.Sprintf("unknown(%d)", auth)
|
|
}
|
|
}
|