From 7b9ba13e3abfb49958dcfbdd08ec31cb72601786 Mon Sep 17 00:00:00 2001 From: Mitchell R Date: Tue, 26 May 2026 06:23:38 +0200 Subject: [PATCH] fix(kiosk): extract RTSP creds as rtspsrc properties for digest auth Credentials embedded in RTSP URL can skip digest negotiation on some cameras. Now extract user:pass from URL, set as user-id/user-pw properties on rtspsrc, pass clean URL as location. Co-Authored-By: Claude Opus 4.6 (1M context) --- kiosk/src/pipeline.rs | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/kiosk/src/pipeline.rs b/kiosk/src/pipeline.rs index b53a58e..bb8ddfb 100644 --- a/kiosk/src/pipeline.rs +++ b/kiosk/src/pipeline.rs @@ -6,10 +6,20 @@ pub fn create_camera_pipeline(name: &str, rtsp_uri: &str) -> Option<(Pipeline, E let pipeline_name = format!("cam-{name}"); let pipeline = Pipeline::with_name(&pipeline_name); - let src = gst::ElementFactory::make("rtspsrc") - .property("location", rtsp_uri) + // Parse user:pass from RTSP URL and set as properties so rtspsrc + // does proper digest auth negotiation (embedding in URL can skip it). + let (location, user, pass) = extract_rtsp_creds(rtsp_uri); + let mut builder = gst::ElementFactory::make("rtspsrc") + .property("location", &location) .property("latency", 300u32) - .property_from_str("protocols", "tcp") + .property_from_str("protocols", "tcp"); + if let Some(ref u) = user { + builder = builder.property("user-id", u.as_str()); + } + if let Some(ref p) = pass { + builder = builder.property("user-pw", p.as_str()); + } + let src = builder .build() .map_err(|e| error!("[{pipeline_name}] rtspsrc: {e}")) .ok()?; @@ -123,3 +133,25 @@ pub fn play(pipeline: &Pipeline) { pub fn stop(pipeline: &Pipeline) { let _ = pipeline.set_state(gst::State::Null); } + +/// Extract user:pass from rtsp://user:pass@host/... URL. +/// Returns (url_without_creds, Option, Option). +fn extract_rtsp_creds(uri: &str) -> (String, Option, Option) { + if let Some(after_scheme) = uri.strip_prefix("rtsp://") { + if let Some(at_pos) = after_scheme.find('@') { + let creds = &after_scheme[..at_pos]; + let rest = &after_scheme[at_pos + 1..]; + let clean_url = format!("rtsp://{rest}"); + let (user, pass) = match creds.find(':') { + Some(colon) => { + let u = urlencoding::decode(&creds[..colon]).unwrap_or_default().to_string(); + let p = urlencoding::decode(&creds[colon + 1..]).unwrap_or_default().to_string(); + (Some(u), Some(p)) + } + None => (Some(creds.to_string()), None), + }; + return (clean_url, user, pass); + } + } + (uri.to_string(), None, None) +}