From e94b8c3d04c80849a0d49f5d6e638613c337a2e5 Mon Sep 17 00:00:00 2001 From: Sasha Szpakowski Date: Sat, 3 Aug 2024 19:33:55 -0300 Subject: [PATCH] love.filesystem canonicalizes internal full paths where possible. resolves #1693. resolves #1100. --- src/modules/filesystem/Filesystem.cpp | 15 +++++++++ src/modules/filesystem/Filesystem.h | 6 ++++ src/modules/filesystem/physfs/Filesystem.cpp | 33 +++++++++++++------- src/modules/filesystem/wrap_Filesystem.cpp | 8 +++++ src/modules/love/arg.lua | 17 ++-------- 5 files changed, 53 insertions(+), 26 deletions(-) diff --git a/src/modules/filesystem/Filesystem.cpp b/src/modules/filesystem/Filesystem.cpp index 233e95a24..df9863f7f 100644 --- a/src/modules/filesystem/Filesystem.cpp +++ b/src/modules/filesystem/Filesystem.cpp @@ -38,6 +38,9 @@ #include #endif +// C++17 std::filesystem +#include + namespace love { namespace filesystem @@ -182,6 +185,18 @@ bool Filesystem::createRealDirectory(const std::string &path) return true; } +std::string Filesystem::canonicalizeRealPath(const std::string &p) const +{ + try + { + return std::filesystem::weakly_canonical(p).string(); + } + catch (std::exception &) + { + return p; + } +} + std::string Filesystem::getExecutablePath() const { #if defined(LOVE_MACOS) || defined(LOVE_IOS) diff --git a/src/modules/filesystem/Filesystem.h b/src/modules/filesystem/Filesystem.h index 91359635d..9c08e4f6d 100644 --- a/src/modules/filesystem/Filesystem.h +++ b/src/modules/filesystem/Filesystem.h @@ -303,6 +303,12 @@ public: **/ virtual bool createRealDirectory(const std::string &path); + /** + * Converts the given real path to its canonical version (e.g. resolving + * '..', '.', relative paths, etc). + **/ + virtual std::string canonicalizeRealPath(const std::string &path) const; + /** * Gets the full platform-dependent path to the executable. **/ diff --git a/src/modules/filesystem/physfs/Filesystem.cpp b/src/modules/filesystem/physfs/Filesystem.cpp index 99a1beb55..53e8872f9 100644 --- a/src/modules/filesystem/physfs/Filesystem.cpp +++ b/src/modules/filesystem/physfs/Filesystem.cpp @@ -172,9 +172,14 @@ bool Filesystem::setIdentity(const char *ident, bool appendToPath) continue; // If a file is still open, unmount will fail. - std::string fullPath = getFullCommonPath(p); - if (!fullPath.empty() && !PHYSFS_canUnmount(fullPath.c_str())) - return false; + std::string fullpath = getFullCommonPath(p); + + if (!fullpath.empty()) + { + std::string canonpath = canonicalizeRealPath(fullpath); + if (!PHYSFS_canUnmount(canonpath.c_str())) + return false; + } } bool oldMountedCommonPaths[COMMONPATH_MAX_ENUM] = {false}; @@ -237,7 +242,7 @@ bool Filesystem::setSource(const char *source) if (!gameSource.empty()) return false; - std::string new_search_path = source; + std::string new_search_path = canonicalizeRealPath(source); #ifdef LOVE_ANDROID if (!love::android::createStorageDirectories()) @@ -395,10 +400,12 @@ bool Filesystem::mountFullPath(const char *archive, const char *mountpoint, Moun if (!PHYSFS_isInit() || !archive) return false; - if (permissions == MOUNT_PERMISSIONS_READWRITE) - return PHYSFS_mountRW(archive, mountpoint, appendToPath) != 0; + std::string canonarchive = canonicalizeRealPath(archive); - return PHYSFS_mount(archive, mountpoint, appendToPath) != 0; + if (permissions == MOUNT_PERMISSIONS_READWRITE) + return PHYSFS_mountRW(canonarchive.c_str(), mountpoint, appendToPath) != 0; + + return PHYSFS_mount(canonarchive.c_str(), mountpoint, appendToPath) != 0; } bool Filesystem::mountCommonPathInternal(CommonPath path, const char *mountpoint, MountPermissions permissions, bool appendToPath, bool createDir) @@ -474,10 +481,12 @@ bool Filesystem::unmount(const char *archive) realPath += LOVE_PATH_SEPARATOR; realPath += archive; - if (PHYSFS_getMountPoint(realPath.c_str()) == nullptr) + std::string canonpath = canonicalizeRealPath(realPath.c_str()); + + if (PHYSFS_getMountPoint(canonpath.c_str()) == nullptr) return false; - return PHYSFS_unmount(realPath.c_str()) != 0; + return PHYSFS_unmount(canonpath.c_str()) != 0; } bool Filesystem::unmountFullPath(const char *fullpath) @@ -485,7 +494,9 @@ bool Filesystem::unmountFullPath(const char *fullpath) if (!PHYSFS_isInit() || !fullpath) return false; - return PHYSFS_unmount(fullpath) != 0; + std::string canonpath = canonicalizeRealPath(fullpath); + + return PHYSFS_unmount(canonpath.c_str()) != 0; } bool Filesystem::unmount(CommonPath path) @@ -725,8 +736,6 @@ std::string Filesystem::getSourceBaseDirectory() const if (source_len == 0) return ""; - // FIXME: This doesn't take into account parent and current directory - // symbols (i.e. '..' and '.') #ifdef LOVE_WINDOWS // In windows, delimiters can be either '/' or '\'. size_t base_end_pos = gameSource.find_last_of("/\\", source_len - 2); diff --git a/src/modules/filesystem/wrap_Filesystem.cpp b/src/modules/filesystem/wrap_Filesystem.cpp index 65ed25eec..10a607a13 100644 --- a/src/modules/filesystem/wrap_Filesystem.cpp +++ b/src/modules/filesystem/wrap_Filesystem.cpp @@ -498,6 +498,13 @@ int w_getRealDirectory(lua_State *L) return 1; } +int w_canonicalizeRealPath(lua_State *L) +{ + const char *path = luaL_checkstring(L, 1); + luax_pushstring(L, instance()->canonicalizeRealPath(path)); + return 1; +} + int w_getExecutablePath(lua_State *L) { luax_pushstring(L, instance()->getExecutablePath()); @@ -1033,6 +1040,7 @@ static const luaL_Reg functions[] = { "getSaveDirectory", w_getSaveDirectory }, { "getSourceBaseDirectory", w_getSourceBaseDirectory }, { "getRealDirectory", w_getRealDirectory }, + { "canonicalizeRealPath", w_canonicalizeRealPath }, { "getExecutablePath", w_getExecutablePath }, { "createDirectory", w_createDirectory }, { "remove", w_remove }, diff --git a/src/modules/love/arg.lua b/src/modules/love/arg.lua index 41d339de2..8257d03d5 100644 --- a/src/modules/love/arg.lua +++ b/src/modules/love/arg.lua @@ -66,20 +66,9 @@ end -- Converts any path into a full path. function love.path.getFull(p) - - if love.path.abs(p) then - return love.path.normalslashes(p) - end - - local cwd = love.filesystem.getWorkingDirectory() - cwd = love.path.normalslashes(cwd) - cwd = love.path.endslash(cwd) - - -- Construct a full path. - local full = cwd .. love.path.normalslashes(p) - - -- Remove trailing /., if applicable - return full:match("(.-)/%.$") or full + p = love.filesystem.canonicalizeRealPath(p) + p = love.path.normalslashes(p) + return p end -- Returns the leaf of a full path.