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.
This commit is contained in:
Oliver Hamlet
2016-09-17 22:48:54 +01:00
parent 19778db975
commit b0040bba11
2 changed files with 34 additions and 6 deletions
+16 -5
View File
@@ -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<DatabaseInterface> 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<ApiDatabase>(game, gamePath, gameLocalPath);
return std::make_shared<ApiDatabase>(game, resolvedGamePath, resolvedGameLocalPath);
}
}
+18 -1
View File
@@ -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<DatabaseInterface> 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_);
}
}
}