2014-09-23 18:06:30 +03:00
|
|
|
-- Buildat: extension/urho3d
|
2014-09-22 21:42:00 +03:00
|
|
|
-- http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
-- Copyright 2014 Perttu Ahola <celeron55@gmail.com>
|
2014-09-23 18:06:30 +03:00
|
|
|
local log = buildat.Logger("extension/urho3d")
|
|
|
|
local dump = buildat.dump
|
|
|
|
local M = {safe = {}}
|
2014-09-22 21:42:00 +03:00
|
|
|
|
2014-09-26 08:55:10 +03:00
|
|
|
--
|
|
|
|
-- Safe interface
|
|
|
|
--
|
|
|
|
|
2014-09-22 21:42:00 +03:00
|
|
|
-- Set every plain value in global environment to the sandbox
|
2014-09-24 00:00:24 +03:00
|
|
|
-- ...it's maybe safe enough... TODO: Not safe
|
2014-09-22 21:42:00 +03:00
|
|
|
for k, v in pairs(_G) do
|
|
|
|
if type(v) == 'number' or type(v) == 'string' then
|
|
|
|
--log:info("Setting sandbox["..k.."] = "..buildat.dump(v))
|
2014-09-23 18:06:30 +03:00
|
|
|
M.safe[k] = _G[k]
|
2014-09-22 21:42:00 +03:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- TODO: Require explicit whitelisting of classes, method/function argument and
|
|
|
|
-- property types
|
|
|
|
|
|
|
|
local safe_globals = {
|
|
|
|
-- Instances
|
|
|
|
"cache",
|
|
|
|
"ui",
|
|
|
|
"renderer",
|
|
|
|
"input",
|
|
|
|
-- Types
|
|
|
|
"Scene",
|
|
|
|
"Text",
|
|
|
|
"Color",
|
|
|
|
"Vector3",
|
|
|
|
"Quaternion",
|
|
|
|
"Viewport",
|
|
|
|
"CustomGeometry",
|
|
|
|
"Texture",
|
|
|
|
"Material",
|
|
|
|
-- Functions
|
|
|
|
"Random",
|
|
|
|
"Clamp",
|
|
|
|
-- WTF properties
|
2014-09-24 00:37:23 +03:00
|
|
|
"KEY_UP",
|
|
|
|
"KEY_DOWN",
|
|
|
|
"KEY_LEFT",
|
|
|
|
"KEY_RIGHT",
|
2014-09-22 21:42:00 +03:00
|
|
|
"KEY_W",
|
|
|
|
"KEY_S",
|
|
|
|
"KEY_A",
|
|
|
|
"KEY_D",
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, v in ipairs(safe_globals) do
|
2014-09-23 18:06:30 +03:00
|
|
|
M.safe[v] = _G[v]
|
2014-09-22 21:42:00 +03:00
|
|
|
end
|
|
|
|
|
2014-09-24 00:00:24 +03:00
|
|
|
-- ResourceCache
|
|
|
|
|
|
|
|
-- Checks that this is not an absolute file path or anything funny
|
|
|
|
local allowed_name_pattern = '^[a-zA-Z0-9][a-zA-Z0-9/._ ]*$'
|
2014-09-24 10:58:46 +03:00
|
|
|
function M.check_safe_resource_name(name)
|
2014-09-24 00:00:24 +03:00
|
|
|
if type(name) ~= "string" then
|
2014-09-24 10:58:46 +03:00
|
|
|
error("Unsafe resource name: "..dump(name).." (not string)")
|
2014-09-24 00:00:24 +03:00
|
|
|
end
|
|
|
|
if string.match(name, '^/.*$') then
|
2014-09-24 10:58:46 +03:00
|
|
|
error("Unsafe resource name: "..dump(name).." (absolute path)")
|
2014-09-24 00:00:24 +03:00
|
|
|
end
|
|
|
|
if not string.match(name, allowed_name_pattern) then
|
2014-09-24 10:58:46 +03:00
|
|
|
error("Unsafe resource name: "..dump(name).." (unneeded chars)")
|
2014-09-24 00:00:24 +03:00
|
|
|
end
|
|
|
|
if string.match(name, '[.][.]') then
|
2014-09-24 10:58:46 +03:00
|
|
|
error("Unsafe resource name: "..dump(name).." (contains ..)")
|
2014-09-24 00:00:24 +03:00
|
|
|
end
|
2014-09-24 10:58:46 +03:00
|
|
|
log:verbose("Safe resource name: "..name)
|
2014-09-24 00:00:24 +03:00
|
|
|
return name
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Basic tests
|
|
|
|
assert(pcall(function()
|
|
|
|
M.check_safe_resource_name("/etc/passwd")
|
|
|
|
end) == false)
|
|
|
|
assert(pcall(function()
|
|
|
|
M.check_safe_resource_name(" /etc/passwd")
|
|
|
|
end) == false)
|
|
|
|
assert(pcall(function()
|
|
|
|
M.check_safe_resource_name("\t /etc/passwd")
|
|
|
|
end) == false)
|
|
|
|
assert(pcall(function()
|
|
|
|
M.check_safe_resource_name("Models/Box.mdl")
|
|
|
|
end) == true)
|
|
|
|
assert(pcall(function()
|
|
|
|
M.check_safe_resource_name("Fonts/Anonymous Pro.ttf")
|
|
|
|
end) == true)
|
|
|
|
assert(pcall(function()
|
|
|
|
M.check_safe_resource_name("test1/pink_texture.png")
|
|
|
|
end) == true)
|
|
|
|
assert(pcall(function()
|
|
|
|
M.check_safe_resource_name(" Box.mdl ")
|
|
|
|
end) == false)
|
|
|
|
assert(pcall(function()
|
|
|
|
M.check_safe_resource_name("../../foo")
|
|
|
|
end) == false)
|
|
|
|
assert(pcall(function()
|
|
|
|
M.check_safe_resource_name("abc$de")
|
|
|
|
end) == false)
|
|
|
|
|
2014-09-24 10:58:46 +03:00
|
|
|
local hack_resaved_files = {} -- name -> temporary target file
|
2014-09-24 00:00:24 +03:00
|
|
|
|
|
|
|
-- Create temporary file with wanted file name to make Urho3D load it correctly
|
|
|
|
function M.resave_file(resource_name)
|
|
|
|
M.check_safe_resource_name(resource_name)
|
2014-09-24 10:58:46 +03:00
|
|
|
local path2 = hack_resaved_files[resource_name]
|
2014-09-24 00:00:24 +03:00
|
|
|
if path2 == nil then
|
2014-09-24 10:58:46 +03:00
|
|
|
local path = __buildat_get_file_path(resource_name)
|
|
|
|
if path == nil then
|
|
|
|
return nil
|
|
|
|
end
|
2014-09-24 00:00:24 +03:00
|
|
|
path2 = __buildat_get_path("tmp").."/"..resource_name
|
|
|
|
dir2 = string.match(path2, '^(.*)/.+$')
|
|
|
|
if dir2 then
|
|
|
|
if not __buildat_mkdir(dir2) then
|
|
|
|
error("Failed to create directory: \""..dir2.."\"")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
log:info("Temporary path: "..path2)
|
|
|
|
local src = io.open(path, "rb")
|
|
|
|
local dst = io.open(path2, "wb")
|
|
|
|
while true do
|
|
|
|
local buf = src:read(100000)
|
|
|
|
if buf == nil then break end
|
|
|
|
dst:write(buf)
|
|
|
|
end
|
|
|
|
src:close()
|
|
|
|
dst:close()
|
2014-09-24 10:58:46 +03:00
|
|
|
hack_resaved_files[resource_name] = path2
|
2014-09-24 00:00:24 +03:00
|
|
|
end
|
|
|
|
return path2
|
|
|
|
end
|
|
|
|
|
2014-09-24 10:58:46 +03:00
|
|
|
-- Callback from core
|
|
|
|
function __buildat_file_updated_in_cache(name, hash, cached_path)
|
|
|
|
log:debug("__buildat_file_updated_in_cache(): name="..dump(name)..
|
|
|
|
", cached_path="..dump(cached_path))
|
|
|
|
if hack_resaved_files[name] then
|
|
|
|
log:verbose("__buildat_file_updated_in_cache(): Re-saving: "..dump(name))
|
|
|
|
hack_resaved_files[name] = nil -- Force re-copy
|
|
|
|
M.resave_file(name)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-09-24 00:00:24 +03:00
|
|
|
M.safe.cache = {
|
|
|
|
GetResource = function(self, resource_type, resource_name)
|
2014-09-26 11:47:16 +03:00
|
|
|
-- TODO: If resource_type=XMLFile, we probably have to go through it and
|
|
|
|
-- resave all references to other files found in there
|
2014-09-24 00:00:24 +03:00
|
|
|
resource_name = M.check_safe_resource_name(resource_name)
|
2014-09-26 11:47:16 +03:00
|
|
|
M.resave_file(resource_name)
|
2014-09-24 00:00:24 +03:00
|
|
|
return cache:GetResource(resource_type, resource_name)
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
|
|
|
|
-- SubscribeToEvent
|
|
|
|
|
2014-09-26 08:55:10 +03:00
|
|
|
local sandbox_callback_to_global_function_name = {}
|
|
|
|
local next_sandbox_global_function_i = 1
|
2014-09-22 21:42:00 +03:00
|
|
|
|
2014-09-26 11:47:16 +03:00
|
|
|
function M.safe.SubscribeToEvent(x, y, z)
|
|
|
|
local object = x
|
|
|
|
local event_name = y
|
|
|
|
local callback = z
|
|
|
|
if callback == nil then
|
|
|
|
object = nil
|
|
|
|
event_name = x
|
|
|
|
callback = y
|
|
|
|
end
|
2014-09-26 08:55:10 +03:00
|
|
|
if type(callback) == 'string' then
|
|
|
|
-- Allow supplying callback function name like Urho3D does by default
|
|
|
|
local caller_environment = getfenv(2)
|
|
|
|
callback = caller_environment[callback]
|
|
|
|
if type(callback) ~= 'function' then
|
|
|
|
error("SubscribeToEvent(): '"..callback..
|
|
|
|
"' is not a global function in current sandbox environment")
|
|
|
|
end
|
|
|
|
else
|
|
|
|
-- Allow directly supplying callback function
|
2014-09-22 21:42:00 +03:00
|
|
|
end
|
2014-09-26 08:55:10 +03:00
|
|
|
local global_function_i = next_sandbox_global_function_i
|
|
|
|
next_sandbox_global_function_i = next_sandbox_global_function_i + 1
|
|
|
|
local global_callback = "__buildat_sandbox_callback_"..global_function_i
|
|
|
|
sandbox_callback_to_global_function_name[callback] = global_callback
|
|
|
|
_G[global_callback] = function(eventType, eventData)
|
2014-09-22 21:42:00 +03:00
|
|
|
local f = function()
|
2014-09-26 11:47:16 +03:00
|
|
|
if object then
|
|
|
|
callback(object, eventType, eventData)
|
|
|
|
else
|
|
|
|
callback(eventType, eventData)
|
|
|
|
end
|
2014-09-22 21:42:00 +03:00
|
|
|
end
|
|
|
|
__buildat_run_function_in_sandbox(f)
|
|
|
|
end
|
2014-09-26 11:47:16 +03:00
|
|
|
if object then
|
|
|
|
SubscribeToEvent(object, event_name, global_callback)
|
|
|
|
else
|
|
|
|
SubscribeToEvent(event_name, global_callback)
|
|
|
|
end
|
2014-09-26 08:55:10 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
--
|
|
|
|
-- Unsafe interface
|
|
|
|
--
|
|
|
|
|
|
|
|
-- Just wrap everything to the global environment as we don't have a full list
|
|
|
|
-- of Urho3D's API available.
|
|
|
|
|
|
|
|
setmetatable(M, {
|
|
|
|
__index = function(t, k)
|
|
|
|
local v = rawget(t, k)
|
|
|
|
if v ~= nil then return v end
|
|
|
|
return _G[k]
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
|
|
|
|
-- Add GetResource wrapper to ResourceCache instance
|
|
|
|
|
|
|
|
--[[M.cache.GetResource = function(self, resource_type, resource_name)
|
|
|
|
local path = M.resave_file(resource_name)
|
|
|
|
-- Note: path is unused
|
|
|
|
resource_name = M.check_safe_resource_name(resource_name)
|
|
|
|
return M.cache:GetResource(resource_type, resource_name)
|
|
|
|
end]]
|
|
|
|
|
|
|
|
-- Unsafe SubscribeToEvent with function support
|
|
|
|
|
|
|
|
local unsafe_callback_to_global_function_name = {}
|
|
|
|
local next_unsafe_global_function_i = 1
|
|
|
|
|
2014-09-26 11:47:16 +03:00
|
|
|
function M.SubscribeToEvent(x, y, z)
|
|
|
|
local object = x
|
|
|
|
local event_name = y
|
|
|
|
local callback = z
|
|
|
|
if callback == nil then
|
|
|
|
object = nil
|
|
|
|
event_name = x
|
|
|
|
callback = y
|
|
|
|
end
|
2014-09-26 08:55:10 +03:00
|
|
|
if type(callback) == 'string' then
|
|
|
|
-- Allow supplying callback function name like Urho3D does by default
|
|
|
|
local caller_environment = getfenv(2)
|
|
|
|
callback = caller_environment[callback]
|
|
|
|
if type(callback) ~= 'function' then
|
|
|
|
error("SubscribeToEvent(): '"..callback..
|
|
|
|
"' is not a global function in current unsafe environment")
|
|
|
|
end
|
|
|
|
else
|
|
|
|
-- Allow directly supplying callback function
|
|
|
|
end
|
|
|
|
local global_function_i = next_unsafe_global_function_i
|
|
|
|
next_unsafe_global_function_i = next_unsafe_global_function_i + 1
|
|
|
|
local global_callback = "__buildat_unsafe_callback_"..global_function_i
|
|
|
|
unsafe_callback_to_global_function_name[callback] = global_callback
|
|
|
|
_G[global_callback] = function(eventType, eventData)
|
2014-09-26 11:47:16 +03:00
|
|
|
if object then
|
|
|
|
callback(object, eventType, eventData)
|
|
|
|
else
|
|
|
|
callback(eventType, eventData)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if object then
|
|
|
|
SubscribeToEvent(object, event_name, global_callback)
|
|
|
|
else
|
|
|
|
SubscribeToEvent(event_name, global_callback)
|
2014-09-26 08:55:10 +03:00
|
|
|
end
|
2014-09-22 21:42:00 +03:00
|
|
|
end
|
2014-09-23 18:06:30 +03:00
|
|
|
|
|
|
|
return M
|
2014-09-24 15:13:49 +03:00
|
|
|
-- vim: set noet ts=4 sw=4:
|