Skip to content

Commit 04dfdab

Browse files
committed
added Lua Safe Mode; added lcmd Command
1 parent 232bc49 commit 04dfdab

File tree

3 files changed

+92
-27
lines changed

3 files changed

+92
-27
lines changed

src/MultiReplacePanel.cpp

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5005,6 +5005,10 @@ bool MultiReplace::initLuaState()
50055005

50065006
luaL_openlibs(_luaState);
50075007

5008+
if (luaSafeModeEnabled) {
5009+
applyLuaSafeMode(_luaState);
5010+
}
5011+
50085012
// Load Lua source code directly (fallback mode)
50095013
if (luaL_loadstring(_luaState, luaSourceCode) != LUA_OK) {
50105014
const char* errMsg = lua_tostring(_luaState, -1);
@@ -5213,75 +5217,94 @@ bool MultiReplace::resolveLuaSyntax(std::string& inputString, const LuaVariables
52135217
return true;
52145218
}
52155219

5216-
int MultiReplace::safeLoadFileSandbox(lua_State* L) {
5217-
// Return signature: (bool success, table or errorMessage)
5218-
5219-
// Get the file path argument
5220+
int MultiReplace::safeLoadFileSandbox(lua_State* L)
5221+
{
52205222
const char* path = luaL_checkstring(L, 1);
52215223

5222-
// UTF-8 → Wide → filesystem::path → ifstream
5224+
// Read file (your existing UTF-8/ANSI logic)
52235225
std::wstring wpath = Encoding::utf8ToWString(path);
52245226
std::ifstream in(std::filesystem::path(wpath), std::ios::binary);
52255227
if (!in) {
52265228
lua_pushboolean(L, false);
52275229
lua_pushfstring(L, "Cannot open file: %s", path);
52285230
return 2;
52295231
}
5230-
std::string raw((std::istreambuf_iterator<char>(in)),
5231-
std::istreambuf_iterator<char>());
5232+
std::string raw((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
52325233
in.close();
52335234

5234-
// Detect UTF-8 vs ANSI
52355235
bool rawIsUtf8 = Encoding::isValidUtf8(raw);
5236-
5237-
// Convert to a true UTF-8 buffer if needed
52385236
std::string utf8_buf;
52395237
if (rawIsUtf8) {
52405238
utf8_buf = std::move(raw);
52415239
}
52425240
else {
5243-
// ANSI → UTF-16
52445241
int wlen = MultiByteToWideChar(CP_ACP, 0, raw.data(), (int)raw.size(), nullptr, 0);
52455242
std::wstring wide(wlen, L'\0');
52465243
MultiByteToWideChar(CP_ACP, 0, raw.data(), (int)raw.size(), &wide[0], wlen);
52475244

5248-
// UTF-16 → UTF-8
52495245
int u8len = WideCharToMultiByte(CP_UTF8, 0, wide.data(), wlen, nullptr, 0, nullptr, nullptr);
52505246
utf8_buf.resize(u8len);
52515247
WideCharToMultiByte(CP_UTF8, 0, wide.data(), wlen, &utf8_buf[0], u8len, nullptr, nullptr);
52525248
}
52535249

52545250
// Strip UTF-8 BOM if present
52555251
if (utf8_buf.size() >= 3 &&
5256-
static_cast<unsigned char>(utf8_buf[0]) == 0xEF &&
5257-
static_cast<unsigned char>(utf8_buf[1]) == 0xBB &&
5258-
static_cast<unsigned char>(utf8_buf[2]) == 0xBF)
5252+
(unsigned char)utf8_buf[0] == 0xEF &&
5253+
(unsigned char)utf8_buf[1] == 0xBB &&
5254+
(unsigned char)utf8_buf[2] == 0xBF)
52595255
{
52605256
utf8_buf.erase(0, 3);
52615257
}
52625258

5263-
// Load the UTF-8 chunk into Lua
5264-
if (luaL_loadbuffer(L, utf8_buf.data(), utf8_buf.size(), path) != LUA_OK) {
5265-
lua_pushboolean(L, false);
5266-
lua_pushstring(L, lua_tostring(L, -1)); // error message
5259+
// Load as text chunk only (avoid binary precompiled chunks)
5260+
#if LUA_VERSION_NUM >= 503
5261+
if (luaL_loadbufferx(L, utf8_buf.data(), utf8_buf.size(), path, "t") != LUA_OK)
5262+
#else
5263+
if (luaL_loadbuffer(L, utf8_buf.data(), utf8_buf.size(), path) != LUA_OK)
5264+
#endif
5265+
{
5266+
lua_pushboolean(L, false);
5267+
lua_pushstring(L, lua_tostring(L, -2)); // copy error
5268+
lua_remove(L, -3); // remove original error under the two pushes
52675269
return 2;
52685270
}
52695271

5270-
// Run the chunk: expect it to return a table of entries
52715272
if (lua_pcall(L, 0, 1, 0) != LUA_OK) {
52725273
lua_pushboolean(L, false);
5273-
lua_pushstring(L, lua_tostring(L, -1));
5274+
lua_pushstring(L, lua_tostring(L, -2));
5275+
lua_remove(L, -3);
52745276
return 2;
52755277
}
5276-
// Stack now: [ table ]
52775278

5278-
// Prepend a 'true' for the success flag
5279+
// Success: stack has [ table ]
52795280
lua_pushboolean(L, true);
5280-
lua_insert(L, -2); // now stack: [ true, table ]
5281-
5281+
lua_insert(L, -2); // [ true, table ]
52825282
return 2;
52835283
}
52845284

5285+
void MultiReplace::applyLuaSafeMode(lua_State* L)
5286+
{
5287+
auto removeGlobal = [&](const char* name) {
5288+
lua_pushnil(L);
5289+
lua_setglobal(L, name);
5290+
};
5291+
5292+
// Remove dangerous base functions
5293+
removeGlobal("dofile");
5294+
removeGlobal("load");
5295+
removeGlobal("loadfile");
5296+
removeGlobal("require");
5297+
removeGlobal("collectgarbage");
5298+
5299+
// Remove whole libraries
5300+
removeGlobal("os");
5301+
removeGlobal("io");
5302+
removeGlobal("package");
5303+
removeGlobal("debug");
5304+
5305+
// Keep string/table/math/utf8/base intact.
5306+
}
5307+
52855308
#pragma endregion
52865309

52875310

@@ -10534,6 +10557,10 @@ void MultiReplace::saveSettingsToIni(const std::wstring& iniFilePath) {
1053410557
outFile << Encoding::wstringToUtf8(L"StayAfterReplace=" + std::to_wstring(stayAfterReplaceEnabled ? 1 : 0) + L"\n");
1053510558
outFile << Encoding::wstringToUtf8(L"GroupResults=" + std::to_wstring(groupResultsEnabled) + L"\n");
1053610559

10560+
// Lua runtime options
10561+
outFile << Encoding::wstringToUtf8(L"[Lua]\n");
10562+
outFile << Encoding::wstringToUtf8(L"SafeMode=" + std::to_wstring(luaSafeModeEnabled ? 1 : 0) + L"\n");
10563+
1053710564
// Convert and Store the scope options
1053810565
int selection = IsDlgButtonChecked(_hSelf, IDC_SELECTION_RADIO) == BST_CHECKED ? 1 : 0;
1053910566
int columnMode = IsDlgButtonChecked(_hSelf, IDC_COLUMN_MODE_RADIO) == BST_CHECKED ? 1 : 0;
@@ -10734,6 +10761,9 @@ void MultiReplace::loadSettingsFromIni() {
1073410761
stayAfterReplaceEnabled = CFG.readBool(L"Options", L"StayAfterReplace", false);
1073510762
groupResultsEnabled = CFG.readBool(L"Options", L"GroupResults", false);
1073610763

10764+
// Lua runtime options
10765+
luaSafeModeEnabled = CFG.readBool(L"Lua", L"SafeMode", true);
10766+
1073710767
// Loading and setting the scope
1073810768
int selection = CFG.readInt(L"Scope", L"Selection", 0);
1073910769
int columnMode = CFG.readInt(L"Scope", L"ColumnMode", 0);

src/MultiReplacePanel.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -609,8 +609,9 @@ class MultiReplace : public StaticDialog
609609
inline static bool isHoverTextEnabled = true; // Important to set on false as TIMER will be triggered at startup.
610610
inline static int editFieldSize = 5; // Size of the edit field for find/replace input
611611
inline static bool listStatisticsEnabled = false; // Status for showing list statistics
612-
inline static bool stayAfterReplaceEnabled = false; // Status for keeping panel open after replace
613-
inline static bool groupResultsEnabled = false; // Status for flat list view
612+
inline static bool stayAfterReplaceEnabled = false; // Status for keeping panel open after replace
613+
inline static bool groupResultsEnabled = false; // Status for flat list view
614+
inline static bool luaSafeModeEnabled = true; // Safer Lua mode: disables system/file/debug libs; common libs stay enabled
614615

615616
bool isHoverTextSuppressed = false; // Temporarily suppress HoverText to avoid flickering when Edit in list is open
616617

@@ -751,6 +752,7 @@ class MultiReplace : public StaticDialog
751752
bool initLuaState();
752753
bool compileLuaReplaceCode(const std::string& luaCode);
753754
static int safeLoadFileSandbox(lua_State* L);
755+
static void applyLuaSafeMode(lua_State* L);
754756

755757
//Replace in files
756758
bool handleBrowseDirectoryButton();

src/luaEmbedded.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,39 @@ function lvars(filePath)
296296
return resultTable
297297
end
298298

299+
------------------------------------------------------------------
300+
-- 6) lcmd function
301+
-- Loads a file that returns a table of helper functions and registers them globally.
302+
-- Example file: return { padLeft=function(s,w,ch) ... end, upper=function(s) ... end }
303+
------------------------------------------------------------------
304+
function lcmd(path)
305+
local ok, mod = safeLoadFileSandbox(path)
306+
if not ok then
307+
error("lcmd: " .. tostring(mod))
308+
end
309+
if type(mod) ~= "table" then
310+
error("lcmd: file must return a table of functions")
311+
end
312+
313+
local env = _ENV or _G
314+
local count = 0
315+
for name, fn in pairs(mod) do
316+
if type(fn) == "function" then
317+
if env[name] ~= nil then
318+
error("lcmd: command already exists: " .. tostring(name)) -- no overrides
319+
end
320+
env[name] = fn -- helper (returns string/number; used with set()/cond())
321+
count = count + 1
322+
end
323+
end
324+
if count == 0 then
325+
error("lcmd: file exported no functions")
326+
end
327+
328+
resultTable = resultTable or { result = "", skip = true }
329+
return resultTable
330+
end
331+
299332
)";
300333
static const size_t luaSourceSize = sizeof(luaSourceCode);
301334

0 commit comments

Comments
 (0)