jan/src-tauri/utils/src/network.rs
Dinh Long Nguyen e1c8d98bf2
Backend Architecture Refactoring (#6094) (#6162)
* add llamacpp plugin

* Refactor llamacpp plugin

* add utils plugin

* remove utils folder

* add hardware implementation

* add utils folder + move utils function

* organize cargo files

* refactor utils src

* refactor util

* apply fmt

* fmt

* Update gguf + reformat

* add permission for gguf commands

* fix cargo test windows

* revert yarn lock

* remove cargo.lock for hardware plugin

* ignore cargo.lock file

* Fix hardware invoke + refactor hardware + refactor tests, constants

* use api wrapper in extension to invoke hardware call + api wrapper build integration

* add newline at EOF (per Akarshan)

* add vi mock for getSystemInfo
2025-08-15 08:59:01 +07:00

149 lines
4.5 KiB
Rust

use rand::{rngs::StdRng, Rng, SeedableRng};
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
use std::collections::{HashMap, HashSet};
use url::Url;
#[derive(serde::Deserialize, Clone, Debug)]
pub struct ProxyConfig {
pub url: String,
pub username: Option<String>,
pub password: Option<String>,
pub no_proxy: Option<Vec<String>>, // List of domains to bypass proxy
pub ignore_ssl: Option<bool>, // Ignore SSL certificate verification
}
/// Check if a port is available for binding
pub fn is_port_available(port: u16) -> bool {
std::net::TcpListener::bind(("127.0.0.1", port)).is_ok()
}
/// Generate a random port that's not in the used_ports set and is available
pub fn generate_random_port(used_ports: &HashSet<u16>) -> Result<u16, String> {
const MAX_ATTEMPTS: u32 = 20000;
let mut attempts = 0;
let mut rng = StdRng::from_entropy();
while attempts < MAX_ATTEMPTS {
let port = rng.gen_range(3000..4000);
if used_ports.contains(&port) {
attempts += 1;
continue;
}
if is_port_available(port) {
return Ok(port);
}
attempts += 1;
}
Err("Failed to find an available port for the model to load".into())
}
/// Validates proxy configuration including URL format, scheme, authentication, and no_proxy entries
pub fn validate_proxy_config(config: &ProxyConfig) -> Result<(), String> {
// Validate proxy URL format
if let Err(e) = Url::parse(&config.url) {
return Err(format!("Invalid proxy URL '{}': {}", config.url, e));
}
// Check if proxy URL has valid scheme
let url = Url::parse(&config.url).unwrap(); // Safe to unwrap as we just validated it
match url.scheme() {
"http" | "https" | "socks4" | "socks5" => {}
scheme => return Err(format!("Unsupported proxy scheme: {}", scheme)),
}
// Validate authentication credentials
if config.username.is_some() && config.password.is_none() {
return Err("Username provided without password".to_string());
}
if config.password.is_some() && config.username.is_none() {
return Err("Password provided without username".to_string());
}
// Validate no_proxy entries
if let Some(no_proxy) = &config.no_proxy {
for entry in no_proxy {
if entry.is_empty() {
return Err("Empty no_proxy entry".to_string());
}
// Basic validation for wildcard patterns
if entry.starts_with("*.") && entry.len() < 3 {
return Err(format!("Invalid wildcard pattern: {}", entry));
}
}
}
// SSL verification settings are all optional booleans, no validation needed
Ok(())
}
/// Checks if URL should bypass proxy based on no_proxy patterns (supports wildcards)
pub fn should_bypass_proxy(url: &str, no_proxy: &[String]) -> bool {
if no_proxy.is_empty() {
return false;
}
// Parse the URL to get the host
let parsed_url = match Url::parse(url) {
Ok(u) => u,
Err(_) => return false,
};
let host = match parsed_url.host_str() {
Some(h) => h,
None => return false,
};
// Check if host matches any no_proxy entry
for entry in no_proxy {
if entry == "*" {
return true;
}
// Simple wildcard matching
if entry.starts_with("*.") {
let domain = &entry[2..];
if host.ends_with(domain) {
return true;
}
} else if host == entry {
return true;
}
}
false
}
/// Creates reqwest::Proxy from ProxyConfig with authentication
pub fn create_proxy_from_config(config: &ProxyConfig) -> Result<reqwest::Proxy, String> {
// Validate the configuration first
validate_proxy_config(config)?;
let mut proxy = reqwest::Proxy::all(&config.url).map_err(|e| format!("Error: {}", e))?;
// Add authentication if provided
if let (Some(username), Some(password)) = (&config.username, &config.password) {
proxy = proxy.basic_auth(username, password);
}
Ok(proxy)
}
/// Converts HashMap<String,String> to reqwest HeaderMap
pub fn convert_headers(
headers: &HashMap<String, String>,
) -> Result<HeaderMap, Box<dyn std::error::Error>> {
let mut header_map = HeaderMap::new();
for (k, v) in headers {
let key = HeaderName::from_bytes(k.as_bytes())?;
let value = HeaderValue::from_str(v)?;
header_map.insert(key, value);
}
Ok(header_map)
}