use std::collections::{BTreeMap, BTreeSet};
use std::sync::OnceLock;
use serde::Deserialize;
static KNOWN_PATHS_TOML: &str = include_str!("known_paths.toml");
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct Registry {
credential: Credential,
package_config: HomeList,
toolchain_read: ToolchainRead,
toolchain_cache: HomeList,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct Credential {
home: Vec<String>,
basename_globs: Vec<String>,
reason: BTreeMap<String, String>,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct HomeList {
home: Vec<String>,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct ToolchainRead {
home: Vec<String>,
windows_home: Vec<String>,
}
fn registry() -> &'static Registry {
static PARSED: OnceLock<Registry> = OnceLock::new();
PARSED.get_or_init(|| {
let registry: Registry = toml::from_str(KNOWN_PATHS_TOML)
.expect("known_paths.toml must parse; the credential registry is not optional");
if let Err(problem) = validate(®istry) {
panic!("known_paths.toml is invalid: {problem}");
}
registry
})
}
fn validate(registry: &Registry) -> Result<(), String> {
let credential = ®istry.credential;
if credential.home.is_empty() || credential.basename_globs.is_empty() {
return Err("the credential lists must not be empty".into());
}
for entry in &credential.home {
if !credential.reason.contains_key(entry) {
return Err(format!("credential `{entry}` has no reason"));
}
}
for key in credential.reason.keys() {
if !credential.home.contains(key) {
return Err(format!("reason `{key}` names no credential"));
}
}
let lists: [(&str, &[String]); 6] = [
("credential.home", &credential.home),
("credential.basename_globs", &credential.basename_globs),
("package_config.home", ®istry.package_config.home),
("toolchain_read.home", ®istry.toolchain_read.home),
(
"toolchain_read.windows_home",
®istry.toolchain_read.windows_home,
),
("toolchain_cache.home", ®istry.toolchain_cache.home),
];
for (name, list) in lists {
let mut seen = BTreeSet::new();
for entry in list {
if entry.is_empty() || entry.starts_with('/') || entry.contains('\\') {
return Err(format!(
"{name} entry `{entry}` must be a relative POSIX path"
));
}
if !seen.insert(entry) {
return Err(format!("{name} lists `{entry}` twice"));
}
}
}
for (name, list) in &lists[2..] {
for grant in *list {
if let Some(credential) = credential
.home
.iter()
.find(|credential| within(grant, credential))
{
return Err(format!(
"{name} grants `{grant}`, which is the credential `{credential}` or inside it"
));
}
}
}
Ok(())
}
fn within(path: &str, root: &str) -> bool {
path == root
|| path
.strip_prefix(root)
.is_some_and(|rest| rest.starts_with('/'))
}
pub fn credential_home_paths() -> &'static [String] {
®istry().credential.home
}
pub fn credential_basename_globs() -> &'static [String] {
®istry().credential.basename_globs
}
pub fn credential_reason(path: &str) -> Option<&'static str> {
registry().credential.reason.get(path).map(String::as_str)
}
#[cfg(any(test, target_os = "linux", target_os = "macos", target_os = "windows"))]
pub(crate) fn package_config_home_paths() -> &'static [String] {
®istry().package_config.home
}
#[cfg(any(test, target_os = "linux", target_os = "macos", target_os = "windows"))]
pub(crate) fn toolchain_read_home_paths(windows: bool) -> impl Iterator<Item = &'static str> {
let read = ®istry().toolchain_read;
let extra: &'static [String] = if windows { &read.windows_home } else { &[] };
read.home.iter().chain(extra).map(String::as_str)
}
#[cfg(any(test, target_os = "linux", target_os = "macos"))]
pub(crate) fn toolchain_cache_home_paths() -> &'static [String] {
®istry().toolchain_cache.home
}
pub fn is_credential_path(path: &str) -> bool {
let trimmed = path.trim_matches(|c: char| {
c.is_whitespace()
|| matches!(
c,
'"' | '\'' | '`' | ',' | ';' | ':' | '(' | ')' | '[' | ']'
)
});
let normalized = trimmed.replace('\\', "/").to_ascii_lowercase();
let components: Vec<&str> = normalized
.split('/')
.filter(|component| !component.is_empty() && *component != ".")
.collect();
let Some(basename) = components.last() else {
return false;
};
let registry = registry();
registry.credential.home.iter().any(|entry| {
let needle: Vec<String> = entry.split('/').map(str::to_ascii_lowercase).collect();
components
.windows(needle.len())
.any(|window| window.iter().zip(&needle).all(|(a, b)| *a == b))
}) || registry
.credential
.basename_globs
.iter()
.any(|glob| harn_glob::match_name(&glob.to_ascii_lowercase(), basename))
}
#[cfg(test)]
mod parity_tests;
#[cfg(test)]
mod tests;