Files
downloadwf/backend/src/config.rs
T
2026-08-25 12:20:31 +02:00

152 lines
4.9 KiB
Rust

use std::path::PathBuf;
const MAX_TTL_MS: i64 = 48 * 60 * 60 * 1000; // 48 hours hard maximum
#[derive(Clone, Debug)]
pub struct Config {
pub host: String,
pub port: u16,
pub storage_dir: PathBuf,
pub static_dir: PathBuf,
pub max_file_size: u64,
pub file_ttl_ms: i64,
pub max_storage_usage: f64,
pub key_length: usize,
pub public_base_url: Option<String>,
pub rate_limit_enabled: bool,
pub rate_limit_max: usize,
pub rate_limit_window_ms: u64,
pub trust_proxy: bool,
}
pub fn parse_size(value: &str) -> Result<u64, String> {
let s = value.trim();
let split = s.find(|c: char| !c.is_ascii_digit() && c != '.');
let (num_part, unit_part) = match split {
Some(i) => (&s[..i], &s[i..]),
None => (s, ""),
};
let num: f64 = num_part
.trim()
.parse()
.map_err(|_| format!("Invalid size value: \"{value}\""))?;
let unit = unit_part.trim().to_uppercase();
let factor: f64 = match unit.as_str() {
"B" | "" => 1.0,
"K" | "KB" | "KIB" => 1024.0,
"M" | "MB" | "MIB" => 1024.0f64.powi(2),
"G" | "GB" | "GIB" => 1024.0f64.powi(3),
"T" | "TB" | "TIB" => 1024.0f64.powi(4),
_ => return Err(format!("Invalid size unit in: \"{value}\"")),
};
Ok((num * factor) as u64)
}
fn env(name: &str) -> Option<String> {
std::env::var(name).ok().filter(|v| !v.is_empty())
}
fn bool_from_env(value: Option<&str>, fallback: bool) -> bool {
match value {
Some(v) => !["0", "false", "no", "off"].contains(&v.trim().to_lowercase().as_str()),
None => fallback,
}
}
pub fn load() -> Result<Config, String> {
let port = env("PORT")
.unwrap_or_else(|| "3000".to_string())
.parse::<u16>()
.map_err(|_| format!("Invalid PORT: {:?}", env("PORT")))?;
if port == 0 {
return Err("Invalid PORT".to_string());
}
let host = env("HOST").unwrap_or_else(|| "0.0.0.0".to_string());
let storage_dir = PathBuf::from(env("STORAGE_DIR").unwrap_or_else(|| "./storage".to_string()));
let static_dir = PathBuf::from(env("STATIC_DIR").unwrap_or_else(|| "./frontend/dist".to_string()));
let max_file_size = parse_size(
&env("MAX_FILE_SIZE").unwrap_or_else(|| "10GB".to_string()),
)?;
if max_file_size < 1024 {
return Err("MAX_FILE_SIZE must be at least 1KB".to_string());
}
let file_ttl_ms_raw = env("FILE_TTL_MS")
.unwrap_or_else(|| MAX_TTL_MS.to_string())
.parse::<i64>()
.map_err(|_| "Invalid FILE_TTL_MS".to_string())?;
if file_ttl_ms_raw < 1 {
return Err("Invalid FILE_TTL_MS".to_string());
}
let file_ttl_ms = file_ttl_ms_raw.min(MAX_TTL_MS);
let max_storage_usage = env("MAX_STORAGE_USAGE")
.unwrap_or_else(|| "0.5".to_string())
.parse::<f64>()
.map_err(|_| "Invalid MAX_STORAGE_USAGE".to_string())?;
if !(0.0..=1.0).contains(&max_storage_usage) {
return Err("Invalid MAX_STORAGE_USAGE".to_string());
}
let key_length = env("KEY_LENGTH")
.unwrap_or_else(|| "8".to_string())
.parse::<usize>()
.map_err(|_| "Invalid KEY_LENGTH".to_string())?;
if !(4..=64).contains(&key_length) {
return Err("Invalid KEY_LENGTH".to_string());
}
let rate_limit_max = env("RATE_LIMIT_MAX")
.unwrap_or_else(|| "300".to_string())
.parse::<usize>()
.map_err(|_| "Invalid RATE_LIMIT_MAX".to_string())?;
if rate_limit_max < 1 {
return Err("Invalid RATE_LIMIT_MAX".to_string());
}
let rate_limit_window_ms = env("RATE_LIMIT_WINDOW_MS")
.unwrap_or_else(|| "60000".to_string())
.parse::<u64>()
.map_err(|_| "Invalid RATE_LIMIT_WINDOW_MS".to_string())?;
if rate_limit_window_ms < 1000 {
return Err("Invalid RATE_LIMIT_WINDOW_MS".to_string());
}
let trust_proxy = bool_from_env(env("TRUST_PROXY").as_deref(), false);
let rate_limit_enabled = bool_from_env(env("RATE_LIMIT_ENABLED").as_deref(), true);
let public_base_url = match env("PUBLIC_BASE_URL") {
Some(raw) => {
let normalized = raw.trim_end_matches('/').to_string();
let valid = (normalized.starts_with("http://") || normalized.starts_with("https://"))
&& !normalized[normalized.find("://").map(|i| i + 3).unwrap_or(0)..]
.contains('/')
&& !normalized.chars().any(char::is_whitespace);
if !valid {
return Err(format!("Invalid PUBLIC_BASE_URL: \"{raw}\""));
}
Some(normalized)
}
None => None,
};
Ok(Config {
host,
port,
storage_dir,
static_dir,
max_file_size,
file_ttl_ms,
max_storage_usage,
key_length,
public_base_url,
rate_limit_enabled,
rate_limit_max,
rate_limit_window_ms,
trust_proxy,
})
}