Lazy load safety.lua and fix some issues
This commit is contained in:
parent
a42f688274
commit
ec24aed60a
@ -22,14 +22,17 @@ A Minetest mod library to make modifying formspecs easier.
|
|||||||
- `formspec_ast.apply_offset(tree, x, y)`: Shifts all elements in `tree`.
|
- `formspec_ast.apply_offset(tree, x, y)`: Shifts all elements in `tree`.
|
||||||
Similar to `container`.
|
Similar to `container`.
|
||||||
- `formspec_ast.flatten(tree)`: Removes all containers and offsets elements
|
- `formspec_ast.flatten(tree)`: Removes all containers and offsets elements
|
||||||
that were in containers accordingly. **The use of this function is
|
that were in containers accordingly. Note that `scroll_container[]`
|
||||||
discouraged as `scroll_container[]` elements are not flattened.**
|
elements are not flattened.
|
||||||
- `formspec_ast.show_formspec(player_or_name, formname, formspec)`: Similar
|
- `formspec_ast.show_formspec(player_or_name, formname, formspec)`: Similar
|
||||||
to `minetest.show_formspec`, however also accepts player objects and will
|
to `minetest.show_formspec`, however also accepts player objects and will
|
||||||
pass `formspec` through `formspec_ast.interpret` first.
|
pass `formspec` through `formspec_ast.interpret` first.
|
||||||
- `formspec_ast.safe_parse(string_or_tree)`: Similar to `formspec_ast.parse`,
|
- `formspec_ast.safe_parse(string_or_tree)`: Similar to `formspec_ast.parse`,
|
||||||
however will delete any elements that may crash the client (or any I
|
however will delete any elements that may crash the client (or any I
|
||||||
haven't added to the safe element list).
|
haven't added to the safe element list). The safe element list that this
|
||||||
|
function uses is very limited, it may break complex formspecs.
|
||||||
|
- `formspec_ast.safe_interpret(string_or_tree)`: Equivalent to
|
||||||
|
`formspec_ast.unparse(formspec_ast.safe_parse(string_or_tree))`.
|
||||||
- `formspec_ast.formspec_escape(text)`: The same as `minetest.formspec_escape`,
|
- `formspec_ast.formspec_escape(text)`: The same as `minetest.formspec_escape`,
|
||||||
should only be used when formspec_ast is being embedded outside of Minetest.
|
should only be used when formspec_ast is being embedded outside of Minetest.
|
||||||
|
|
||||||
|
8
core.lua
8
core.lua
@ -1,12 +1,12 @@
|
|||||||
--
|
--
|
||||||
-- formspec_ast: An abstract system tree for formspecs.
|
-- formspec_ast: An abstract syntax tree for formspecs.
|
||||||
--
|
--
|
||||||
-- This does not actually depend on Minetest and could probably run in
|
-- This does not actually depend on Minetest and could probably run in
|
||||||
-- standalone Lua.
|
-- standalone Lua.
|
||||||
--
|
--
|
||||||
-- The MIT License (MIT)
|
-- The MIT License (MIT)
|
||||||
--
|
--
|
||||||
-- Copyright © 2019 by luk3yx.
|
-- Copyright © 2019-2022 by luk3yx.
|
||||||
--
|
--
|
||||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
-- of this software and associated documentation files (the "Software"), to
|
-- of this software and associated documentation files (the "Software"), to
|
||||||
@ -541,8 +541,8 @@ end
|
|||||||
|
|
||||||
-- Allow other mods to access raw_parse and raw_unparse. Note that these may
|
-- Allow other mods to access raw_parse and raw_unparse. Note that these may
|
||||||
-- change or be removed at any time.
|
-- change or be removed at any time.
|
||||||
formspec_ast._raw_parse = raw_parse
|
-- formspec_ast._raw_parse = raw_parse
|
||||||
formspec_ast._raw_unparse = raw_unparse
|
-- formspec_ast._raw_unparse = raw_unparse
|
||||||
|
|
||||||
-- Register custom elements
|
-- Register custom elements
|
||||||
parse_mt = {}
|
parse_mt = {}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
--
|
--
|
||||||
-- formspec_ast: An abstract system tree for formspecs.
|
-- formspec_ast: An abstract syntax tree for formspecs.
|
||||||
--
|
--
|
||||||
-- This does not actually depend on Minetest and could probably run in
|
-- This does not actually depend on Minetest and could probably run in
|
||||||
-- standalone Lua.
|
-- standalone Lua.
|
||||||
--
|
--
|
||||||
-- The MIT License (MIT)
|
-- The MIT License (MIT)
|
||||||
--
|
--
|
||||||
-- Copyright © 2019 by luk3yx.
|
-- Copyright © 2019-2022 by luk3yx.
|
||||||
--
|
--
|
||||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
-- of this software and associated documentation files (the "Software"), to
|
-- of this software and associated documentation files (the "Software"), to
|
||||||
@ -29,6 +29,9 @@
|
|||||||
|
|
||||||
local formspec_ast, minetest = formspec_ast, formspec_ast.minetest
|
local formspec_ast, minetest = formspec_ast, formspec_ast.minetest
|
||||||
|
|
||||||
|
-- Expose minetest.formspec_escape for use outside of Minetest
|
||||||
|
formspec_ast.formspec_escape = minetest.formspec_escape
|
||||||
|
|
||||||
-- Parses and unparses plain formspecs and just unparses AST trees.
|
-- Parses and unparses plain formspecs and just unparses AST trees.
|
||||||
function formspec_ast.interpret(spec, custom_handlers)
|
function formspec_ast.interpret(spec, custom_handlers)
|
||||||
local ast = spec
|
local ast = spec
|
||||||
|
17
init.lua
17
init.lua
@ -1,12 +1,12 @@
|
|||||||
--
|
--
|
||||||
-- formspec_ast: An abstract system tree for formspecs.
|
-- formspec_ast: An abstract syntax tree for formspecs.
|
||||||
--
|
--
|
||||||
-- This does not actually depend on Minetest and could probably run in
|
-- This does not actually depend on Minetest and could probably run in
|
||||||
-- standalone Lua.
|
-- standalone Lua.
|
||||||
--
|
--
|
||||||
-- The MIT License (MIT)
|
-- The MIT License (MIT)
|
||||||
--
|
--
|
||||||
-- Copyright © 2019 by luk3yx.
|
-- Copyright © 2019-2022 by luk3yx.
|
||||||
--
|
--
|
||||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
-- of this software and associated documentation files (the "Software"), to
|
-- of this software and associated documentation files (the "Software"), to
|
||||||
@ -37,7 +37,6 @@ if minetest then
|
|||||||
modpath = minetest.get_modpath('formspec_ast')
|
modpath = minetest.get_modpath('formspec_ast')
|
||||||
assert(minetest.get_current_modname() == 'formspec_ast',
|
assert(minetest.get_current_modname() == 'formspec_ast',
|
||||||
'This mod must be called formspec_ast!')
|
'This mod must be called formspec_ast!')
|
||||||
formspec_ast.formspec_escape = minetest.formspec_escape
|
|
||||||
else
|
else
|
||||||
-- Probably running outside Minetest.
|
-- Probably running outside Minetest.
|
||||||
modpath = rawget(_G, 'FORMSPEC_AST_PATH') or '.'
|
modpath = rawget(_G, 'FORMSPEC_AST_PATH') or '.'
|
||||||
@ -69,13 +68,21 @@ else
|
|||||||
return res
|
return res
|
||||||
end
|
end
|
||||||
formspec_ast.minetest = minetest
|
formspec_ast.minetest = minetest
|
||||||
formspec_ast.formspec_escape = minetest.formspec_escape
|
|
||||||
end
|
end
|
||||||
|
|
||||||
formspec_ast.modpath = modpath
|
formspec_ast.modpath = modpath
|
||||||
|
|
||||||
dofile(modpath .. '/core.lua')
|
dofile(modpath .. '/core.lua')
|
||||||
dofile(modpath .. '/helpers.lua')
|
dofile(modpath .. '/helpers.lua')
|
||||||
dofile(modpath .. '/safety.lua')
|
|
||||||
|
-- Lazy load safety.lua because I don't think anything actually uses it
|
||||||
|
function formspec_ast.safe_parse(...)
|
||||||
|
dofile(modpath .. '/safety.lua')
|
||||||
|
return formspec_ast.safe_parse(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
function formspec_ast.safe_interpret(tree)
|
||||||
|
return formspec_ast.unparse(formspec_ast.safe_parse(tree))
|
||||||
|
end
|
||||||
|
|
||||||
formspec_ast.modpath, formspec_ast.minetest = nil, nil
|
formspec_ast.modpath, formspec_ast.minetest = nil, nil
|
||||||
|
92
safety.lua
92
safety.lua
@ -1,12 +1,12 @@
|
|||||||
--
|
--
|
||||||
-- formspec_ast: An abstract system tree for formspecs.
|
-- formspec_ast: An abstract syntax tree for formspecs.
|
||||||
--
|
--
|
||||||
-- This verifies that formspecs from untrusted sources are safe(-ish) to
|
-- This verifies that formspecs from untrusted sources are safe(-ish) to
|
||||||
-- display, provided they are passed through formspec_ast.interpret.
|
-- display, provided they are passed through formspec_ast.interpret.
|
||||||
--
|
--
|
||||||
-- The MIT License (MIT)
|
-- The MIT License (MIT)
|
||||||
--
|
--
|
||||||
-- Copyright © 2019 by luk3yx.
|
-- Copyright © 2019-2022 by luk3yx.
|
||||||
--
|
--
|
||||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
-- Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
-- of this software and associated documentation files (the "Software"), to
|
-- of this software and associated documentation files (the "Software"), to
|
||||||
@ -27,8 +27,8 @@
|
|||||||
-- IN THE SOFTWARE.
|
-- IN THE SOFTWARE.
|
||||||
--
|
--
|
||||||
|
|
||||||
-- Similar to ast.walk(), however returns {} and then exits if walk() would
|
-- Similar to ast.walk(), however returns nil if walk() would crash. Use this
|
||||||
-- crash. Use this for untrusted formspecs, otherwise use walk() for speed.
|
-- for untrusted formspecs, otherwise use walk() for speed.
|
||||||
local function safe_walk(tree)
|
local function safe_walk(tree)
|
||||||
local walk = formspec_ast.walk(tree)
|
local walk = formspec_ast.walk(tree)
|
||||||
local seen = {}
|
local seen = {}
|
||||||
@ -36,13 +36,9 @@ local function safe_walk(tree)
|
|||||||
if not walk or not seen then return end
|
if not walk or not seen then return end
|
||||||
|
|
||||||
local good, msg = pcall(walk)
|
local good, msg = pcall(walk)
|
||||||
if good and (type(msg) == 'table' or msg == nil) and not seen[msg] then
|
if good and type(msg) == 'table' and not seen[msg] then
|
||||||
if msg then
|
seen[msg] = true
|
||||||
seen[msg] = true
|
|
||||||
end
|
|
||||||
return msg
|
return msg
|
||||||
else
|
|
||||||
return {}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -52,7 +48,7 @@ local function safe_flatten(tree)
|
|||||||
local res = {formspec_version = 1}
|
local res = {formspec_version = 1}
|
||||||
if type(tree.formspec_version) == 'number' and
|
if type(tree.formspec_version) == 'number' and
|
||||||
tree.formspec_version > 1 then
|
tree.formspec_version > 1 then
|
||||||
res.formspec_version = 2
|
res.formspec_version = math.min(math.floor(tree.formspec_version), 6)
|
||||||
end
|
end
|
||||||
for elem in safe_walk(table.copy(tree)) do
|
for elem in safe_walk(table.copy(tree)) do
|
||||||
if elem.type == 'container' then
|
if elem.type == 'container' then
|
||||||
@ -76,16 +72,40 @@ function ensure.string(obj)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function ensure.number(obj, max, min)
|
function ensure.number(obj, max, min)
|
||||||
|
if obj == nil then return end
|
||||||
|
|
||||||
local res = tonumber(obj)
|
local res = tonumber(obj)
|
||||||
assert(res ~= nil and res == res)
|
assert(res ~= nil and res == res)
|
||||||
assert(res <= (max or 100) and res >= (min or 0))
|
assert(res <= (max or 100) and res >= (min or 0))
|
||||||
return res
|
return res
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function ensure.boolean(bool)
|
||||||
|
assert(type(bool) == "boolean" or bool == nil)
|
||||||
|
return bool
|
||||||
|
end
|
||||||
|
|
||||||
function ensure.integer(obj)
|
function ensure.integer(obj)
|
||||||
return math.floor(ensure.number(obj))
|
return math.floor(ensure.number(obj))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function ensure.texture(obj)
|
||||||
|
return ensure.string(obj):match("^[^%[]+")
|
||||||
|
end
|
||||||
|
|
||||||
|
function ensure.list(items)
|
||||||
|
assert(type(items) == 'table')
|
||||||
|
for k, v in pairs(items) do
|
||||||
|
assert(type(k) == 'number' and type(v) == 'string')
|
||||||
|
end
|
||||||
|
return items
|
||||||
|
end
|
||||||
|
|
||||||
|
function ensure.inventory_location(location)
|
||||||
|
assert(location == 'current_node' or location == 'current_player')
|
||||||
|
return location
|
||||||
|
end
|
||||||
|
|
||||||
local validate
|
local validate
|
||||||
local function validate_elem(obj)
|
local function validate_elem(obj)
|
||||||
local template = validate[obj.type]
|
local template = validate[obj.type]
|
||||||
@ -95,15 +115,11 @@ local function validate_elem(obj)
|
|||||||
if k == 'type' then
|
if k == 'type' then
|
||||||
func = ensure.string
|
func = ensure.string
|
||||||
else
|
else
|
||||||
local type_ = template[k]
|
local value_type = template[k]
|
||||||
if type(type_) == 'string' then
|
if value_type and value_type:sub(-1) == '?' then
|
||||||
if type_:sub(#type_) == '?' then
|
value_type = value_type:sub(1, -2)
|
||||||
type_ = type_:sub(1, #type_ - 1)
|
|
||||||
end
|
|
||||||
func = ensure[type_]
|
|
||||||
elseif type(type_) == 'function' then
|
|
||||||
func = type_
|
|
||||||
end
|
end
|
||||||
|
func = ensure[value_type]
|
||||||
end
|
end
|
||||||
|
|
||||||
if func then
|
if func then
|
||||||
@ -114,7 +130,7 @@ local function validate_elem(obj)
|
|||||||
end
|
end
|
||||||
|
|
||||||
for k, v in pairs(template) do
|
for k, v in pairs(template) do
|
||||||
if type(v) ~= 'string' or v:sub(#v) ~= '?' then
|
if v:sub(-1) ~= '?' then
|
||||||
assert(obj[k] ~= nil, k .. ' does not exist!')
|
assert(obj[k] ~= nil, k .. ' does not exist!')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -124,16 +140,16 @@ validate = {
|
|||||||
size = {w = 'number', h = 'number'},
|
size = {w = 'number', h = 'number'},
|
||||||
label = {x = 'number', y = 'number', label = 'string'},
|
label = {x = 'number', y = 'number', label = 'string'},
|
||||||
image = {x = 'number', y = 'number', w = 'number', h = 'number',
|
image = {x = 'number', y = 'number', w = 'number', h = 'number',
|
||||||
texture_name = 'string'},
|
texture_name = 'texture'},
|
||||||
button = {x = 'number', y = 'number', w = 'number', h = 'number',
|
button = {x = 'number', y = 'number', w = 'number', h = 'number',
|
||||||
name = 'string', label = 'string'},
|
name = 'string', label = 'string'},
|
||||||
image_button = {x = 'number', y = 'number', w = 'number', h = 'number',
|
image_button = {x = 'number', y = 'number', w = 'number', h = 'number',
|
||||||
name = 'string', label = 'string', texture_name = 'string',
|
name = 'string', label = 'string', texture_name = 'texture',
|
||||||
noclip = 'string', drawborder = 'string',
|
noclip = 'string', drawborder = 'string',
|
||||||
pressed_texture_name = 'string'},
|
pressed_texture_name = 'texture'},
|
||||||
item_image_button = {x = 'number', y = 'number', w = 'number',
|
item_image_button = {x = 'number', y = 'number', w = 'number',
|
||||||
h = 'number', name = 'string', label = 'string',
|
h = 'number', name = 'string', label = 'string',
|
||||||
texture_name = 'string'},
|
texture_name = 'texture'},
|
||||||
field = {x = 'number', y = 'number', w = 'number', h = 'number',
|
field = {x = 'number', y = 'number', w = 'number', h = 'number',
|
||||||
name = 'string', label = 'string', default = 'string'},
|
name = 'string', label = 'string', default = 'string'},
|
||||||
pwdfield = {x = 'number', y = 'number', w = 'number', h = 'number',
|
pwdfield = {x = 'number', y = 'number', w = 'number', h = 'number',
|
||||||
@ -142,14 +158,13 @@ validate = {
|
|||||||
textarea = {x = 'number', y = 'number', w = 'number', h = 'number',
|
textarea = {x = 'number', y = 'number', w = 'number', h = 'number',
|
||||||
name = 'string', label = 'string', default = 'string'},
|
name = 'string', label = 'string', default = 'string'},
|
||||||
dropdown = {
|
dropdown = {
|
||||||
x = 'number', y = 'number', w = 'number', name = 'string',
|
x = 'number', y = 'number', w = 'number', h = 'number?',
|
||||||
items = function(items)
|
name = 'string', items = 'list', selected_idx = 'integer',
|
||||||
assert(type(items) == 'list')
|
},
|
||||||
for k, v in pairs(items) do
|
textlist = {
|
||||||
assert(type(k) == 'number' and type(v) == 'string')
|
x = 'number', y = 'number', w = 'number', h = 'number?',
|
||||||
end
|
name = 'string', listelems = 'list', selected_idx = 'integer?',
|
||||||
end,
|
transparent = 'boolean?',
|
||||||
selected_idx = 'integer',
|
|
||||||
},
|
},
|
||||||
checkbox = {x = 'number', y = 'number', name = 'string', label = 'string',
|
checkbox = {x = 'number', y = 'number', name = 'string', label = 'string',
|
||||||
selected = 'string'},
|
selected = 'string'},
|
||||||
@ -157,12 +172,9 @@ validate = {
|
|||||||
color = 'string'},
|
color = 'string'},
|
||||||
|
|
||||||
list = {
|
list = {
|
||||||
inventory_location = function(location)
|
inventory_location = 'inventory_location', list_name = 'string',
|
||||||
assert(location == 'current_node' or location == 'current_player')
|
x = 'number', y = 'number', w = 'number', h = 'number',
|
||||||
return location
|
starting_item_index = 'number?',
|
||||||
end,
|
|
||||||
list_name = 'string', x = 'number', y = 'number', w = 'number',
|
|
||||||
h = 'number', starting_item_index = 'number?',
|
|
||||||
},
|
},
|
||||||
listring = {},
|
listring = {},
|
||||||
}
|
}
|
||||||
@ -196,7 +208,3 @@ function formspec_ast.safe_parse(tree, custom_handlers)
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
end
|
end
|
||||||
|
|
||||||
function formspec_ast.safe_interpret(tree)
|
|
||||||
return formspec_ast.unparse(formspec_ast.safe_parse(tree))
|
|
||||||
end
|
|
||||||
|
16
tests.lua
16
tests.lua
@ -562,4 +562,20 @@ assert_equal(assert(formspec_ast.interpret('label[1,2;abc\\')),
|
|||||||
assert_equal(assert(formspec_ast.interpret('label[1,2;abc\\\\')),
|
assert_equal(assert(formspec_ast.interpret('label[1,2;abc\\\\')),
|
||||||
'label[1,2;abc\\\\]')
|
'label[1,2;abc\\\\]')
|
||||||
|
|
||||||
|
assert_equal(formspec_ast.formspec_escape('label[1,2;abc\\def]'),
|
||||||
|
'label\\[1\\,2\\;abc\\\\def\\]')
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
formspec_ast.safe_interpret([[
|
||||||
|
formspec_version[5.1]
|
||||||
|
size[3,3]
|
||||||
|
label[0,0;Hi]
|
||||||
|
image[1,2;3,4;a^b\[c]
|
||||||
|
formspec_ast:crash[]
|
||||||
|
textlist[1,2;3,4;test;a,b,c]
|
||||||
|
]]),
|
||||||
|
'formspec_version[5]size[3,3]label[0,0;Hi]image[1,2;3,4;a^b]' ..
|
||||||
|
'textlist[1,2;3,4;test;a,b,c]'
|
||||||
|
)
|
||||||
|
|
||||||
print('Tests pass')
|
print('Tests pass')
|
||||||
|
Loading…
Reference in New Issue
Block a user