Release MiniVLESS 0.1.0 for Linux

This commit is contained in:
Emil
2026-09-10 18:57:05 +03:00
commit ff7c4e02a7
31 changed files with 10213 additions and 0 deletions
+291
View File
@@ -0,0 +1,291 @@
use serde::Serialize;
use std::{
collections::{BTreeMap, HashMap},
fs,
io::Read,
os::unix::fs::{MetadataExt, PermissionsExt},
path::{Path, PathBuf},
};
#[derive(Clone, Serialize)]
pub struct ApplicationInfo {
pub name: String,
pub executable: String,
pub running: bool,
pub pid: Option<u32>,
}
pub fn normalize(path: &str) -> Result<String, String> {
if path.len() > 4096 || !Path::new(path).is_absolute() || path.chars().any(char::is_control) {
return Err("Choose an absolute executable path".into());
}
let p = fs::canonicalize(path).map_err(|_| "Application executable was not found")?;
let m = fs::metadata(&p).map_err(|_| "Cannot read application executable")?;
if !m.is_file() || m.permissions().mode() & 0o111 == 0 {
return Err("Choose an executable file".into());
}
let mut magic = [0; 4];
fs::File::open(&p)
.and_then(|mut f| f.read_exact(&mut magic))
.map_err(|_| "Cannot read application executable")?;
if magic != *b"\x7fELF" {
return Err("Choose the real application binary, not a launcher script".into());
}
let s = p.to_str().ok_or("Application path must be valid UTF-8")?;
if s.chars().any(char::is_control) {
return Err("Invalid executable path".into());
}
Ok(s.into())
}
fn desktop_dirs() -> Vec<PathBuf> {
let mut dirs = std::env::var_os("XDG_DATA_DIRS")
.map(|v| std::env::split_paths(&v).collect::<Vec<_>>())
.unwrap_or_else(|| vec!["/usr/local/share".into(), "/usr/share".into()]);
if let Some(home) = std::env::var_os("HOME") {
dirs.push(PathBuf::from(home).join(".local/share"));
}
if let Some(home) = std::env::var_os("XDG_DATA_HOME") {
if Path::new(&home).is_absolute() {
dirs.push(home.into());
}
}
dirs.into_iter().map(|p| p.join("applications")).collect()
}
// Tokenize Desktop Entry Exec without ever executing it. Field codes are never evaluated.
fn exec_token(exec: &str) -> Option<String> {
let mut out = String::new();
let mut quoted = false;
let mut escaped = false;
for c in exec.trim().chars() {
if escaped {
out.push(c);
escaped = false;
} else if c == '\\' {
escaped = true;
} else if c == '"' {
quoted = !quoted;
} else if c.is_whitespace() && !quoted {
break;
} else {
out.push(c);
}
}
if out.is_empty() || quoted || escaped {
None
} else {
Some(out)
}
}
fn resolve_program(token: &str) -> Option<String> {
if token.starts_with('/') {
return normalize(token).ok();
}
std::env::var_os("PATH").and_then(|paths| {
std::env::split_paths(&paths)
.filter(|p| p.is_absolute())
.find_map(|dir| normalize(dir.join(token).to_str()?).ok())
})
}
struct DesktopApp {
name: String,
path: Option<String>,
keys: Vec<String>,
}
fn catalog() -> Vec<DesktopApp> {
let mut result = vec![];
for dir in desktop_dirs() {
let Ok(entries) = fs::read_dir(dir) else {
continue;
};
for entry in entries.flatten().take(4096) {
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("desktop") {
continue;
}
if entry.metadata().map(|m| m.len() > 128_000).unwrap_or(true) {
continue;
}
let Ok(text) = fs::read_to_string(&path) else {
continue;
};
let mut fields = HashMap::new();
let mut main = false;
for line in text.lines() {
if line.starts_with('[') {
main = line == "[Desktop Entry]";
continue;
}
if main {
if let Some((k, v)) = line.split_once('=') {
fields.insert(k, v);
}
}
}
if fields.get("Type") != Some(&"Application")
|| fields.get("Hidden") == Some(&"true")
|| fields.get("NoDisplay") == Some(&"true")
{
continue;
}
let Some(name) = fields
.get("Name")
.filter(|s| s.len() <= 128 && !s.chars().any(char::is_control))
else {
continue;
};
let Some(token) = fields.get("Exec").and_then(|s| exec_token(s)) else {
continue;
};
let mut keys = vec![];
let mut is_launcher = true;
if let Some(stem) = Path::new(&token).file_name().and_then(|s| s.to_str()) {
// Generic launchers must never group unrelated apps into a single routing rule.
if ![
"env", "sh", "bash", "flatpak", "snap", "python", "python3", "node",
"electron", "java",
]
.contains(&stem)
{
keys.push(stem.to_lowercase());
is_launcher = false;
}
}
if let Some(wm) = fields.get("StartupWMClass") {
keys.push(wm.to_lowercase());
}
if keys.is_empty() {
continue;
}
result.push(DesktopApp {
name: (*name).into(),
path: if is_launcher {
None
} else {
resolve_program(&token)
},
keys,
});
}
}
result
}
pub fn list_installed() -> Vec<ApplicationInfo> {
let mut by_path = BTreeMap::new();
for app in catalog() {
if let Some(path) = app.path {
by_path.entry(path.clone()).or_insert(ApplicationInfo {
name: app.name,
executable: path,
running: false,
pid: None,
});
}
}
sorted(by_path.into_values().collect())
}
pub fn list_running() -> Vec<ApplicationInfo> {
let apps = catalog();
let uid = unsafe { libc::geteuid() };
let mut by_path = BTreeMap::new();
let Ok(entries) = fs::read_dir("/proc") else {
return vec![];
};
for e in entries.flatten() {
let Some(pid) = e.file_name().to_str().and_then(|s| s.parse::<u32>().ok()) else {
continue;
};
if e.metadata().map(|m| m.uid() != uid).unwrap_or(true) {
continue;
}
let Ok(exe) = fs::read_link(e.path().join("exe")) else {
continue;
};
let Some(executable) = exe.to_str() else {
continue;
};
if executable.ends_with(" (deleted)") {
continue;
}
let basename = exe
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_lowercase();
let app = apps
.iter()
.find(|a| a.path.as_deref() == Some(executable))
.or_else(|| apps.iter().find(|a| a.keys.contains(&basename)));
let Some(app) = app else { continue };
by_path
.entry(executable.to_owned())
.or_insert(ApplicationInfo {
name: app.name.clone(),
executable: executable.into(),
running: true,
pid: Some(pid),
});
}
sorted(by_path.into_values().collect())
}
fn sorted(mut apps: Vec<ApplicationInfo>) -> Vec<ApplicationInfo> {
apps.sort_by_cached_key(|a| a.name.to_lowercase());
apps
}
pub fn describe(path: &str) -> Result<ApplicationInfo, String> {
let path = normalize(path)?;
if let Some(app) = list_running()
.into_iter()
.chain(list_installed())
.find(|a| a.executable == path)
{
return Ok(app);
}
let name = Path::new(&path)
.file_name()
.unwrap_or_default()
.to_string_lossy()
.into_owned();
Ok(ApplicationInfo {
name,
executable: path,
running: false,
pid: None,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn quoted_desktop_exec() {
assert_eq!(
exec_token("\"/opt/My App/app\" %U"),
Some("/opt/My App/app".into())
);
assert!(exec_token("\"unclosed").is_none());
}
#[test]
fn path_validation() {
assert!(normalize("relative").is_err());
assert!(normalize("/etc/passwd").is_err());
assert!(normalize("/tmp/x\n").is_err());
assert!(normalize("/usr/bin/true").is_ok());
}
#[test]
fn scripts_are_not_routing_identities() {
let d = tempfile::tempdir().unwrap();
let p = d.path().join("launcher");
fs::write(&p, "#!/bin/sh\nexec true\n").unwrap();
fs::set_permissions(&p, fs::Permissions::from_mode(0o755)).unwrap();
assert!(normalize(p.to_str().unwrap()).is_err());
}
}