pub(crate) fn redact_url(raw: &str) -> String {
let Some(scheme_end) = raw.find("://") else {
return raw.to_string();
};
let after_scheme = &raw[scheme_end + 3..];
let authority_end = after_scheme.find('/').unwrap_or(after_scheme.len());
let authority = &after_scheme[..authority_end];
let Some(at) = authority.rfind('@') else {
return raw.to_string();
};
let scheme = &raw[..scheme_end + 3];
let rest = &after_scheme[at + 1..];
format!("{scheme}***@{rest}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn redacts_userinfo_from_credentialed_url() {
let redacted = redact_url("rtsp://user:secretpass@host/s");
assert_eq!(redacted, "rtsp://***@host/s");
assert!(!redacted.contains("user"));
assert!(!redacted.contains("secretpass"));
}
#[test]
fn leaves_url_without_userinfo_unchanged() {
assert_eq!(redact_url("rtsp://host/s"), "rtsp://host/s");
}
#[test]
fn leaves_non_url_string_unchanged() {
assert_eq!(redact_url("not a url"), "not a url");
}
#[test]
fn does_not_mistake_a_path_at_sign_for_userinfo() {
assert_eq!(
redact_url("rtsp://host/user@host/s"),
"rtsp://host/user@host/s"
);
}
#[test]
fn redacts_username_only_credentials() {
let redacted = redact_url("rtsp://user@host/s");
assert_eq!(redacted, "rtsp://***@host/s");
assert!(!redacted.contains("user"));
}
}