Core/VirtualDirectory: Add Uproot property

This commit is contained in:
Lynix 2022-05-14 10:22:22 +02:00
parent b6ab3ba1b3
commit dd4be081aa
3 changed files with 104 additions and 63 deletions

View File

@ -39,13 +39,17 @@ namespace Nz
VirtualDirectory(VirtualDirectory&&) = delete;
~VirtualDirectory() = default;
bool Exists(std::string_view path);
inline void AllowUproot(bool uproot = true);
inline bool Exists(std::string_view path);
template<typename F> void Foreach(F&& callback, bool includeDots = false);
template<typename F> bool GetEntry(std::string_view path, F&& callback);
template<typename F> bool GetFileContent(std::string_view path, F&& callback);
inline bool IsUprootAllowed() const;
inline DirectoryEntry& StoreDirectory(std::string_view path, VirtualDirectoryPtr directory);
inline PhysicalDirectoryEntry& StoreDirectory(std::string_view path, std::filesystem::path directoryPath);
inline FileContentEntry& StoreFile(std::string_view path, std::vector<UInt8> file);
@ -99,6 +103,7 @@ namespace Nz
std::optional<std::filesystem::path> m_physicalPath;
std::vector<ContentEntry> m_content;
std::weak_ptr<VirtualDirectory> m_parent;
bool m_isUprootAllowed;
};
}

View File

@ -12,16 +12,23 @@
namespace Nz
{
inline VirtualDirectory::VirtualDirectory(std::weak_ptr<VirtualDirectory> parentDirectory) :
m_parent(std::move(parentDirectory))
m_parent(std::move(parentDirectory)),
m_isUprootAllowed(false)
{
}
inline VirtualDirectory::VirtualDirectory(std::filesystem::path physicalPath, std::weak_ptr<VirtualDirectory> parentDirectory) :
m_physicalPath(std::move(physicalPath)),
m_parent(std::move(parentDirectory))
m_parent(std::move(parentDirectory)),
m_isUprootAllowed(false)
{
}
inline void VirtualDirectory::AllowUproot(bool uproot)
{
m_isUprootAllowed = uproot;
}
inline bool VirtualDirectory::Exists(std::string_view path)
{
return GetEntry(path, [](const auto&) {});
@ -94,7 +101,7 @@ namespace Nz
if (physicalPathBase)
{
// Special case when traversing directory
if (dirName == "..")
if (dirName == ".." && !m_isUprootAllowed)
{
// Don't allow to escape virtual directory
if (!physicalDirectoryParts.empty())
@ -193,6 +200,11 @@ namespace Nz
});
}
inline bool VirtualDirectory::IsUprootAllowed() const
{
return m_isUprootAllowed;
}
inline auto VirtualDirectory::StoreDirectory(std::string_view path, VirtualDirectoryPtr directory) -> DirectoryEntry&
{
assert(!path.empty());
@ -260,46 +272,49 @@ namespace Nz
Entry entry{ DirectoryEntry{ shared_from_this() } };
return CallbackReturn(callback, entry);
}
else if (name == "..")
if (name == "..")
{
Entry entry;
VirtualDirectoryPtr parentEntry;
if (VirtualDirectoryPtr parent = m_parent.lock())
entry = DirectoryEntry{ std::move(parent) };
else
entry = DirectoryEntry{ shared_from_this() };
parentEntry = std::move(parent);
else if (!m_isUprootAllowed)
parentEntry = shared_from_this();
return CallbackReturn(callback, entry);
if (parentEntry)
{
Entry entry = DirectoryEntry{ std::move(parentEntry) };
return CallbackReturn(callback, entry);
}
}
else
auto it = std::lower_bound(m_content.begin(), m_content.end(), name, [](const ContentEntry& entry, std::string_view name)
{
auto it = std::lower_bound(m_content.begin(), m_content.end(), name, [](const ContentEntry& entry, std::string_view name)
return entry.name < name;
});
if (it == m_content.end() || it->name != name)
{
// Virtual file not found, check if it has a physical one
if (m_physicalPath)
{
return entry.name < name;
});
if (it == m_content.end() || it->name != name)
{
// Virtual file not found, check if it has a physical one
if (m_physicalPath)
{
std::filesystem::path filePath = *m_physicalPath / name;
std::filesystem::file_status status = std::filesystem::status(filePath); //< FIXME: This will follow symlink, is this the intended behavior? (see symlink_status)
std::filesystem::path filePath = *m_physicalPath / name;
std::filesystem::file_status status = std::filesystem::status(filePath); //< FIXME: This will follow symlink, is this the intended behavior? (see symlink_status)
Entry entry;
if (std::filesystem::is_regular_file(status))
entry = PhysicalFileEntry{ std::move(filePath) };
else if (std::filesystem::is_directory(status))
entry = PhysicalDirectoryEntry{ std::move(filePath) };
else
return false; //< either not known or of a special type
Entry entry;
if (std::filesystem::is_regular_file(status))
entry = PhysicalFileEntry{ std::move(filePath) };
else if (std::filesystem::is_directory(status))
entry = PhysicalDirectoryEntry{ std::move(filePath) };
else
return false; //< either not known or of a special type
return CallbackReturn(callback, entry);
}
return false;
return CallbackReturn(callback, entry);
}
return CallbackReturn(callback, it->entry);
return false;
}
return CallbackReturn(callback, it->entry);
}
inline bool VirtualDirectory::CreateOrRetrieveDirectory(std::string_view path, std::shared_ptr<VirtualDirectory>& directory, std::string_view& entryName)

View File

@ -187,12 +187,12 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]")
SECTION("Accessing filesystem using a VirtualDirectory")
{
std::shared_ptr<Nz::VirtualDirectory> virtualDir = std::make_shared<Nz::VirtualDirectory>(GetResourceDir());
std::shared_ptr<Nz::VirtualDirectory> resourceDir = std::make_shared<Nz::VirtualDirectory>(GetResourceDir());
WHEN("Iterating, it's not empty")
{
bool empty = true;
virtualDir->Foreach([&](std::string_view name, const Nz::VirtualDirectory::Entry& entry)
resourceDir->Foreach([&](std::string_view name, const Nz::VirtualDirectory::Entry& entry)
{
CHECK_FALSE(name == ".");
CHECK_FALSE(name == "..");
@ -205,9 +205,9 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]")
REQUIRE_FALSE(empty);
}
auto CheckFileHash = [&](const char* filepath, const char* expectedHash)
auto CheckFileHash = [](const Nz::VirtualDirectoryPtr& dir, const char* filepath, const char* expectedHash)
{
return virtualDir->GetEntry(filepath, [&](const Nz::VirtualDirectory::Entry& entry)
return dir->GetEntry(filepath, [&](const Nz::VirtualDirectory::Entry& entry)
{
REQUIRE(std::holds_alternative<Nz::VirtualDirectory::PhysicalFileEntry>(entry));
@ -224,9 +224,9 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]")
});
};
auto CheckFileContentHash = [&](const char* filepath, const char* expectedHash)
auto CheckFileContentHash = [](const Nz::VirtualDirectoryPtr& dir, const char* filepath, const char* expectedHash)
{
return virtualDir->GetFileContent(filepath, [&](const void* data, std::size_t size)
return dir->GetFileContent(filepath, [&](const void* data, std::size_t size)
{
Nz::SHA256Hash hash;
WHEN("We compute " << hash.GetHashName() << " of " << filepath << " file")
@ -240,42 +240,63 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]")
WHEN("Accessing files")
{
CHECK(CheckFileHash("Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423"));
CHECK(CheckFileHash("./Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423"));
CHECK(CheckFileHash("Engine/Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK(CheckFileHash("Engine/Audio/./The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK_FALSE(CheckFileHash("The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK_FALSE(CheckFileHash("Engine/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK_FALSE(CheckFileHash("Engine/Audio/../The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK(CheckFileHash(resourceDir, "Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423"));
CHECK(CheckFileHash(resourceDir, "./Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423"));
CHECK(CheckFileHash(resourceDir, "Engine/Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK(CheckFileHash(resourceDir, "Engine/Audio/./The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK_FALSE(CheckFileHash(resourceDir, "The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK_FALSE(CheckFileHash(resourceDir, "Engine/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK_FALSE(CheckFileHash(resourceDir, "Engine/Audio/../The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
// We can't escape the virtual directory
CHECK(CheckFileHash("../Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423"));
CHECK(CheckFileHash("../../Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423"));
CHECK(CheckFileHash("Engine/../Engine/Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK(CheckFileHash("../Engine/./Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK_FALSE(CheckFileHash("../Engine/Engine/Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK(CheckFileHash(resourceDir, "../Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423"));
CHECK(CheckFileHash(resourceDir, "../../Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423"));
CHECK(CheckFileHash(resourceDir, "Engine/../Engine/Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK(CheckFileHash(resourceDir, "../Engine/./Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK_FALSE(CheckFileHash(resourceDir, "../Engine/Engine/Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
auto CheckOurselves = [&](const auto& entry)
{
REQUIRE(std::holds_alternative<Nz::VirtualDirectory::DirectoryEntry>(entry));
const auto& dirEntry = std::get<Nz::VirtualDirectory::DirectoryEntry>(entry);
return dirEntry.directory == virtualDir;
return dirEntry.directory == resourceDir;
};
CHECK(virtualDir->GetEntry("..", CheckOurselves));
CHECK(virtualDir->GetEntry("../..", CheckOurselves));
CHECK(virtualDir->GetEntry("./..", CheckOurselves));
CHECK(virtualDir->GetEntry("./..", CheckOurselves));
CHECK(virtualDir->GetEntry("Engine/../..", CheckOurselves));
CHECK(virtualDir->GetEntry("Engine/../Engine/Audio/../../..", CheckOurselves));
CHECK(resourceDir->GetEntry("..", CheckOurselves));
CHECK(resourceDir->GetEntry("../..", CheckOurselves));
CHECK(resourceDir->GetEntry("./..", CheckOurselves));
CHECK(resourceDir->GetEntry("./..", CheckOurselves));
CHECK(resourceDir->GetEntry("Engine/../..", CheckOurselves));
CHECK(resourceDir->GetEntry("Engine/../Engine/Audio/../../..", CheckOurselves));
}
AND_THEN("Overriding the physical file with another one")
{
virtualDir->StoreFile("Logo.png", GetResourceDir() / "ambience.ogg");
CHECK(CheckFileHash("ambience.ogg", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94"));
CHECK(CheckFileHash("Logo.png", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94"));
CHECK(CheckFileContentHash("ambience.ogg", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94"));
CHECK(CheckFileContentHash("Logo.png", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94"));
resourceDir->StoreFile("Logo.png", GetResourceDir() / "ambience.ogg");
CHECK(CheckFileHash(resourceDir, "ambience.ogg", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94"));
CHECK(CheckFileHash(resourceDir, "Logo.png", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94"));
CHECK(CheckFileContentHash(resourceDir, "ambience.ogg", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94"));
CHECK(CheckFileContentHash(resourceDir, "Logo.png", "49C486F44E43F023D54C9F375D902C21375DDB2748D3FA1863C9581D30E17F94"));
}
WHEN("Testing uproot escape")
{
std::shared_ptr<Nz::VirtualDirectory> engineDir = std::make_shared<Nz::VirtualDirectory>(GetResourceDir() / "Engine");
CHECK_FALSE(engineDir->IsUprootAllowed());
// We can't escape the virtual directory
CHECK_FALSE(engineDir->Exists("../Logo.png"));
CHECK_FALSE(engineDir->Exists("../../Logo.png"));
CHECK_FALSE(engineDir->Exists("../Engine/Audio/Audio/The_Brabanconne.ogg"));
CHECK(CheckFileHash(engineDir, "Audio/../../Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
CHECK(CheckFileHash(engineDir, "../Audio/./The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
engineDir->AllowUproot(true);
CHECK(engineDir->IsUprootAllowed());
// Now we're able to access the resource folder beneath
CHECK(CheckFileHash(engineDir, "../Logo.png", "5C4B9387327C039A6CE9ED51983D6C2ADA9F9DD01D024C2D5D588237ADFC7423"));
CHECK(CheckFileHash(engineDir, "../Engine/Audio/The_Brabanconne.ogg", "E07706E0BEEC7770CDE36008826743AF9EEE5C80CA0BD83C37771CBC8B52E738"));
}
}
}