refactor: 迁移 winapi 到 windows-sys,修复配置解析漏洞
- 依赖替换为 windows-sys 0.61,main.rs 全面适配新 API - 配置解析错误显式传播:env/args 类型错误、数组空洞、非法键不再静默 吞错 - 修正空环境变量与空参数语义,补充 UTF-8 与路径拼接校验 - require 容错移至 Rust 侧,模块加载失败记录日志并跳过 - 新增配置解析与运行时单元测试(19 个)
This commit is contained in:
130
src/runtime.rs
130
src/runtime.rs
@@ -1,18 +1,13 @@
|
||||
use crate::error::ShimError;
|
||||
use crate::{ShimConfig, ShimEnv};
|
||||
use crate::{ShimConfig, ShimLayout};
|
||||
use mlua::{FromLua, Lua, StdLib, Table, Value};
|
||||
use std::path::Path;
|
||||
use std::{env, fs};
|
||||
/// 将 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('\\', "/")
|
||||
// 自动将 Windows UNC 规范路径转回传统路径
|
||||
let simplified = dunce::simplified(path);
|
||||
simplified.to_string_lossy().replace('\\', "/")
|
||||
}
|
||||
|
||||
pub struct LuaRuntime {
|
||||
@@ -21,66 +16,147 @@ pub struct LuaRuntime {
|
||||
|
||||
impl LuaRuntime {
|
||||
/// 初始化限定权限的 Lua 沙箱环境
|
||||
pub fn new(shim_env: &ShimEnv) -> Result<Self, ShimError> {
|
||||
pub fn new(layout: &ShimLayout) -> Result<Self, ShimError> {
|
||||
// 只加载安全的标准库,剥离 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)))?;
|
||||
.map_err(|e| ShimError::Environment(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);
|
||||
let root_dir = normalize_path_for_lua(&layout.root_dir);
|
||||
let tools_dir = normalize_path_for_lua(&layout.tools_dir);
|
||||
|
||||
// 1. 注入锚点变量
|
||||
// 1. 注入锚点变量 __SHIM_DIR__(shim 安装根目录)
|
||||
globals
|
||||
.set("__SHIM_DIR__", root_dir_str.clone())
|
||||
.map_err(|e| ShimError::EnvError(e.to_string()))?;
|
||||
.set("__SHIM_DIR__", root_dir.clone())
|
||||
.map_err(|e| ShimError::Environment(e.to_string()))?;
|
||||
|
||||
// 2. 安全暴露 get_env 供配置读取环境变量
|
||||
let get_env = lua
|
||||
.create_function(|_, key: String| -> mlua::Result<String> {
|
||||
Ok(env::var(key).unwrap_or_default())
|
||||
})
|
||||
.map_err(|e| ShimError::EnvError(e.to_string()))?;
|
||||
.map_err(|e| ShimError::Environment(e.to_string()))?;
|
||||
|
||||
globals
|
||||
.set("get_env", get_env)
|
||||
.map_err(|e| ShimError::EnvError(e.to_string()))?;
|
||||
.map_err(|e| ShimError::Environment(e.to_string()))?;
|
||||
|
||||
// 3. 配置 package.path,确保 require 行为正常
|
||||
if let Ok(package) = globals.get::<Table>("package") {
|
||||
let _ = package.set("cpath", "");
|
||||
let _ = package.set("loadlib", Value::Nil);
|
||||
|
||||
if let Ok(path) = package.get::<String>("path") {
|
||||
let new_path = format!(
|
||||
"{};{}/?.lua;{}/?/init.lua;{}/?.lua;{}/?/init.lua",
|
||||
path, root_dir_str, root_dir_str, tools_dir_str, tools_dir_str
|
||||
path, root_dir, root_dir, tools_dir, tools_dir
|
||||
);
|
||||
let _ = package.set("path", new_path);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 包装 require:配置模块缺失/加载失败时记录日志并跳过该条目,
|
||||
// 而不是让整个 shims.lua 解析失败(排查问题时日志可见)
|
||||
let original_require: mlua::Function = globals
|
||||
.get("require")
|
||||
.map_err(|e| ShimError::Environment(format!("获取 require 失败: {}", e)))?;
|
||||
globals
|
||||
.set("_rshim_original_require", &original_require)
|
||||
.map_err(|e| ShimError::Environment(e.to_string()))?;
|
||||
|
||||
let wrapped_require = lua
|
||||
.create_function(|lua, module: String| -> mlua::Result<Value> {
|
||||
let original: mlua::Function = lua.globals().get("_rshim_original_require")?;
|
||||
match original.call::<Value>(module.clone()) {
|
||||
Ok(value) => Ok(value),
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
module = %module,
|
||||
error = %e,
|
||||
"配置模块加载失败,已跳过该条目(可在独立配置文件中定义)"
|
||||
);
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
.map_err(|e| ShimError::Environment(e.to_string()))?;
|
||||
|
||||
globals
|
||||
.set("require", wrapped_require)
|
||||
.map_err(|e| ShimError::Environment(e.to_string()))?;
|
||||
|
||||
Ok(Self { lua })
|
||||
}
|
||||
|
||||
/// 执行指定脚本文件,直接返回完整的 Lua Table
|
||||
pub fn evaluate_lua_script(&self, path: &Path) -> Result<Table, ShimError> {
|
||||
let code = fs::read_to_string(path)?;
|
||||
pub fn eval_script<T: FromLua>(&self, path: impl AsRef<Path>) -> Result<T, ShimError> {
|
||||
let path = path.as_ref();
|
||||
|
||||
let bytes = fs::read(path)?;
|
||||
let code = String::from_utf8(bytes).map_err(|e| {
|
||||
ShimError::InvalidConfig(format!(
|
||||
"{} 不是有效的 UTF-8 文件(请将 Lua 配置文件保存为 UTF-8 编码): {}",
|
||||
path.display(),
|
||||
e.utf8_error()
|
||||
))
|
||||
})?;
|
||||
|
||||
let chunk_name = format!("@{}", path.display());
|
||||
|
||||
self.lua
|
||||
.load(&code)
|
||||
.set_name(path.to_string_lossy())
|
||||
.eval::<Table>()
|
||||
.map_err(|e| ShimError::LuaExecutionError {
|
||||
.set_name(&chunk_name)
|
||||
.eval::<T>()
|
||||
.map_err(|e| ShimError::LuaExecution {
|
||||
file: path.display().to_string(),
|
||||
source: e,
|
||||
})
|
||||
}
|
||||
|
||||
/// 将 Lua Value 解析转化为 ShimConfig 数据对象
|
||||
pub fn parse_config(&self, value: Value) -> Result<ShimConfig, ShimError> {
|
||||
ShimConfig::from_lua(value, &self.lua).map_err(|e| ShimError::InvalidConfig(e.to_string()))
|
||||
// /// 将 Lua Value 解析转化为 ShimConfig 数据对象
|
||||
// pub fn parse_config(&self, value: Value) -> Result<ShimConfig, ShimError> {
|
||||
// ShimConfig::from_lua(value, &self.lua).map_err(|e| ShimError::InvalidConfig(e.to_string()))
|
||||
// }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ShimLayout;
|
||||
|
||||
fn test_layout() -> ShimLayout {
|
||||
let root = std::env::temp_dir().join("rshim-test-layout");
|
||||
ShimLayout {
|
||||
root_dir: root.clone(),
|
||||
bin_dir: root.join("bin"),
|
||||
tools_dir: root.join("tools"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_module_require_returns_nil() {
|
||||
let runtime = LuaRuntime::new(&test_layout()).unwrap();
|
||||
let value: Value = runtime
|
||||
.lua
|
||||
.load(r#"return require("rshim_test_no_such_module")"#)
|
||||
.eval()
|
||||
.unwrap();
|
||||
assert!(matches!(value, Value::Nil));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_module_require_still_works() {
|
||||
let runtime = LuaRuntime::new(&test_layout()).unwrap();
|
||||
let value: Value = runtime
|
||||
.lua
|
||||
.load(r#"return pcall(require, "string")"#)
|
||||
.eval()
|
||||
.unwrap();
|
||||
assert!(matches!(value, Value::Boolean(true)));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user