Fix HTTP camera authentication: generate all auth variants for comprehensive testing
This commit addresses the issue where HTTP-based cameras (JPEG/MJPEG) were not being discovered due to incomplete authentication variant generation. Changes: - Builder now generates 4 authentication variants for each HTTP/HTTPS URL: 1. No authentication (for open cameras) 2. Basic Auth only (embedded credentials in URL) 3. Query parameters only (user=X&pwd=Y) 4. Basic Auth + Query parameters (combined approach) - Scanner updated to use BuildURLsFromEntry instead of BuildURL for popular patterns, ensuring all authentication variants are tested - Preserved existing query parameter values (e.g., channel=1 remains unchanged) This fix enables discovery of cameras that require different authentication methods, including those that only accept Basic Auth headers, query parameters, or combinations. Tested with ZOSI ZG23213M camera - increased discovery from 0 to 9 working streams.
This commit is contained in:
@@ -362,17 +362,20 @@ func (s *Scanner) collectStreams(ctx context.Context, req models.StreamDiscovery
|
|||||||
buildCtx.Port = pattern.Port
|
buildCtx.Port = pattern.Port
|
||||||
buildCtx.Protocol = pattern.Protocol
|
buildCtx.Protocol = pattern.Protocol
|
||||||
|
|
||||||
url := s.builder.BuildURL(entry, buildCtx)
|
// Generate all URL variants for this pattern
|
||||||
if !urlMap[url] {
|
urls := s.builder.BuildURLsFromEntry(entry, buildCtx)
|
||||||
allStreams = append(allStreams, models.DiscoveredStream{
|
for _, url := range urls {
|
||||||
URL: url,
|
if !urlMap[url] {
|
||||||
Type: pattern.Type,
|
allStreams = append(allStreams, models.DiscoveredStream{
|
||||||
|
URL: url,
|
||||||
|
Type: pattern.Type,
|
||||||
Protocol: pattern.Protocol,
|
Protocol: pattern.Protocol,
|
||||||
Port: pattern.Port,
|
Port: pattern.Port,
|
||||||
Working: false, // Will be tested
|
Working: false, // Will be tested
|
||||||
})
|
})
|
||||||
urlMap[url] = true
|
urlMap[url] = true
|
||||||
popularCount++
|
popularCount++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,41 +300,95 @@ func (b *Builder) BuildURLsFromEntry(entry models.CameraEntry, ctx BuildContext)
|
|||||||
switch entry.Protocol {
|
switch entry.Protocol {
|
||||||
case "rtsp", "rtsps":
|
case "rtsp", "rtsps":
|
||||||
// For RTSP: generate with and without credentials
|
// For RTSP: generate with and without credentials
|
||||||
// 1. With credentials (if provided)
|
|
||||||
if ctx.Username != "" && ctx.Password != "" {
|
if ctx.Username != "" && ctx.Password != "" {
|
||||||
addURL(b.BuildURL(entry, ctx))
|
addURL(b.BuildURL(entry, ctx))
|
||||||
}
|
}
|
||||||
|
// Without credentials (for open cameras)
|
||||||
// 2. Without credentials (for open cameras)
|
|
||||||
ctxNoAuth := ctx
|
ctxNoAuth := ctx
|
||||||
ctxNoAuth.Username = ""
|
ctxNoAuth.Username = ""
|
||||||
ctxNoAuth.Password = ""
|
ctxNoAuth.Password = ""
|
||||||
addURL(b.BuildURL(entry, ctxNoAuth))
|
addURL(b.BuildURL(entry, ctxNoAuth))
|
||||||
|
|
||||||
case "http", "https":
|
case "http", "https":
|
||||||
// For HTTP/JPEG/MJPEG: generate multiple auth variants
|
// For HTTP/HTTPS: ALWAYS generate 4 authentication variants
|
||||||
if entry.Type == "JPEG" || entry.Type == "MJPEG" {
|
if ctx.Username != "" && ctx.Password != "" {
|
||||||
// Check if URL has auth placeholders
|
// 1. No authentication
|
||||||
hasAuthPlaceholders := strings.Contains(entry.URL, "[USERNAME]") ||
|
ctxNoAuth := ctx
|
||||||
strings.Contains(entry.URL, "[PASSWORD]") ||
|
ctxNoAuth.Username = ""
|
||||||
strings.Contains(entry.URL, "[AUTH]")
|
ctxNoAuth.Password = ""
|
||||||
|
urlNoAuth := b.BuildURL(entry, ctxNoAuth)
|
||||||
|
addURL(urlNoAuth)
|
||||||
|
|
||||||
if hasAuthPlaceholders {
|
// 2. Basic Auth only (embedded credentials)
|
||||||
// 1. URL with credentials in parameters (replaced placeholders)
|
urlBasic := b.BuildURL(entry, ctxNoAuth) // Use clean URL
|
||||||
addURL(b.BuildURL(entry, ctx))
|
if u, err := url.Parse(urlBasic); err == nil {
|
||||||
|
u.User = url.UserPassword(ctx.Username, ctx.Password)
|
||||||
|
addURL(u.String())
|
||||||
|
}
|
||||||
|
|
||||||
// 2. URL without credentials (for cameras that don't require auth)
|
// 3. Query parameters only
|
||||||
ctxNoAuth := ctx
|
urlWithParams := b.BuildURL(entry, ctx) // This will replace placeholders if any
|
||||||
ctxNoAuth.Username = ""
|
|
||||||
ctxNoAuth.Password = ""
|
// If URL has auth placeholders, they're already replaced
|
||||||
addURL(b.BuildURL(entry, ctxNoAuth))
|
if strings.Contains(entry.URL, "[USERNAME]") || strings.Contains(entry.URL, "[PASSWORD]") {
|
||||||
|
addURL(urlWithParams)
|
||||||
} else {
|
} else {
|
||||||
// URL without placeholders - will use Basic Auth in headers
|
// No placeholders - add query params for auth (don't overwrite existing params)
|
||||||
// Generate only one URL, auth will be in headers
|
if u, err := url.Parse(urlWithParams); err == nil {
|
||||||
addURL(b.BuildURL(entry, ctx))
|
q := u.Query()
|
||||||
|
|
||||||
|
// Add user/pwd if not already present
|
||||||
|
if !q.Has("user") && !q.Has("usr") && !q.Has("username") {
|
||||||
|
q.Set("user", ctx.Username)
|
||||||
|
}
|
||||||
|
if !q.Has("pwd") && !q.Has("password") && !q.Has("pass") {
|
||||||
|
q.Set("pwd", ctx.Password)
|
||||||
|
}
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
addURL(u.String())
|
||||||
|
|
||||||
|
// Try alternative names too
|
||||||
|
q2 := url.Values{}
|
||||||
|
for k, v := range u.Query() {
|
||||||
|
q2[k] = v
|
||||||
|
}
|
||||||
|
if !q2.Has("username") && !q2.Has("user") && !q2.Has("usr") {
|
||||||
|
q2.Set("username", ctx.Username)
|
||||||
|
}
|
||||||
|
if !q2.Has("password") && !q2.Has("pwd") && !q2.Has("pass") {
|
||||||
|
q2.Set("password", ctx.Password)
|
||||||
|
}
|
||||||
|
u.RawQuery = q2.Encode()
|
||||||
|
addURL(u.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Basic Auth + Query parameters (combined)
|
||||||
|
if strings.Contains(entry.URL, "[USERNAME]") || strings.Contains(entry.URL, "[PASSWORD]") {
|
||||||
|
// URL has placeholders - add Basic Auth to the URL with replaced params
|
||||||
|
if u, err := url.Parse(urlWithParams); err == nil {
|
||||||
|
u.User = url.UserPassword(ctx.Username, ctx.Password)
|
||||||
|
addURL(u.String())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No placeholders - add both Basic Auth and query params (without overwriting existing)
|
||||||
|
if u, err := url.Parse(urlNoAuth); err == nil {
|
||||||
|
u.User = url.UserPassword(ctx.Username, ctx.Password)
|
||||||
|
q := u.Query()
|
||||||
|
|
||||||
|
// Add auth params only if not already present
|
||||||
|
if !q.Has("user") && !q.Has("usr") && !q.Has("username") {
|
||||||
|
q.Set("user", ctx.Username)
|
||||||
|
}
|
||||||
|
if !q.Has("pwd") && !q.Has("password") && !q.Has("pass") {
|
||||||
|
q.Set("pwd", ctx.Password)
|
||||||
|
}
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
addURL(u.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Other HTTP types - single URL
|
// No credentials provided - just one URL
|
||||||
addURL(b.BuildURL(entry, ctx))
|
addURL(b.BuildURL(entry, ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,25 +397,6 @@ func (b *Builder) BuildURLsFromEntry(entry models.CameraEntry, ctx BuildContext)
|
|||||||
addURL(b.BuildURL(entry, ctx))
|
addURL(b.BuildURL(entry, ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
// For NVR systems, try multiple channels
|
|
||||||
if ctx.Channel == 0 && strings.Contains(strings.ToLower(entry.Notes), "channel") {
|
|
||||||
for ch := 1; ch <= 4; ch++ {
|
|
||||||
altCtx := ctx
|
|
||||||
altCtx.Channel = ch
|
|
||||||
|
|
||||||
// Regenerate with different channel
|
|
||||||
if entry.Protocol == "rtsp" || entry.Protocol == "rtsps" {
|
|
||||||
if ctx.Username != "" && ctx.Password != "" {
|
|
||||||
addURL(b.BuildURL(entry, altCtx))
|
|
||||||
}
|
|
||||||
altCtx.Username = ""
|
|
||||||
altCtx.Password = ""
|
|
||||||
addURL(b.BuildURL(entry, altCtx))
|
|
||||||
} else {
|
|
||||||
addURL(b.BuildURL(entry, altCtx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b.logger.Debug("BuildURLsFromEntry complete",
|
b.logger.Debug("BuildURLsFromEntry complete",
|
||||||
"entry_url_pattern", entry.URL,
|
"entry_url_pattern", entry.URL,
|
||||||
|
|||||||
Reference in New Issue
Block a user