Browse Source

love.filesystem canonicalizes internal full paths where possible.

resolves #1693.
resolves #1100.
Sasha Szpakowski 1 year ago
parent
commit
e94b8c3d04

+ 15 - 0
src/modules/filesystem/Filesystem.cpp

@@ -38,6 +38,9 @@
 #include <unistd.h>
 #endif
 
+// C++17 std::filesystem
+#include <filesystem>
+
 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)

+ 6 - 0
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.
 	 **/

+ 20 - 11
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;
 
+	std::string canonarchive = canonicalizeRealPath(archive);
+
 	if (permissions == MOUNT_PERMISSIONS_READWRITE)
-		return PHYSFS_mountRW(archive, mountpoint, appendToPath) != 0;
+		return PHYSFS_mountRW(canonarchive.c_str(), mountpoint, appendToPath) != 0;
 
-	return PHYSFS_mount(archive, 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);

+ 8 - 0
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 },

+ 3 - 14
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.