– 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