use crate::error::ShimError; use mlua::{Error as LuaError, FromLua, Lua, StdLib, Table, Value}; use std::process::Command; use std::{ collections::HashMap, env, fs, io::{Error, ErrorKind}, path::{Path, PathBuf}, }; /// 将 Path 转换为适合 Lua 使用的安全字符串路径 fn normalize_path_for_lua(path: &Path) -> String { let path_str = path.to_string_lossy(); // 1. 剥离 Windows UNC 规范路径前缀 (\\?\) let clean_str = path_str.strip_prefix(r"\\?\").unwrap_or(&path_str); // 2. 将反斜杠转换成正斜杠(在非 UNC 路径下,Windows 和 Lua 均完美支持 /) // 这样既避免了 Lua 字符串转义隐患,又不会破坏 Windows 路径 clean_str.replace('\\', "/") } pub struct ShimEnv { pub bin_dir: PathBuf, pub root_dir: PathBuf, pub tools_dir: PathBuf, pub target_name: String, } impl ShimEnv { /// 提取当前代理程序的运行环境信息 pub fn new(current_exe: PathBuf) -> Result { let bin_dir = current_exe .parent() .ok_or_else(|| { ShimError::PathResolutionError(format!( "无法获取程序 [{}] 的父级 bin 目录", current_exe.display() )) })? .to_path_buf(); println!("bin_dir目录 {}", bin_dir.display()); let root_dir = bin_dir .parent() .ok_or_else(|| { ShimError::PathResolutionError(format!( "无法获取 bin 目录 [{}] 的父级 root 目录", bin_dir.display() )) })? .to_path_buf(); println!("root_dir 目录 {}", root_dir.display()); let tools_dir = root_dir.join("tools"); println!("tools_dir 目录 {}", tools_dir.display()); let target_name = current_exe .file_stem() .and_then(|s| s.to_str()) .ok_or_else(|| { ShimError::PathResolutionError(format!( "无法从路径 [{}] 提取有效的程序名称", current_exe.display() )) })? .to_lowercase(); println!("程序名称 {}", target_name); Ok(Self { bin_dir, root_dir, tools_dir, target_name, }) } } #[derive(Debug, Clone)] pub struct ShimConfig { pub target_path: PathBuf, pub args: Option>, pub envs: Option>, } impl ShimConfig { /// 根据配置快速构建准备执行的 Command 对象 pub fn to_command(&self) -> Command { let mut cmd = Command::new(&self.target_path); if let Some(args) = &self.args { cmd.args(args); } if let Some(envs) = &self.envs { for (key, val) in envs { // 直接应用环境变量(Lua 端已经处理好字符串拼接或列表合并) cmd.env(key, val); } } cmd } } /// 实现 FromLua Trait,由 mlua 自动处理 Table 转换 impl FromLua for ShimConfig { fn from_lua(value: Value, _lua: &Lua) -> mlua::Result { match value { Value::Table(table) => { let path_str: String = table.get("path")?; let args: Option> = table.get("args")?; // 解析 env Table let mut envs_map = HashMap::new(); if let Ok(env_table) = table.get::("env") { // 获取当前系统的路径分隔符(Windows 为 ";",Unix 为 ":") #[cfg(windows)] let sep = ";"; #[cfg(not(windows))] let sep = ":"; for pair in env_table.pairs::() { let (k, v) = pair?; match v { // 情况 1: 普通字符串,如 HOME = "C:/path" -> 直接覆盖 Value::String(s) => { envs_map.insert(k, s.to_str()?.to_string()); } // 情况 2: 数组 Table,如 PATH = { bin_dir, get_env("PATH") } Value::Table(arr) => { let paths: Vec = arr // 将 arr 作为序列(数组)处理,每个元素转为 String .sequence_values::() .filter_map(|r| r.ok()) .filter(|s| !s.is_empty()) // 过滤空串,防止生成不必要的连续 ;; .collect(); let combined = paths.join(sep); envs_map.insert(k, combined); } _ => {} } } } let envs = if envs_map.is_empty() { None } else { Some(envs_map) }; Ok(ShimConfig { target_path: PathBuf::from(path_str), args, envs, }) } _ => Err(mlua::Error::FromLuaConversionError { from: value.type_name(), to: "ShimConfig".into(), message: Some("Expected a Lua table".to_string()), }), } } } pub struct LuaRuntime { lua: Lua, } impl LuaRuntime { /// 初始化限定权限的 Lua 沙箱环境 pub fn new(shim_env: &ShimEnv) -> Result { // 只加载安全的标准库,剥离 os / io 等风险模块 let lua = Lua::new_with( StdLib::TABLE | StdLib::STRING | StdLib::MATH | StdLib::PACKAGE, mlua::LuaOptions::default(), ) .map_err(|e| ShimError::EnvError(format!("初始化 Lua 失败: {}", e)))?; let globals = lua.globals(); // 统一使用 POSIX 风格路径规范化路径字符串 let root_dir_str = normalize_path_for_lua(&shim_env.root_dir); let tools_dir_str = normalize_path_for_lua(&shim_env.tools_dir); // 1. 注入锚点变量 globals .set("__SHIM_DIR__", root_dir_str.clone()) .map_err(|e| ShimError::EnvError(e.to_string()))?; // 2. 安全暴露 get_env 供配置读取环境变量 let get_env = lua .create_function(|_, key: String| -> mlua::Result { Ok(env::var(key).unwrap_or_default()) }) .map_err(|e| ShimError::EnvError(e.to_string()))?; globals .set("get_env", get_env) .map_err(|e| ShimError::EnvError(e.to_string()))?; // 3. 配置 package.path,确保 require 行为正常 if let Ok(package) = globals.get::
("package") { if let Ok(path) = package.get::("path") { let new_path = format!( "{};{}/?.lua;{}/?/init.lua;{}/?.lua;{}/?/init.lua", path, root_dir_str, root_dir_str, tools_dir_str, tools_dir_str ); let _ = package.set("path", new_path); } } Ok(Self { lua }) } /// 执行指定脚本文件,直接返回完整的 Lua Table pub fn evaluate_lua_script(&self, path: &Path) -> Result { let code = fs::read_to_string(path)?; self.lua .load(&code) .set_name(path.to_string_lossy()) .eval::
() .map_err(|e| ShimError::LuaExecutionError { file: path.display().to_string(), source: e, }) } /// 将 Lua Value 解析转化为 ShimConfig 数据对象 pub fn parse_config(&self, value: Value) -> Result { ShimConfig::from_lua(value, &self.lua).map_err(|e| ShimError::InvalidConfig(e.to_string())) } } pub struct Shim; impl Shim { pub fn init() -> Result { let current_exe = env::current_exe() .map_err(|e| Error::new(ErrorKind::Other, format!("获取代理程序路径失败: {}", e)))?; println!("当前目录 {}", current_exe.display()); let shim_env = ShimEnv::new(current_exe)?; let runtime = LuaRuntime::new(&shim_env)?; Self::resolve_config(&runtime, &shim_env) } fn resolve_config(engine: &LuaRuntime, env: &ShimEnv) -> Result { // 策略 1: 尝试加载全局配置文件 shims.lua let global_config = env.root_dir.join("shims.lua"); if global_config.is_file() { let root_table = engine.evaluate_lua_script(&global_config)?; // 检查 shims.lua 中是否存在以 target_name 命名的 Table 节点 if let Ok(target_val) = root_table.get::(env.target_name.as_str()) { if matches!(target_val, Value::Table(_)) { return engine.parse_config(target_val); } } // 穿透:若全局配置文件存在但未包含当前程序的 key,继续向下探查 } // 策略 2: 降级寻找独立文件 ({exe}.lua),优先顺序:tools/ > root/ let target_filename = format!("{}.lua", env.target_name); let candidates = [ env.tools_dir.join(&target_filename), env.root_dir.join(&target_filename), ]; for config_path in &candidates { if config_path.is_file() { let table = engine.evaluate_lua_script(config_path)?; return engine.parse_config(Value::Table(table)); } } // 策略 3: 所有查找失败,抛出错误 Err(ShimError::ConfigNotFound(format!( "未找到关于 '{}' 的配置。请检查 shims.lua 或特定的 {}.lua 文件", env.target_name, env.target_name ))) } }