use url::Url;
pub fn redact_url_credentials_for_logs(input: &str) -> String {
if let Ok(mut u) = Url::parse(input) {
let has_user = !u.username().is_empty();
let has_pass = u.password().is_some();
if has_user || has_pass {
let _ = u.set_username("");
let _ = u.set_password(None);
return u.to_string();
}
}
heuristic_redact_userinfo(input)
}
pub fn redact_url_if_echoed_in_text(err_text: &str, raw_connection_url: &str) -> String {
let redacted = redact_url_credentials_for_logs(raw_connection_url);
if raw_connection_url == redacted || !err_text.contains(raw_connection_url) {
return err_text.to_string();
}
err_text.replace(raw_connection_url, &redacted)
}
fn heuristic_redact_userinfo(s: &str) -> String {
let Some(pos) = s.find("://") else {
return s.to_string();
};
let after_scheme = &s[pos + 3..];
let Some(at_rel) = after_scheme.find('@') else {
return s.to_string();
};
format!("{}<redacted>{}", &s[..pos + 3], &after_scheme[at_rel + 1..])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn leaves_bare_nats_url_unchanged() {
assert_eq!(
redact_url_credentials_for_logs("nats://127.0.0.1:4222"),
"nats://127.0.0.1:4222"
);
}
#[test]
fn strips_user_password_nats() {
let out = redact_url_credentials_for_logs("nats://alice:secret@broker.internal:4222");
assert!(!out.contains("alice"), "{out}");
assert!(!out.contains("secret"), "{out}");
assert!(out.contains("broker.internal"), "{out}");
}
#[test]
fn heuristic_when_not_parseable() {
let out = heuristic_redact_userinfo("nats://x:y@host:1/extra");
assert_eq!(out, "nats://<redacted>host:1/extra");
}
#[test]
fn redacts_echoed_url_in_error_blob() {
let raw = "nats://alice:hunter2@10.0.0.5:4222";
let err = format!("connection refused: {raw} (try again)");
let out = redact_url_if_echoed_in_text(&err, raw);
assert!(!out.contains("alice"), "{out}");
assert!(!out.contains("hunter2"), "{out}");
assert!(out.contains("10.0.0.5"), "{out}");
}
#[test]
fn leaves_error_unchanged_when_url_not_echoed() {
let err = "IO error: connection refused";
assert_eq!(
redact_url_if_echoed_in_text(err, "nats://u:p@h:1"),
err.to_string()
);
}
}