@@ -5005,6 +5005,10 @@ bool MultiReplace::initLuaState()
5005
5005
5006
5006
luaL_openlibs (_luaState);
5007
5007
5008
+ if (luaSafeModeEnabled) {
5009
+ applyLuaSafeMode (_luaState);
5010
+ }
5011
+
5008
5012
// Load Lua source code directly (fallback mode)
5009
5013
if (luaL_loadstring (_luaState, luaSourceCode) != LUA_OK) {
5010
5014
const char * errMsg = lua_tostring (_luaState, -1 );
@@ -5213,75 +5217,94 @@ bool MultiReplace::resolveLuaSyntax(std::string& inputString, const LuaVariables
5213
5217
return true ;
5214
5218
}
5215
5219
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
+ {
5220
5222
const char * path = luaL_checkstring (L, 1 );
5221
5223
5222
- // UTF-8 → Wide → filesystem::path → ifstream
5224
+ // Read file (your existing UTF-8/ANSI logic)
5223
5225
std::wstring wpath = Encoding::utf8ToWString (path);
5224
5226
std::ifstream in (std::filesystem::path (wpath), std::ios::binary);
5225
5227
if (!in) {
5226
5228
lua_pushboolean (L, false );
5227
5229
lua_pushfstring (L, " Cannot open file: %s" , path);
5228
5230
return 2 ;
5229
5231
}
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 >());
5232
5233
in.close ();
5233
5234
5234
- // Detect UTF-8 vs ANSI
5235
5235
bool rawIsUtf8 = Encoding::isValidUtf8 (raw);
5236
-
5237
- // Convert to a true UTF-8 buffer if needed
5238
5236
std::string utf8_buf;
5239
5237
if (rawIsUtf8) {
5240
5238
utf8_buf = std::move (raw);
5241
5239
}
5242
5240
else {
5243
- // ANSI → UTF-16
5244
5241
int wlen = MultiByteToWideChar (CP_ACP, 0 , raw.data (), (int )raw.size (), nullptr , 0 );
5245
5242
std::wstring wide (wlen, L' \0 ' );
5246
5243
MultiByteToWideChar (CP_ACP, 0 , raw.data (), (int )raw.size (), &wide[0 ], wlen);
5247
5244
5248
- // UTF-16 → UTF-8
5249
5245
int u8len = WideCharToMultiByte (CP_UTF8, 0 , wide.data (), wlen, nullptr , 0 , nullptr , nullptr );
5250
5246
utf8_buf.resize (u8len);
5251
5247
WideCharToMultiByte (CP_UTF8, 0 , wide.data (), wlen, &utf8_buf[0 ], u8len, nullptr , nullptr );
5252
5248
}
5253
5249
5254
5250
// Strip UTF-8 BOM if present
5255
5251
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 )
5259
5255
{
5260
5256
utf8_buf.erase (0 , 3 );
5261
5257
}
5262
5258
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
5267
5269
return 2 ;
5268
5270
}
5269
5271
5270
- // Run the chunk: expect it to return a table of entries
5271
5272
if (lua_pcall (L, 0 , 1 , 0 ) != LUA_OK) {
5272
5273
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 );
5274
5276
return 2 ;
5275
5277
}
5276
- // Stack now: [ table ]
5277
5278
5278
- // Prepend a 'true' for the success flag
5279
+ // Success: stack has [ table ]
5279
5280
lua_pushboolean (L, true );
5280
- lua_insert (L, -2 ); // now stack: [ true, table ]
5281
-
5281
+ lua_insert (L, -2 ); // [ true, table ]
5282
5282
return 2 ;
5283
5283
}
5284
5284
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
+
5285
5308
#pragma endregion
5286
5309
5287
5310
@@ -10534,6 +10557,10 @@ void MultiReplace::saveSettingsToIni(const std::wstring& iniFilePath) {
10534
10557
outFile << Encoding::wstringToUtf8 (L" StayAfterReplace=" + std::to_wstring (stayAfterReplaceEnabled ? 1 : 0 ) + L" \n " );
10535
10558
outFile << Encoding::wstringToUtf8 (L" GroupResults=" + std::to_wstring (groupResultsEnabled) + L" \n " );
10536
10559
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
+
10537
10564
// Convert and Store the scope options
10538
10565
int selection = IsDlgButtonChecked (_hSelf, IDC_SELECTION_RADIO) == BST_CHECKED ? 1 : 0 ;
10539
10566
int columnMode = IsDlgButtonChecked (_hSelf, IDC_COLUMN_MODE_RADIO) == BST_CHECKED ? 1 : 0 ;
@@ -10734,6 +10761,9 @@ void MultiReplace::loadSettingsFromIni() {
10734
10761
stayAfterReplaceEnabled = CFG.readBool (L" Options" , L" StayAfterReplace" , false );
10735
10762
groupResultsEnabled = CFG.readBool (L" Options" , L" GroupResults" , false );
10736
10763
10764
+ // Lua runtime options
10765
+ luaSafeModeEnabled = CFG.readBool (L" Lua" , L" SafeMode" , true );
10766
+
10737
10767
// Loading and setting the scope
10738
10768
int selection = CFG.readInt (L" Scope" , L" Selection" , 0 );
10739
10769
int columnMode = CFG.readInt (L" Scope" , L" ColumnMode" , 0 );
0 commit comments