#[allow(unused_imports)]
use anyhow::Result;
#[cfg(feature = "lua_support")]
use mlua::{Lua, LuaOptions, StdLib};
#[allow(unused_imports)]
use tracing::debug;
#[cfg(feature = "lua_support")]
pub struct LuaScriptCallbackEngine {
engine: Lua,
}
#[cfg(feature = "lua_support")]
impl LuaScriptCallbackEngine {
pub fn new(memory_limit: usize) -> Self {
debug!(
"Creating Lua engine with all libraries enabled, memory limit: {} bytes",
memory_limit
);
let engine = Lua::new();
engine
.set_memory_limit(memory_limit)
.expect("failed to set Lua memory limit");
LuaScriptCallbackEngine { engine }
}
pub fn new_without_os_io_libs(memory_limit: usize) -> Self {
debug!(
"Creating Lua engine without OS library, memory limit: {} bytes",
memory_limit
);
let engine = Lua::new_with(
StdLib::ALL_SAFE ^ (StdLib::OS | StdLib::IO),
LuaOptions::default(),
)
.expect("failed to create Lua engine without OS/IO libs");
engine
.set_memory_limit(memory_limit)
.expect("failed to set Lua memory limit");
LuaScriptCallbackEngine { engine }
}
pub fn unsafe_new(memory_limit: usize) -> Self {
debug!("Creating Lua engine with unsafe mode enabled");
let engine;
unsafe { engine = Lua::unsafe_new() };
engine
.set_memory_limit(memory_limit)
.expect("failed to set Lua memory limit");
LuaScriptCallbackEngine { engine }
}
pub fn get_engine(&self) -> &Lua {
&self.engine
}
pub fn load_and_compile(&self, script: &str) -> Result<()> {
self.engine
.load(script)
.exec()
.map_err(|e| anyhow::anyhow!("Failed to load and compile Lua script: {}", e))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "lua_support")]
#[tokio::test]
async fn create_all_modes() {
let _lua_engine = LuaScriptCallbackEngine::new(8 * 1024 * 1024);
let _lua_engine_without_os =
LuaScriptCallbackEngine::new_without_os_io_libs(8 * 1024 * 1024);
let _lua_engine_unsafe = LuaScriptCallbackEngine::unsafe_new(8 * 1024 * 1024);
}
#[cfg(feature = "lua_support")]
#[tokio::test]
async fn load_and_compile_valid_script() {
let engine = LuaScriptCallbackEngine::new(8 * 1024 * 1024);
let result = engine.load_and_compile("function filter(obj) return true end");
assert!(result.is_ok());
}
#[cfg(feature = "lua_support")]
#[tokio::test]
async fn load_and_compile_invalid_script() {
let engine = LuaScriptCallbackEngine::new(8 * 1024 * 1024);
let result = engine.load_and_compile("this is not valid lua %%%");
assert!(result.is_err());
}
#[cfg(feature = "lua_support")]
#[tokio::test]
async fn safe_mode_blocks_os_library() {
let engine = LuaScriptCallbackEngine::new_without_os_io_libs(8 * 1024 * 1024);
let result = engine.load_and_compile("os.execute('echo hello')");
assert!(result.is_err());
}
#[cfg(feature = "lua_support")]
#[tokio::test]
async fn safe_mode_blocks_io_library() {
let engine = LuaScriptCallbackEngine::new_without_os_io_libs(8 * 1024 * 1024);
let result = engine.load_and_compile("io.open('/etc/passwd', 'r')");
assert!(result.is_err());
}
#[cfg(feature = "lua_support")]
#[tokio::test]
async fn full_mode_allows_os_library() {
let engine = LuaScriptCallbackEngine::new(8 * 1024 * 1024);
let result = engine.load_and_compile("local t = os.clock()");
assert!(result.is_ok());
}
}