use std::path::PathBuf;
use crate::Config;
use super::Cli;
pub(super) const MAX_FILE_SIZE_CEILING: u64 = 1_073_741_824;
pub(super) fn resolve_config(cli: &Cli) -> Config {
let repo_root = cli
.repo_root
.clone()
.or_else(detect_repo_root)
.unwrap_or_else(|| PathBuf::from("."));
let index_dir = {
let raw = cli
.index_dir
.clone()
.unwrap_or_else(|| repo_root.join(".syntext"));
if let Err(msg) = validate_index_dir(&raw) {
eprintln!("{msg}");
std::process::exit(2);
}
raw
};
let max_file_size = parse_max_file_size();
let verify_on_open = matches!(
std::env::var("SYNTEXT_VERIFY_ON_OPEN").ok().as_deref(),
Some("1") | Some("true")
);
Config {
max_file_size,
max_segments: 10,
index_dir,
repo_root,
verbose: false,
strict_permissions: true,
verify_on_open,
recalibrate: false,
}
}
fn parse_max_file_size() -> u64 {
clamp_max_file_size(
std::env::var("SYNTEXT_MAX_FILE_SIZE")
.ok()
.and_then(|v| v.parse::<u64>().ok()),
)
}
pub(super) fn clamp_max_file_size(raw: Option<u64>) -> u64 {
raw.unwrap_or(10 * 1024 * 1024).min(MAX_FILE_SIZE_CEILING)
}
fn detect_repo_root() -> Option<PathBuf> {
let mut dir = std::env::current_dir().ok()?;
loop {
if dir.join(".git").exists() {
return Some(dir);
}
if !dir.pop() {
return None;
}
}
}
#[cfg(unix)]
fn validate_index_dir(index_dir: &std::path::Path) -> Result<(), String> {
if !index_dir.is_absolute() {
return Ok(());
}
const SENSITIVE_PREFIXES: &[&str] = &[
"/etc",
"/usr",
"/bin",
"/sbin",
"/lib",
"/lib64",
"/sys",
"/proc",
"/dev",
"/boot",
"/root",
"/System",
"/Library",
"/private/etc",
"/private/var/root",
];
let dir_str = index_dir.to_string_lossy();
if let Some(matched) = overlaps_sensitive_prefix(&dir_str, SENSITIVE_PREFIXES, '/') {
return Err(format!(
"st: refusing --index-dir '{}': overlaps system path '{matched}'; \
use a path under the repository or a user-owned directory",
index_dir.display(),
));
}
Ok(())
}
#[cfg(not(unix))]
fn validate_index_dir(index_dir: &std::path::Path) -> Result<(), String> {
if !index_dir.is_absolute() {
return Ok(());
}
let mut sensitive: Vec<String> = Vec::new();
for var in [
"SYSTEMROOT",
"PROGRAMFILES",
"PROGRAMFILES(X86)",
"PROGRAMDATA",
] {
if let Some(val) = std::env::var_os(var) {
let lower = val.to_string_lossy().to_lowercase().replace('/', "\\");
if !sensitive.contains(&lower) {
sensitive.push(lower);
}
}
}
for fb in [
"c:\\windows",
"c:\\program files",
"c:\\program files (x86)",
"c:\\programdata",
] {
let s = fb.to_string();
if !sensitive.contains(&s) {
sensitive.push(s);
}
}
let dir_lower = index_dir
.to_string_lossy()
.to_lowercase()
.replace('/', "\\");
let prefixes: Vec<&str> = sensitive.iter().map(|s| s.as_str()).collect();
if let Some(matched) = overlaps_sensitive_prefix(&dir_lower, &prefixes, '\\') {
return Err(format!(
"st: refusing --index-dir '{}': overlaps system path '{matched}'; \
use a path under the repository or a user-owned directory",
index_dir.display(),
));
}
Ok(())
}
pub(super) fn overlaps_sensitive_prefix<'a>(
dir: &str,
prefixes: &[&'a str],
sep: char,
) -> Option<&'a str> {
for &prefix in prefixes {
if dir == prefix || dir.starts_with(&format!("{prefix}{sep}")) {
return Some(prefix);
}
}
None
}