use std::{
collections::BTreeMap,
ffi::{OsStr, OsString},
path::{Path, PathBuf},
};
const BUNDLE_DIRECTORY: &str = "APPDIR";
const SET_BY_BUNDLE: [&str; 5] = [
"APPIMAGE",
"ARGV0",
"OWD",
"PYTHONDONTWRITEBYTECODE",
"GTK_THEME",
];
const DATA_DIRECTORIES: &str = "XDG_DATA_DIRS";
const HOOK_DATA_DIRECTORY: &str = "/usr/share";
pub(super) struct Bundle {
roots: Vec<PathBuf>,
}
impl Bundle {
pub(super) fn current() -> Option<Self> {
let named = PathBuf::from(std::env::var_os(BUNDLE_DIRECTORY).filter(|v| !v.is_empty())?);
let resolved = std::fs::canonicalize(&named).ok()?;
let executable = std::env::current_exe().ok()?;
executable
.starts_with(&resolved)
.then(|| Self::new(named, resolved))
}
fn new(named: PathBuf, resolved: PathBuf) -> Self {
let roots = if named == resolved {
vec![named]
} else {
vec![named, resolved]
};
Self { roots }
}
pub(super) fn without(
&self,
environment: impl IntoIterator<Item = (OsString, OsString)>,
) -> BTreeMap<OsString, OsString> {
environment
.into_iter()
.filter(|(name, _)| !SET_BY_BUNDLE.iter().any(|set| name == set))
.filter_map(|(name, value)| {
let value = self.person_entries(&name, &value)?;
Some((name, value))
})
.collect()
}
fn contains(&self, entry: &Path) -> bool {
self.roots.iter().any(|root| entry.starts_with(root))
}
fn person_entries(&self, name: &OsStr, value: &OsStr) -> Option<OsString> {
let entries: Vec<PathBuf> = std::env::split_paths(value).collect();
if !entries.iter().any(|entry| self.contains(entry)) {
return Some(value.to_owned());
}
let mut hook_entry_pending = name == DATA_DIRECTORIES;
let mut after_bundle = false;
let mut kept = Vec::with_capacity(entries.len());
for entry in entries {
let inside = self.contains(&entry);
let hook_entry = hook_entry_pending
&& after_bundle
&& !inside
&& entry == Path::new(HOOK_DATA_DIRECTORY);
hook_entry_pending &= !hook_entry;
after_bundle = inside;
if !inside && !hook_entry {
kept.push(entry);
}
}
kept.iter()
.any(|entry| !entry.as_os_str().is_empty())
.then(|| std::env::join_paths(kept).expect("entries split from one list join again"))
}
}