– Lua parser extensions for MediaWiki - Wrapper for Lua interpreter – © 2008 Fran Rogers - see 'COPYING' for license
– Creates a new sandbox environment for scripts to safely run in. function make_sandbox()
-- Dummy function that returns nil, to quietly replace unsafe functions local function dummy(...) return nil end -- Deep-copy an object; optionally replace all its leaf members with the -- value 'override' if it's non-nil local function deepcopy(object, override) local lookup_table = {} local function _copy(object, override) if type(object) ~= "table" then return object elseif lookup_table[object] then return lookup_table[object] end local new_table = {} lookup_table[object] = new_table for index, value in pairs(object) do if override ~= nil then value = override end new_table[_copy(index)] = _copy(value, override) end return setmetatable(new_table, _copy(getmetatable(object), override)) end return _copy(object, override) end -- Our new environment local env = {} -- "_OUTPUT" will accumulate the results of print() and friends env._OUTPUT = "" -- _OUTPUT wrapper for io.write() local function writewrapper(...) local out = "" for n = 1, select("#", ...) do if out == "" then out = tostring(select(n, ...)) else out = out .. tostring(select(n, ...)) end end env._OUTPUT = env._OUTPUT .. out end -- _OUTPUT wrapper for io.stdout:output() local function outputwrapper(file) if file == nil then local file = {} file.close = dummy file.lines = dummy file.read = dummy file.flush = dummy file.seek = dummy file.setvbuf = dummy function file:write(...) writewrapper(...); end return file else return nil end end -- _OUTPUT wrapper for print() local function printwrapper(...) local out = "" for n = 1, select("#", ...) do if out == "" then out = tostring(select(n, ...)) else out = out .. '\t' .. tostring(select(n, ...)) end end env._OUTPUT =env._OUTPUT .. out .. "\n" end -- Safe wrapper for loadstring() local oldloadstring = loadstring local function safeloadstring(s, chunkname) local f, message = oldloadstring(s, chunkname) if not f then return f, message end setfenv(f, getfenv(2)) return f end -- Populate the sandbox environment env.assert = _G.assert env.error = _G.error env._G = env env.ipairs = _G.ipairs env.loadstring = safeloadstring env.next = _G.next env.pairs = _G.pairs env.pcall = _G.pcall env.print = printwrapper env.write = writewrapper env.select = _G.select env.tonumber = _G.tonumber env.tostring = _G.tostring env.type = _G.type env.unpack = _G.unpack env._VERSION = _G._VERSION env.xpcall = _G.xpcall env.coroutine = deepcopy(_G.coroutine) env.string = deepcopy(_G.string) env.string.dump = nil env.table = deepcopy(_G.table) env.math = deepcopy(_G.math) env.io = {} env.io.write = writewrapper env.io.flush = dummy env.io.type = typewrapper env.io.output = outputwrapper env.io.stdout = outputwrapper() env.os = {} env.os.clock = _G.os.clock -- env.os.date = _G.os.date env.os.difftime = _G.os.difftime env.os.time = _G.os.time -- Return the new sandbox environment return env
end
– Creates a new debug hook that aborts with 'error(“LOC_LIMIT”)' after – 'maxlines' lines have been passed, or 'error(“RECURSION_LIMIT”)' after – 'maxcalls' levels of recursion have been entered. function make_hook(maxlines, maxcalls, diefunc)
local lines = 0 local calls = 0 function _hook(event, ...) if event == "line" then lines = lines + 1 if lines > maxlines then error("LOC_LIMIT") end elseif event == "call" then calls = calls + 1 if calls > maxcalls then error("RECURSION_LIMIT") end elseif event == "return" then calls = calls - 1 end end return _hook
end
– Creates and returns a function, 'wrap(input)', which reads a string into – a Lua chunk and executes it in a persistent sandbox environment, returning – 'output, err' where 'output' is the combined output of print() and friends – from within the chunk and 'err' is either nil or an error incurred while – executing the chunk; or halting after 'maxlines' lines or 'maxcalls' levels – of recursion. function make_wrapper(maxlines, maxcalls)
-- Create the debug hook and sandbox environment. local hook = make_hook(maxlines, maxcalls) local env = make_sandbox() -- The actual 'wrap()' function. -- All of the above variables will be bound in its closure. function _wrap(chunkstr) local chunk, err, done -- Clear any leftover output from the last call env._OUTPUT = "" err = nil -- Load the string into a chunk; fail on error chunk, err = loadstring(chunkstr) if err ~= nil then return nil, err end -- Set the chunk's environment, enable the debug hook, and execute it setfenv(chunk, env) co = coroutine.create(chunk) debug.sethook(co, hook, "crl") done, err = coroutine.resume(co) if done == true then err = nil end -- Collect and return the results return env._OUTPUT, err end return _wrap
end
– Listen on stdin for Lua chunks, parse and execute them, and print the – results of each on stdout. function main(arg)
if #arg ~= 2 then io.stderr:write(string.format("usage: %s MAXLINES MAXCALLS\n", arg[0])) os.exit(1) end -- Create a wrapper function, wrap() local wrap = make_wrapper(tonumber(arg[1]), tonumber(arg[2])) -- Turn off buffering, and loop through the input io.stdout:setvbuf("no") while true do -- Read in a chunk local chunkstr = "" while true do local line = io.stdin:read("*l") if chunkstr == "" and line == nil then -- On EOF, exit. os.exit(0) elseif line == "." or line == nil then -- Finished this chunk; move on to the next step break elseif chunkstr ~= "" then chunkstr = chunkstr .. "\n" .. line else chunkstr = line end end -- Parse and execute the chunk local res, err res, err = wrap(chunkstr, env, hook) -- Write out the results if err == nil then io.stdout:write("'", res, "', true\n.\n") else io.stdout:write("'", err, "', false\n.\n") end end
end
– If called as a script instead of imported as a library, run main(). if arg ~= nil then
main(arg)
end