pub mod auth;
pub mod config;
pub mod package_managers;
pub mod propagate;
pub mod resolve;
pub mod testing;
#[allow(unused_imports)]
pub use auth::*;
pub use config::*;
#[allow(unused_imports)]
pub use package_managers::*;
#[allow(unused_imports)]
pub use propagate::*;
#[allow(unused_imports)]
pub use resolve::*;
#[allow(unused_imports)]
pub use testing::*;
#[allow(dead_code)] pub fn redact_home(path: &str) -> String {
let home = dirs::home_dir()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default();
if !home.is_empty() && path.starts_with(&home) {
path.replacen(&home, "~", 1)
} else {
path.to_string()
}
}
#[allow(dead_code)] pub fn redact_credentials(url: &str) -> std::borrow::Cow<'_, str> {
let Some(proto_end) = url.find("://") else {
return std::borrow::Cow::Borrowed(url);
};
let after_scheme = &url[proto_end + 3..];
let authority_end = after_scheme
.find(['/', '?', '#'])
.unwrap_or(after_scheme.len());
let authority = &after_scheme[..authority_end];
let Some(at_offset_in_authority) = authority.find('@') else {
return std::borrow::Cow::Borrowed(url);
};
let proto_part = &url[..proto_end + 3];
let creds_part = &authority[..at_offset_in_authority];
let after_at = &url[proto_end + 3 + at_offset_in_authority..];
let redacted = match creds_part.find(':') {
Some(colon) => {
let username = &creds_part[..colon];
if username.is_empty() {
format!("{}:***{}", proto_part, after_at)
} else {
format!("{}{}:***{}", proto_part, username, after_at)
}
}
None => format!("{}***{}", proto_part, after_at),
};
std::borrow::Cow::Owned(redacted)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_network_config_default() {
let config = NetworkConfig::default();
assert!(config.http_proxy.is_none());
assert!(config.https_proxy.is_none());
assert!(config.socks_proxy.is_none());
assert!(config.no_proxy.is_none());
}
#[test]
fn test_proxy_url_redaction() {
let url = "http://user:secret@proxy.corp.com:8080";
let redacted = redact_credentials(url);
assert!(redacted.contains("user:***"));
assert!(!redacted.contains("secret"));
}
#[test]
fn redact_userinfo_only_token() {
let url = "https://ghp_abcd1234@github.com/owner/repo.git";
let redacted = redact_credentials(url);
assert_eq!(
&*redacted, "https://***@github.com/owner/repo.git",
"userinfo-only token must be fully redacted"
);
assert!(!redacted.contains("ghp_abcd1234"));
}
#[test]
fn redact_password_only() {
let url = "http://:secret@host:8080";
let redacted = redact_credentials(url);
assert_eq!(&*redacted, "http://:***@host:8080");
}
#[test]
fn redact_no_credentials_borrows() {
let url = "https://github.com/owner/repo.git";
let redacted = redact_credentials(url);
assert!(matches!(redacted, std::borrow::Cow::Borrowed(_)));
assert_eq!(&*redacted, url);
}
#[test]
fn redact_does_not_target_at_in_path() {
let url = "https://github.com/owner/repo/blob/main/file.txt?ref=abc@v1";
let redacted = redact_credentials(url);
assert_eq!(&*redacted, url);
}
#[test]
fn redact_socks5() {
let url = "socks5://u:p@host:1080";
let redacted = redact_credentials(url);
assert_eq!(&*redacted, "socks5://u:***@host:1080");
}
#[test]
fn redact_unparseable_returns_original() {
let weird = "not://valid".replace("://", "");
let redacted = redact_credentials(&weird);
assert_eq!(&*redacted, &weird);
}
#[test]
fn test_no_proxy_parsing() {
let no_proxy = NoProxy::String("localhost,127.0.0.1,.corp.com".to_string());
let hosts = no_proxy.to_hosts();
assert_eq!(hosts.len(), 3);
assert!(hosts.contains(&"localhost".to_string()));
assert!(hosts.contains(&"127.0.0.1".to_string()));
assert!(hosts.contains(&".corp.com".to_string()));
}
#[test]
fn test_no_proxy_array() {
let no_proxy = NoProxy::Array(vec!["localhost".to_string(), "127.0.0.1".to_string()]);
let hosts = no_proxy.to_hosts();
assert_eq!(hosts.len(), 2);
}
#[test]
fn test_password_source_variants() {
let plain = PasswordSource::Plain("secret".to_string());
assert!(matches!(plain, PasswordSource::Plain(_)));
let env = PasswordSource::Env("PROXY_PASSWORD".to_string());
assert!(matches!(env, PasswordSource::Env(_)));
let file = PasswordSource::File("/path/to/password".to_string());
assert!(matches!(file, PasswordSource::File(_)));
let prompt = PasswordSource::Prompt;
assert!(matches!(prompt, PasswordSource::Prompt));
}
}