From b0040bba11ec03db76ec0c122ed79abcc8609230 Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Sat, 17 Sep 2016 14:23:04 +0100 Subject: [PATCH] Resolve symlinks passed to CreateDatabase() The API checks if the given game path and game local path are directories, which isn't true if they are symlinks to directories, so if they are symlinks, resolve them before performing the directory check. Note that creating symlinks in Windows requires the SeCreateSymbolicLinkPrivilege privilege, which only admins have by default, so the test will fail unless run as admin or the privilege is given to the user running the test. Closes #645. --- src/api/api.cpp | 21 ++++++++++++++++----- src/tests/api/create_database_test.h | 19 ++++++++++++++++++- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/api/api.cpp b/src/api/api.cpp index 433f3e10..fd584fd5 100644 --- a/src/api/api.cpp +++ b/src/api/api.cpp @@ -31,7 +31,16 @@ #include "loot/error.h" #include "backend/app/loot_paths.h" +namespace fs = boost::filesystem; + namespace loot { +std::string ResolvePath(const std::string& path) { + if (path.empty() || !fs::is_symlink(path)) + return path; + + return fs::read_symlink(path).string(); +} + LOOT_API bool IsCompatible(const unsigned int versionMajor, const unsigned int versionMinor, const unsigned int versionPatch) { if (versionMajor > 0) return versionMajor == loot::LootVersion::major; @@ -48,12 +57,14 @@ LOOT_API std::shared_ptr CreateDatabase(const GameType game, boost::log::core::get()->set_logging_enabled(false); // Check for valid paths. - if (!gamePath.empty() && !boost::filesystem::is_directory(gamePath)) - throw loot::Error(Error::Code::invalid_args, "Given game path \"" + std::string(gamePath) + "\" is not a valid directory."); + const std::string resolvedGamePath = ResolvePath(gamePath); + if (!gamePath.empty() && !fs::is_directory(resolvedGamePath)) + throw Error(Error::Code::invalid_args, "Given game path \"" + gamePath + "\" does not resolve to a valid directory."); - if (!gameLocalPath.empty() && !boost::filesystem::is_directory(gameLocalPath)) - throw loot::Error(Error::Code::invalid_args, "Given local data path \"" + std::string(gameLocalPath) + "\" is not a valid directory."); + const std::string resolvedGameLocalPath = ResolvePath(gameLocalPath); + if (!gameLocalPath.empty() && !fs::is_directory(resolvedGameLocalPath)) + throw Error(Error::Code::invalid_args, "Given local data path \"" + gameLocalPath + "\" does not resolve to a valid directory."); - return std::make_shared(game, gamePath, gameLocalPath); + return std::make_shared(game, resolvedGamePath, resolvedGameLocalPath); } } diff --git a/src/tests/api/create_database_test.h b/src/tests/api/create_database_test.h index b1a3fdf9..f1f996ae 100644 --- a/src/tests/api/create_database_test.h +++ b/src/tests/api/create_database_test.h @@ -35,17 +35,29 @@ namespace loot { namespace test { class CreateDatabaseTest : public CommonGameTestFixture { protected: - CreateDatabaseTest() : db_(nullptr) {} + CreateDatabaseTest() : + db_(nullptr), + gamePathSymlink(dataPath.parent_path().string() + "symlink"), + localPathSymlink(localPath.string() + "symlink") {} void SetUp() { CommonGameTestFixture::SetUp(); + + boost::filesystem::create_directory_symlink(dataPath.parent_path(), gamePathSymlink); + boost::filesystem::create_directory_symlink(localPath, localPathSymlink); } void TearDown() { CommonGameTestFixture::TearDown(); + + boost::filesystem::remove(gamePathSymlink); + boost::filesystem::remove(localPathSymlink); } std::shared_ptr db_; + + const boost::filesystem::path gamePathSymlink; + const boost::filesystem::path localPathSymlink; }; // Pass an empty first argument, as it's a prefix for the test instantation, @@ -86,6 +98,11 @@ TEST_P(CreateDatabaseTest, shouldReturnOkIfPassedAnEmptyLocalPathString) { EXPECT_NE(nullptr, db_); } #endif + +TEST_P(CreateDatabaseTest, shouldReturnOkIfPassedGameAndLocalPathSymlinks) { + EXPECT_NO_THROW(db_ = CreateDatabase(GetParam(), gamePathSymlink.string(), localPathSymlink.string())); + EXPECT_NE(nullptr, db_); +} } }