From dd4be081aa20ee68a72601f21a872d823f836157 Mon Sep 17 00:00:00 2001 From: Lynix Date: Sat, 14 May 2022 10:22:22 +0200 Subject: [PATCH] Core/VirtualDirectory: Add Uproot property --- include/Nazara/Core/VirtualDirectory.hpp | 7 +- include/Nazara/Core/VirtualDirectory.inl | 79 ++++++++++++--------- tests/Engine/Core/VirtualDirectoryTest.cpp | 81 ++++++++++++++-------- 3 files changed, 104 insertions(+), 63 deletions(-) diff --git a/include/Nazara/Core/VirtualDirectory.hpp b/include/Nazara/Core/VirtualDirectory.hpp index db6d15aa0..57d46f6f7 100644 --- a/include/Nazara/Core/VirtualDirectory.hpp +++ b/include/Nazara/Core/VirtualDirectory.hpp @@ -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 void Foreach(F&& callback, bool includeDots = false); template bool GetEntry(std::string_view path, F&& callback); template 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 file); @@ -99,6 +103,7 @@ namespace Nz std::optional m_physicalPath; std::vector m_content; std::weak_ptr m_parent; + bool m_isUprootAllowed; }; } diff --git a/include/Nazara/Core/VirtualDirectory.inl b/include/Nazara/Core/VirtualDirectory.inl index 4fbaa099e..a4bdaa6b2 100644 --- a/include/Nazara/Core/VirtualDirectory.inl +++ b/include/Nazara/Core/VirtualDirectory.inl @@ -12,16 +12,23 @@ namespace Nz { inline VirtualDirectory::VirtualDirectory(std::weak_ptr parentDirectory) : - m_parent(std::move(parentDirectory)) + m_parent(std::move(parentDirectory)), + m_isUprootAllowed(false) { } inline VirtualDirectory::VirtualDirectory(std::filesystem::path physicalPath, std::weak_ptr 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& directory, std::string_view& entryName) diff --git a/tests/Engine/Core/VirtualDirectoryTest.cpp b/tests/Engine/Core/VirtualDirectoryTest.cpp index e18a9f944..e9613ea95 100644 --- a/tests/Engine/Core/VirtualDirectoryTest.cpp +++ b/tests/Engine/Core/VirtualDirectoryTest.cpp @@ -187,12 +187,12 @@ TEST_CASE("VirtualDirectory", "[Core][VirtualDirectory]") SECTION("Accessing filesystem using a VirtualDirectory") { - std::shared_ptr virtualDir = std::make_shared(GetResourceDir()); + std::shared_ptr resourceDir = std::make_shared(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(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(entry)); const auto& dirEntry = std::get(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 engineDir = std::make_shared(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")); } } }