Skip to content

Commit

Permalink
Change the FileSystem:Open (and io.open) commands to require a URI sc…
Browse files Browse the repository at this point in the history
…heme path rather than having an explicit root argument.

Valid schemes are "user://" and "data://"

Also implement all of FileSystem in c++.
  • Loading branch information
JonBooth78 committed Oct 9, 2023
1 parent 22f070b commit 027a952
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 102 deletions.
27 changes: 0 additions & 27 deletions data/libs/FileSystem.lua

This file was deleted.

30 changes: 24 additions & 6 deletions data/meta/FileSystemBase.lua → data/meta/FileSystem.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,44 @@

---@meta

---@class FileSystemBase
local FileSystemBase = {}
---@class FileSystem
local FileSystem = {}

---@param root string A FileSystemRoot constant. Can be either "DATA" or "USER"
---@return string[] files A list of files as full paths from the root
---@return string[] dirs A list of dirs as full paths from the root
---
--- Example:
--- > local files, dirs = FileSystem.ReadDirectory(root, path)
function FileSystemBase.ReadDirectory(root, path) end
function FileSystem.ReadDirectory(root, path) end

--- Join the passed arguments into a path, correctly handling separators and .
--- and .. special dirs.
---
---@param arg string[] A list of path elements to be joined
---@return string The joined path elements
function FileSystemBase.JoinPath( ... ) end
function FileSystem.JoinPath( ... ) end

---@param dir_name string The name of the folder to create in the user directory
---@return boolean Success
function FileSystemBase.MakeUserDataDirectory( dir_name ) end
function FileSystem.MakeUserDataDirectory( dir_name ) end

return FileSystemBase
--- Wrapper for our patched io.open that ensures files are opened inside the sandbox.
--- Prefer using this to io.open
---
--- Files in the user folder can be read or written to
--- Files in the data folder are read only.
---
---
---
--- Example:
--- > f = FileSystem.Open( "user://my_file.txt", "w" )
--- > f:write( "file contents" )
--- > f:close()
---
---@param filename string The name of the file to open, must start either user:// or data://
---@param mode string|nil The mode to open the file in, defaults to read only. Only user location files can be written
---@return file A lua io file
function FileSystem.Open( dir_name ) end

return FileSystem
135 changes: 100 additions & 35 deletions src/lua/LuaFileSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,105 @@
#include "LuaObject.h"
#include "Pi.h"

#include "core/StringUtils.h"

class ParsedFilePath
{
public:
ParsedFilePath(lua_State* l, const char* luaPath);

// Probably not needed as if it isn't valid we'll already fail.
bool IsValid() const { return m_fs != nullptr; }

bool ValidateWritePermission(lua_State* l) const;
FileSystem::FileSource& GetFileSource() const { return *m_fs; }
const std::string_view& GetRelativePath() const { return m_fs_path; }

std::string CalculateAbsolutePath(lua_State* l) const;
private:
std::string_view m_luaPath;
std::string_view m_scheme;
std::string_view m_fs_path;
FileSystem::FileSource* m_fs;
};

ParsedFilePath::ParsedFilePath(lua_State* l, const char* luaPath)
: m_luaPath(luaPath)
{
if (starts_with_ci(m_luaPath, "user://"))
{
m_fs = &FileSystem::userFiles;
m_scheme = m_luaPath.substr(0, 7);// strlen("user://"));
}
else if (starts_with_ci(m_luaPath, "data://"))
{
m_fs = &FileSystem::gameDataFiles;
m_scheme = m_luaPath.substr(0, 7);// strlen("data://"));
}
else
{
luaL_error(l, "'%s' does not have a valid scheme, must be user:// or data://", luaPath);
m_fs = nullptr;
return;
}
m_fs_path = m_luaPath;
m_fs_path.remove_prefix(m_scheme.length());
}

bool ParsedFilePath::ValidateWritePermission(lua_State* l) const
{
if (m_fs == &FileSystem::userFiles)
{
return true;
}
luaL_error(l, "'%s' does not support file operations that modify it, only things in the user folder do", m_luaPath.data());
return false;
}

std::string ParsedFilePath::CalculateAbsolutePath(lua_State* l) const
{
try
{
return std::move(m_fs->Lookup(std::string(m_fs_path)).GetAbsolutePath());
}
catch (std::invalid_argument e)
{
luaL_error(l, "'%s' is not a valid file in its scheme root' - Is the file location within the root?", m_luaPath.data());
return "";
}
}

static lua_CFunction l_original_io_open = nullptr;

void LuaFileSystem::register_raw_io_open_function(lua_CFunction open)
{
l_original_io_open = open;
}

int LuaFileSystem::l_patched_io_open(lua_State* L)
{
ParsedFilePath path = ParsedFilePath(L, lua_tostring(L, 1));
const char* root_cstr = lua_tostring(L, 2);
for (const char* c = root_cstr; *c != 0; ++c )
{
if (*c != 'r')
{
if (!path.ValidateWritePermission(L))
{
return 0;
}
break;
}
}

::std::string abs_path = path.CalculateAbsolutePath(L);
lua_pushlstring(L, abs_path.c_str(), abs_path.length() );
lua_replace(L, 1);

const int rv = l_original_io_open(L);

return rv;
}
/*
* Interface: FileSystem
*
Expand Down Expand Up @@ -35,41 +134,6 @@ FileSystem::FileSource* get_filesytem_for_root(LuaFileSystem::Root root)
return fs;
}

std::string LuaFileSystem::lua_path_to_fs_path(lua_State* l, const char* root_name, const char* path, const char* access)
{
const LuaFileSystem::Root root = static_cast<LuaFileSystem::Root>(LuaConstants::GetConstant(l, "FileSystemRoot", root_name));

if (root == LuaFileSystem::ROOT_DATA)
{
// check the acces mode is allowed:

// default mode is read only
if (access[0] != 0)
{
if (access[0] != 'r' || access[1] != 0)
{
// we are requesting an access mode not allowed for the user folder
// as you can't write there.
luaL_error(l, "'%s' is not valid for opening a file in root '%s'", access, root_name);
return "";
}
}
}

FileSystem::FileSource* fs = get_filesytem_for_root(root);
assert(fs);

try
{
return std::move(fs->Lookup(path).GetAbsolutePath());
}
catch (std::invalid_argument e)
{
luaL_error(l, "'%s' is not valid for opening a file in root '%s' - Is the file location within the root?", path, root_name);
return "";
}
}

static void push_date_time(lua_State *l, const Time::DateTime &dt)
{
int year, month, day, hour, minute, second;
Expand Down Expand Up @@ -237,6 +301,7 @@ void LuaFileSystem::Register()
{ "ReadDirectory", l_filesystem_read_dir },
{ "JoinPath", l_filesystem_join_path },
{ "MakeUserDataDirectory", l_filesystem_make_user_directory },
{ "Open", l_patched_io_open },
{ 0, 0 }
};

Expand Down
6 changes: 3 additions & 3 deletions src/lua/LuaFileSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <string>

struct lua_State;
typedef int (*lua_CFunction) (lua_State* L);

namespace LuaFileSystem {
void Register();
Expand All @@ -16,9 +17,8 @@ namespace LuaFileSystem {
ROOT_DATA
};

// will throw lua errors if not allowed
// and return an empty string
std::string lua_path_to_fs_path(lua_State* l, const char* root, const char* path, const char* access);
void register_raw_io_open_function(lua_CFunction open);
int l_patched_io_open(lua_State* L);
} // namespace LuaFileSystem

#endif
33 changes: 2 additions & 31 deletions src/lua/core/Sandbox.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,35 +106,6 @@ static int l_log_warning(lua_State *L)
return 0;
}

static lua_CFunction l_original_io_open = nullptr;

static int l_patched_io_open(lua_State* L)
{
if (lua_gettop(L) != 3)
{
luaL_error(L, "Wrong number of arguments for io.open(). You should be using FileSystem.Open() instead");
return 0;
}
const char* path_arg = lua_tostring(L, 1);
const char* access_arg = lua_tostring(L, 2);
const char* root_arg = lua_tostring(L, 3);

std::string path = LuaFileSystem::lua_path_to_fs_path(L, root_arg, path_arg, access_arg);

if (path.length() == 0)
{
luaL_error(L, "attempt to access filesystem in an invalid user folder location");
return 0;
}

lua_pushstring(L, path.c_str());
lua_replace(L, 1);

const int rv = l_original_io_open(L);

return rv;
}

static const luaL_Reg STANDARD_LIBS[] = {
{ "_G", luaopen_base },
{ LUA_COLIBNAME, luaopen_coroutine },
Expand Down Expand Up @@ -202,13 +173,13 @@ void pi_lua_open_standard_base(lua_State *L)

lua_getfield(L, -1, "open");
assert(lua_iscfunction(L, -1));
l_original_io_open = lua_tocfunction(L, -1);
LuaFileSystem::register_raw_io_open_function(lua_tocfunction(L, -1));

lua_pop(L, 1); // pop the io table
lua_getglobal(L, LUA_IOLIBNAME);

// patch io.open so we can check the path
lua_pushcfunction(L, l_patched_io_open);
lua_pushcfunction(L, LuaFileSystem::l_patched_io_open);
lua_setfield(L, -2, "open");

// remove io.popen as we don't want people running apps
Expand Down

0 comments on commit 027a952

Please sign in to comment.