add lua sandbox, tabs, stuff

This commit is contained in:
unknown 2022-02-18 17:29:31 -05:00
parent 2593c55ee3
commit fa1f4894af
12 changed files with 436 additions and 36 deletions

133
computers/api.lua Normal file
View File

@ -0,0 +1,133 @@
--[[
computers.sandbox code is from https://github.com/kikito/lua-sandbox
License MIT 2013 Enrique García Cota
additionally modified by wsor to work with lua 5.1/luajit
modifications include:
* additional protections
* tweaking of structure
* modifications to the default env
]]
computers.sandbox = {}
computers.api = {}
--default sandbox enviroment used if none is provided
computers.sandbox.default_env = {}
([[
_VERSION assert error ipairs next pairs
select tonumber tostring type unpack
math.abs math.acos math.asin math.atan math.atan2 math.ceil
math.cos math.cosh math.deg math.exp math.fmod math.floor
math.frexp math.huge math.ldexp math.log math.log10 math.max
math.min math.modf math.pi math.pow math.rad math.random
math.sin math.sinh math.sqrt math.tan math.tanh
os.clock os.difftime os.time
string.byte string.char string.format
string.len string.lower string.reverse
string.sub string.upper
table.insert table.maxn table.remove table.sort table.concat
]]):gsub('%S+', function(id)
local module, method = id:match('([^%.]+)%.([^%.]+)')
if module then
computers.sandbox.default_env[module] = computers.sandbox.default_env[module] or {}
computers.sandbox.default_env[module][method] = _G[module][method]
else
computers.sandbox.default_env[id] = _G[id]
end
end)
--takes a code string, returns a sandboxed function to exicute
function computers.sandbox.loadstring(code, options)
local defaults = {
env = table.copy(computers.sandbox.default_env),
quota = 5000
}
options = options or {}
for k, v in pairs(defaults) do
if not options[k] then options[k] = v end
end
assert(type(code) == "string", "[computers.sandbox]: passed is not a string")
local env = options.env
env._G = env
if code:byte(1) == 27 then
minetest.log("warning", "[computers.sandbox]: attempted bytecode execution termminated")
return nil, "bytecode disallowed"
end
local f, err = loadstring(code)
if not f then return nil, err end
setfenv(f, env)
if jit then jit.off(f, true) end
local function timeout()
debug.sethook()
error("quota exceeded: " .. tostring(options.quota))
end
return function(...)
debug.sethook(timeout, "", options.quota)
local string_metatable = getmetatable("")
assert(string_metatable.__index == string, "[computers.sandbox]: error with the string metatable")
string_metatable.__index = env.string
local status, ret = pcall(f, ...)
debug.sethook()
string_metatable.__index = string
if not status then error(ret) end
return ret
end
end
--supports string input or table env input with a default env of the base
function computers.sandbox.merge_env(nenv, denv)
local base_env = table.copy(denv or computers.sandbox.default_env)
if type(nenv) == "table" then
for k, v in pairs(nenv) do
base_env[k] = base_env[k] or {}
for key, value in pairs(v) do
base_env[k][key] = value
end
end
elseif type(nenv) == "string" then
nenv:gsub('%S+', function(id)
local module, method = id:match('([^%.]+)%.([^%.]+)')
if module then
base_env[module] = base_env[module] or {}
base_env[module][method] = _G[module][method]
else
base_env[id] = _G[id]
end
end)
end
return base_env
end
function computers.api.get_dir_keyed_list(path, is_dir)
local files = minetest.get_dir_list(path, is_dir)
local keyed = {}
for _, file in pairs(files) do
keyed[file] = true
end
return keyed
end
function computers.api.chat_send_player(player, msg)
local name = player
if type(name) == "userdata" then name = player:get_player_name() end
minetest.chat_send_player(name, msg)
end

View File

@ -0,0 +1,7 @@
{
"name": "exit",
"license": "MIT",
"version": 0.1,
"engine": 0.42,
"help": "exits the computer"
}

View File

@ -0,0 +1,7 @@
function exit(pos, input, data)
minetest.close_formspec(data.player:get_player_name(), "")
return "you shouldnt see this"
end
return exit

View File

@ -0,0 +1,7 @@
{
"name": "expr",
"license": "MIT",
"version": 0.1,
"engine": 0.42,
"help": "exicutes mathmatical expresions"
}

View File

@ -0,0 +1,14 @@
function expr(pos, input, data)
local output
local func = computers.sandbox.loadstring("return " .. input, {env={}})
if func then output = func() end
if type(output) ~= "number" or output == nil then
return "Error: invalid or missing input"
end
return tostring(output)
end
return expr

View File

@ -0,0 +1,7 @@
{
"name": "nano",
"license": "MIT",
"version": 0.1,
"engine": 0.42,
"help": "allows editing of files"
}

View File

@ -0,0 +1,113 @@
function nano(pos, input, data)
local path = computers.devicepath .. "/" .. minetest.hash_node_position(pos) .. data.element.pwd
local files = computers.api.get_dir_keyed_list(path, false)
if not input then return "no file designated" end
if not files[input:split(" ")[1]] then return "file " .. input:split(" ")[1] .. " not found" end
if computers.formspec.get_index_by_name(
computers.formspec.registered_kast[data.player:get_player_name()],
"nano_btn"
) then return "nano is already open, please close it" end
local f = io.open(path .. "/" .. input:split(" ")[1])
if not f then return "error reading file" end
local file = f:read("*all")
f:close()
--clean up if there exists a legacy nano_ctn
local nctn = computers.formspec.get_index_by_name(
computers.formspec.registered_kast[data.player:get_player_name()],
"nano_ctn"
)
if nctn then
local fs = computers.formspec.registered_kast[data.player:get_player_name()]
table.remove(fs, nctn)
computers.formspec.registered_kast[data.player:get_player_name()] = fs
minetest.chat_send_all(dump(fs))
end
local form = computers.gui.add_tab(data.player, "Nano", {
type = "container",
name = "nano_ctn",
state = 1,
x = 0,
y = 1,
{
type = "background",
x = 0,
y = 0,
w = 10,
h = 11,
texture_name = "[combine:16x16^[noalpha"
},
{
type = "textarea",
x = 0,
y = 0,
w = 10,
h = 10,
name = "nano_editor",
--read_only = 1,
--label = "test",
default = file,
close_on_enter = false,
--this breaks the scrollbar
--[[ props = {
border = false,
} ]]
},
{
type = "box",
x = 0,
y = 10,
w = 10,
h = 1,
color = "#ffffff"
},
{
type = "field",
x = 0,
y = 10,
w = 10,
h = 1,
name = "nano_commands",
close_on_enter = false,
pwd = "",
props = {
border = false,
},
on_event = function(form, player, element, value, fields)
if value == "save" and fields.nano_editor then
--minetest.chat_send_all(fields.nano_editor)
if #fields.nano_editor < 12000 then --12000 is luacheck line length of 120 * 100 lines
minetest.safe_file_write(path .. "/" .. input:split(" ")[1], fields.nano_editor)
else
computers.api.chat_send_player(player, minetest.colorize("red", "[Nano]: file is to long"))
end
elseif value == "exit" then
local btn = computers.formspec.get_index_by_name(form, "nano_btn")
local ctn = computers.formspec.get_index_by_name(form, "nano_ctn")
form[ctn].state = 1
table.remove(form, btn)
--table.remove(form, ctn) cant actually remove it from within itself, therefore we hide it
local tindex = 0
for index, tab in pairs(form.tabs) do
if tab == "nano" then tindex = index end
end
table.remove(form.tabs, tindex)
tindex = computers.formspec.get_index_by_name(form, "terminal_ctn")
form[tindex].state = 0
else
computers.api.chat_send_player(player, minetest.colorize("red", "[Nano]: invalid command"))
end
return form
end,
},
})
return form
end
return nano

View File

@ -1,7 +1,7 @@
{
"name": "touch",
"license": "MIT",
"version": 0.2,
"engine": 0.4,
"version": 0.3,
"engine": 0.42,
"help": "creates a file in the current path"
}

View File

@ -3,11 +3,14 @@ function touch(pos, input, data)
--make dir to be safe
minetest.mkdir(path)
if #minetest.get_dir_list(path, false) > 10
and not minetest.check_player_privs(data.player, "computers_filesystem") then
return "ERROR: you have reached your max file limit"
end
if input and input ~= "" and not input:find("/") then
for _, item in pairs(minetest.get_dir_list(path, nil)) do
if item == input then
return "ERROR: trying to create already existing file/folder"
end
if computers.api.get_dir_keyed_list(path, nil)[input] then
return "ERROR: trying to create already existing file/folder"
end
minetest.safe_file_write(path .. "/" .. input, "")
return "file " .. input .. " created"

View File

@ -1,5 +1,5 @@
local registered_astk = {}
computers.formspec = {}
computers.formspec.registered_kast = {}
computers.formspec.get_element_by_name = formspec_ast.get_element_by_name
@ -83,7 +83,7 @@ function computers.formspec.show_formspec(player, formname, fs)
playername = player:get_player_name()
end
if type(fs) == "table" then
registered_astk[playername] = table.copy(fs)
computers.formspec.registered_kast[playername] = table.copy(fs)
formspec = formspec_ast.unparse(computers.formspec.convert_to_ast(fs))
end
@ -94,16 +94,38 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "computers:gui" then return end
local pname = player:get_player_name()
if fields.quit then registered_astk[pname] = nil return end
if fields.quit then computers.formspec.registered_kast[pname] = nil return end
--[[
trying to figure out what a player actually did can be a mess
input can validly be nil for a text field sometimes, othertimes it can come in first
when a user has actual selected a button. buttons only come in when selected,
so we override whatever the first key is if there a button since we are sure it was pressed
since we cant get types from fields, we rely on the button being named name_btn with _btn being a suffix
]]
local keys = {}
for key, val in pairs(fields) do table.insert(keys, key) end
local btn_override
for key, val in pairs(fields) do
table.insert(keys, key)
local split = key:split("_")
if #split >= 2 and split[2] == "btn" then btn_override = key end
end
local element = computers.formspec.get_element_by_name(registered_astk[pname], keys[1])
local element = computers.formspec.get_element_by_name(
computers.formspec.registered_kast[pname],
btn_override or keys[1]
)
if element and element.on_event then
--on_event(form, player, element)
local form = element.on_event(registered_astk[pname], player, element, fields[keys[1]])
local form = element.on_event(
computers.formspec.registered_kast[pname],
player,
element,
fields[btn_override or keys[1]],
fields
)
if form then computers.formspec.show_formspec(player, formname, form) end
end

View File

@ -1,29 +1,31 @@
--local usage
computers.gui = {}
local futil = computers.formspec
function computers.load_gui(pos, node, clicker)
--minetest.chat_send_all("test")
local function select_btn(form, btn)
--to hardcoded
for _, obtn in pairs({"terminal", "browser"}) do
local cindex = futil.get_index_by_name(form, obtn .. "_btn")
form[cindex].props.bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff70"
form[cindex].props.bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff90"
local function select_btn(form, btn)
--to hardcoded
for _, obtn in pairs(form.tabs) do
local cindex = futil.get_index_by_name(form, obtn .. "_btn")
form[cindex].props.bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff70"
form[cindex].props.bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff90"
cindex = futil.get_index_by_name(form, obtn .. "_ctn")
form[cindex].state = 1
end
local cindex = futil.get_index_by_name(form, btn .. "_btn")
form[cindex].props.bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff20"
form[cindex].props.bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff40"
local aindex = futil.get_index_by_name(form, btn .. "_ctn")
form[aindex].state = 0
cindex = futil.get_index_by_name(form, obtn .. "_ctn")
form[cindex].state = 1
end
local cindex = futil.get_index_by_name(form, btn .. "_btn")
form[cindex].props.bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff20"
form[cindex].props.bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff40"
local aindex = futil.get_index_by_name(form, btn .. "_ctn")
form[aindex].state = 0
end
function computers.gui.load(pos, node, clicker)
local formspec = {
formspec_version = 4,
tabs = {"terminal", "browser"},
{
type = "size",
w = 10,
@ -150,6 +152,8 @@ function computers.load_gui(pos, node, clicker)
local output = computers.registered_commands[cdata[1]](pos, cdata[2], pass_table)
if output and type(output) == "string" then
form[cindex][eindex].default = text .. output .. "\n" .. "user:~" .. element.pwd .."$" .." "
elseif output and type(output) == "table" then
form = output
end
else
form[cindex][eindex].default = text .. value ..
@ -188,10 +192,31 @@ function computers.load_gui(pos, node, clicker)
h = 2,
name = "test_btn",
label = "test btn",
on_event = function(form, player, element)
local cindex = futil.get_index_by_name(form, "browser_ctn")
local eindex = futil.get_index_by_name(form[cindex], "test_btn")
form[cindex][eindex] = {type = "label", x=1, y=3, label = "test button label"}
on_event = function(_, player, element)
--local cindex = futil.get_index_by_name(form, "browser_ctn")
--local eindex = futil.get_index_by_name(form[cindex], "test_btn")
--form[cindex][eindex] = {type = "label", x=1, y=3, label = "test button label"}
local form = computers.gui.add_tab(player, "Tname", {
type = "container",
name = "tname_ctn",
state = 1,
x = 0,
y = 1,
{
type = "background",
x = 0,
y = 0,
w = 10,
h = 11,
texture_name = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff70"
},
{
type = "label",
x = 1,
y = 1.5,
label = "Tname pane",
},
})
return form
end,
@ -206,4 +231,65 @@ function computers.load_gui(pos, node, clicker)
}
futil.show_formspec(clicker, "computers:gui", formspec)
end
--legacy compat
computers.load_gui = computers.gui.load
--note you can create to many pages thuse overflowing the formspec, need to be fixed
function computers.gui.add_tab(player, tname, tab)
local name = player
if type(player) == "userdata" then name = player:get_player_name() end
assert(tab, "[computers.sandbox]: new tab for " .. name .. " not found")
assert(tab.type == "container", "[computers.sandbox]: invalid new tab format for " .. name)
assert(tab.name:split("_")[2] == "ctn", "[computers.sandbox]: invalid tab name for " .. name)
assert(tab.x == 0 and tab.y == 1, "[computers.sandbox]: invalid tab name for " .. name)
local formspec = table.copy(computers.formspec.registered_kast[name])
local fs = {}
local btn
for key, val in pairs(formspec) do
if type(key) == "string" then
fs[key] = val
elseif type(val) == "table" then
if val.name and #val.name:split("_") >= 2 and val.name:split("_")[2] == "btn" then
btn = 1
elseif btn and btn == 1 then
table.insert(fs, {
type = "button",
x = #formspec.tabs*2,
y = 0,
w = 2,
h = 1,
name = tname:lower() .. "_btn",
label = tname,
on_event = function(form, _, element)
select_btn(form, tname:lower())
return form
end,
props = {
border = false,
bgimg = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff70",
bgimg_hovered = "kuto_button.png^[combine:16x16^[noalpha^[colorize:#ffffff90",
bgimg_middle = "4,4",
}
})
btn = nil
end
table.insert(fs, val)
end
end
table.insert(fs, tab)
table.insert(fs.tabs, tname:lower())
return fs
end

View File

@ -8,7 +8,7 @@ minetest.mkdir(computers.devicepath) --make sure it exists
minetest.mkdir(computers.networkpath) --make sure it exists
computers.os = {
version = 0.41,
version = 0.42,
name = "kuto",
authors = {"wsor", "luk3yx"},
license = "MIT",
@ -16,6 +16,7 @@ computers.os = {
dofile(computers.modpath .. "/old_stuff/init.lua")
dofile(computers.modpath .. "/api.lua")
dofile(computers.modpath .. "/formspec.lua")
dofile(computers.modpath .. "/commands.lua")
dofile(computers.modpath .. "/gui.lua")