mirror of
https://gitlab.com/luk3yx/minetest-flow.git
synced 2025-06-17 15:48:01 +02:00
Even more input validation
This commit is contained in:
parent
6745777bfc
commit
918002d505
64
init.lua
64
init.lua
@ -701,15 +701,20 @@ local default_value_fields = {
|
|||||||
tabheader = "current_tab",
|
tabheader = "current_tab",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
local sensible_defaults = {
|
local sensible_defaults = {
|
||||||
default = "", selected = false, selected_idx = 1, value = 0,
|
default = "", selected = false, selected_idx = 1, value = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local button_types = {
|
||||||
|
button = true, image_button = true, item_image_button = true,
|
||||||
|
button_exit = true, image_button_exit = true
|
||||||
|
}
|
||||||
|
|
||||||
-- Removes on_event from a formspec_ast tree and returns a callbacks table
|
-- Removes on_event from a formspec_ast tree and returns a callbacks table
|
||||||
local function parse_callbacks(tree, ctx_form, auto_name_id,
|
local function parse_callbacks(tree, ctx_form, auto_name_id,
|
||||||
replace_backgrounds)
|
replace_backgrounds)
|
||||||
local callbacks = {}
|
local callbacks
|
||||||
|
local btn_callbacks = {}
|
||||||
local saved_fields = {}
|
local saved_fields = {}
|
||||||
local tablecolumn_count = 1
|
local tablecolumn_count = 1
|
||||||
for node in formspec_ast.walk(tree) do
|
for node in formspec_ast.walk(tree) do
|
||||||
@ -810,13 +815,34 @@ local function parse_callbacks(tree, ctx_form, auto_name_id,
|
|||||||
|
|
||||||
-- Add the on_event callback (if any) to the callbacks table
|
-- Add the on_event callback (if any) to the callbacks table
|
||||||
if node.on_event then
|
if node.on_event then
|
||||||
|
local is_btn = button_types[node.type]
|
||||||
if not node_name then
|
if not node_name then
|
||||||
node_name = ("\1%x"):format(auto_name_id)
|
node_name = ("\1%x"):format(auto_name_id)
|
||||||
node.name = node_name
|
node.name = node_name
|
||||||
auto_name_id = auto_name_id + 1
|
auto_name_id = auto_name_id + 1
|
||||||
|
elseif btn_callbacks[node_name] or
|
||||||
|
(is_btn and saved_fields[node_name]) or
|
||||||
|
(callbacks and callbacks[node_name]) then
|
||||||
|
minetest.log("warning", ("[flow] Multiple callbacks have " ..
|
||||||
|
"been registered for elements with the same name (%q), " ..
|
||||||
|
"this will not work properly."):format(node_name))
|
||||||
|
|
||||||
|
-- Preserve previous behaviour
|
||||||
|
btn_callbacks[node_name] = nil
|
||||||
|
if callbacks then
|
||||||
|
callbacks[node_name] = nil
|
||||||
|
end
|
||||||
|
is_btn = is_btn and not saved_fields[node_name]
|
||||||
end
|
end
|
||||||
|
|
||||||
callbacks[node_name] = node.on_event
|
-- Put buttons into a separate callback table so that malicious
|
||||||
|
-- clients can't send multiple button presses in one submission
|
||||||
|
if is_btn then
|
||||||
|
btn_callbacks[node_name] = node.on_event
|
||||||
|
else
|
||||||
|
callbacks = callbacks or {}
|
||||||
|
callbacks[node_name] = node.on_event
|
||||||
|
end
|
||||||
node.on_event = nil
|
node.on_event = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -826,7 +852,7 @@ local function parse_callbacks(tree, ctx_form, auto_name_id,
|
|||||||
node._after_positioned = nil
|
node._after_positioned = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return callbacks, saved_fields, auto_name_id
|
return callbacks, btn_callbacks, saved_fields, auto_name_id
|
||||||
end
|
end
|
||||||
|
|
||||||
local gui_mt = {
|
local gui_mt = {
|
||||||
@ -903,14 +929,15 @@ function Form:_render(player, ctx, formspec_version, id1, embedded, lang_code)
|
|||||||
if not id1 or id1 > 1e6 then id1 = 0 end
|
if not id1 or id1 > 1e6 then id1 = 0 end
|
||||||
|
|
||||||
local tree = render_ast(box, embedded)
|
local tree = render_ast(box, embedded)
|
||||||
local callbacks, saved_fields, id2 = parse_callbacks(tree, orig_form, id1,
|
local callbacks, btn_callbacks, saved_fields, id2 = parse_callbacks(
|
||||||
embedded)
|
tree, orig_form, id1, embedded
|
||||||
|
)
|
||||||
|
|
||||||
local redraw_if_changed = {}
|
local redraw_if_changed = {}
|
||||||
for var in pairs(used_ctx_vars) do
|
for var in pairs(used_ctx_vars) do
|
||||||
-- Only add it if there is no callback and the name exists in the
|
-- Only add it if there is no callback and the name exists in the
|
||||||
-- formspec.
|
-- formspec.
|
||||||
if saved_fields[var] and not callbacks[var] then
|
if saved_fields[var] and (not callbacks or not callbacks[var]) then
|
||||||
redraw_if_changed[var] = true
|
redraw_if_changed[var] = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -920,6 +947,7 @@ function Form:_render(player, ctx, formspec_version, id1, embedded, lang_code)
|
|||||||
return tree, {
|
return tree, {
|
||||||
self = self,
|
self = self,
|
||||||
callbacks = callbacks,
|
callbacks = callbacks,
|
||||||
|
btn_callbacks = btn_callbacks,
|
||||||
saved_fields = saved_fields,
|
saved_fields = saved_fields,
|
||||||
redraw_if_changed = redraw_if_changed,
|
redraw_if_changed = redraw_if_changed,
|
||||||
ctx = ctx,
|
ctx = ctx,
|
||||||
@ -1093,6 +1121,7 @@ end
|
|||||||
-- Declared locally above to be accessible to render_to_formspec_string
|
-- Declared locally above to be accessible to render_to_formspec_string
|
||||||
function fs_process_events(player, form_info, fields)
|
function fs_process_events(player, form_info, fields)
|
||||||
local callbacks = form_info.callbacks
|
local callbacks = form_info.callbacks
|
||||||
|
local btn_callbacks = form_info.btn_callbacks
|
||||||
local ctx = form_info.ctx
|
local ctx = form_info.ctx
|
||||||
local redraw_if_changed = form_info.redraw_if_changed
|
local redraw_if_changed = form_info.redraw_if_changed
|
||||||
local ctx_form = ctx.form
|
local ctx_form = ctx.form
|
||||||
@ -1128,9 +1157,26 @@ function fs_process_events(player, form_info, fields)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Run on_event callbacks
|
-- Run on_event callbacks
|
||||||
|
-- The callbacks table may be nil as adding callbacks to non-buttons is
|
||||||
|
-- likely uncommon (so allocating an empty table would be useless)
|
||||||
|
if callbacks then
|
||||||
|
for field in pairs(fields) do
|
||||||
|
if callbacks[field] and callbacks[field](player, ctx) then
|
||||||
|
redraw_fs = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Run button callbacks after all other callbacks as that seems to be the
|
||||||
|
-- most intuitive thing to do
|
||||||
|
-- Note: Try not to rely on the order of on_event callbacks, I may change
|
||||||
|
-- it in the future.
|
||||||
for field in pairs(fields) do
|
for field in pairs(fields) do
|
||||||
if callbacks[field] and callbacks[field](player, ctx) then
|
if btn_callbacks[field] then
|
||||||
redraw_fs = true
|
redraw_fs = btn_callbacks[field](player, ctx) or redraw_fs
|
||||||
|
|
||||||
|
-- Only run a single button callback
|
||||||
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
42
test.lua
42
test.lua
@ -251,15 +251,18 @@ describe("Flow", function()
|
|||||||
|
|
||||||
it("registers callbacks", function()
|
it("registers callbacks", function()
|
||||||
local function func() end
|
local function func() end
|
||||||
|
local function func2() return true end
|
||||||
|
|
||||||
local tree, state = render(function(player, ctx)
|
local tree, state = render(function(player, ctx)
|
||||||
return gui.VBox{
|
return gui.VBox{
|
||||||
gui.Label{label = "Callback demo:"},
|
gui.Label{label = "Callback demo:"},
|
||||||
gui.Button{label = "Click me!", name = "btn", on_event = func},
|
gui.Button{label = "Click me!", name = "btn", on_event = func},
|
||||||
|
gui.Field{name = "field", on_event = func2}
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert.same(state.callbacks, {btn = func})
|
assert.same(state.callbacks, {field = func2})
|
||||||
|
assert.same(state.btn_callbacks, {btn = func})
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("handles visible = false", function()
|
it("handles visible = false", function()
|
||||||
@ -697,11 +700,38 @@ describe("Flow", function()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it("does not save form input for Button", function()
|
describe("Button", function()
|
||||||
local ctx, event = render_to_string(gui.Button{name = "a"})
|
it("does not save form input", function()
|
||||||
assert.equals(ctx.form.a, nil)
|
local ctx, event = render_to_string(gui.Button{name = "a"})
|
||||||
event({a = "test"})
|
assert.equals(ctx.form.a, nil)
|
||||||
assert.equals(ctx.form.a, nil)
|
event({a = "test"})
|
||||||
|
assert.equals(ctx.form.a, nil)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("only calls a single callback", function()
|
||||||
|
local f, b = 0, 0
|
||||||
|
|
||||||
|
local ctx, event = render_to_string(gui.VBox{
|
||||||
|
gui.Field{name = "a", on_event = function() f = f + 1 end},
|
||||||
|
gui.Button{name = "b", on_event = function() b = b + 1 end},
|
||||||
|
gui.Button{name = "c", on_event = function() b = b + 1 end}
|
||||||
|
})
|
||||||
|
event({})
|
||||||
|
assert.equals(f, 0)
|
||||||
|
assert.equals(b, 0)
|
||||||
|
|
||||||
|
event({a = "test", b = "test", c = "test"})
|
||||||
|
assert.equals(f, 1)
|
||||||
|
assert.equals(b, 1)
|
||||||
|
|
||||||
|
event({b = "test", c = "test"})
|
||||||
|
assert.equals(f, 1)
|
||||||
|
assert.equals(b, 2)
|
||||||
|
|
||||||
|
event({c = "test"})
|
||||||
|
assert.equals(f, 1)
|
||||||
|
assert.equals(b, 3)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
Loading…
Reference in New Issue
Block a user