use super::config::{NetworkConfig, ProxyAuth};
use std::io;
#[allow(dead_code)] pub fn inject_credentials(proxy_url: &str, auth: &ProxyAuth) -> Result<String, String> {
let password = auth.password.resolve()?;
if let Some(proto_end) = proxy_url.find("://") {
let protocol = &proxy_url[..proto_end + 3];
let rest = &proxy_url[proto_end + 3..];
let encoded_user = urlencoding::encode(&auth.username);
let encoded_pass = urlencoding::encode(&password);
Ok(format!(
"{}{}:{}@{}",
protocol, encoded_user, encoded_pass, rest
))
} else {
Err(format!("Invalid proxy URL format: {}", proxy_url))
}
}
#[allow(dead_code)] pub fn prompt_password(prompt: &str) -> io::Result<String> {
inquire::Password::new(prompt)
.without_confirmation()
.prompt()
.map_err(|e| io::Error::other(e.to_string()))
}
#[allow(dead_code)] pub fn get_authenticated_proxy(
proxy_url: Option<&String>,
auth: Option<&ProxyAuth>,
) -> Result<Option<String>, String> {
match (proxy_url, auth) {
(Some(url), Some(auth)) => {
if url.contains('@') {
Ok(Some(url.clone()))
} else {
inject_credentials(url, auth).map(Some)
}
}
(Some(url), None) => Ok(Some(url.clone())),
(None, _) => Ok(None),
}
}
#[allow(dead_code)] pub struct AuthenticatedProxies {
pub http_proxy: Option<String>,
pub https_proxy: Option<String>,
pub socks_proxy: Option<String>,
}
impl AuthenticatedProxies {
#[allow(dead_code)] pub fn from_config(config: &NetworkConfig) -> Result<Self, String> {
Ok(Self {
http_proxy: get_authenticated_proxy(config.http_proxy.as_ref(), config.auth.as_ref())?,
https_proxy: get_authenticated_proxy(
config.https_proxy.as_ref(),
config.auth.as_ref(),
)?,
socks_proxy: get_authenticated_proxy(
config.socks_proxy.as_ref(),
config.auth.as_ref(),
)?,
})
}
}
mod urlencoding {
#[allow(dead_code)] pub fn encode(input: &str) -> String {
let mut result = String::new();
for c in input.chars() {
match c {
'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' | '~' => {
result.push(c);
}
_ => {
for byte in c.to_string().as_bytes() {
result.push_str(&format!("%{:02X}", byte));
}
}
}
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::network::config::PasswordSource;
#[test]
fn test_inject_credentials() {
let auth = ProxyAuth {
username: "user".to_string(),
password: PasswordSource::Plain("pass".to_string()),
};
let result = inject_credentials("http://proxy:8080", &auth).unwrap();
assert_eq!(result, "http://user:pass@proxy:8080");
}
#[test]
fn test_inject_credentials_with_special_chars() {
let auth = ProxyAuth {
username: "user@corp".to_string(),
password: PasswordSource::Plain("p@ss:word".to_string()),
};
let result = inject_credentials("http://proxy:8080", &auth).unwrap();
assert!(result.contains("%40")); }
#[test]
fn test_url_encoding() {
assert_eq!(urlencoding::encode("user"), "user");
assert_eq!(urlencoding::encode("user@corp"), "user%40corp");
assert_eq!(urlencoding::encode("p@ss:word"), "p%40ss%3Aword");
}
#[test]
fn test_get_authenticated_proxy_no_auth() {
let url = Some("http://proxy:8080".to_string());
let result = get_authenticated_proxy(url.as_ref(), None).unwrap();
assert_eq!(result, Some("http://proxy:8080".to_string()));
}
#[test]
fn test_get_authenticated_proxy_already_has_creds() {
let url = Some("http://user:pass@proxy:8080".to_string());
let auth = ProxyAuth {
username: "other".to_string(),
password: PasswordSource::Plain("other".to_string()),
};
let result = get_authenticated_proxy(url.as_ref(), Some(&auth)).unwrap();
assert_eq!(result, Some("http://user:pass@proxy:8080".to_string()));
}
}