From 9a12e93f5aaced4ea8c362ec6adfa2bffd911dac Mon Sep 17 00:00:00 2001 From: reuk Date: Wed, 11 May 2022 20:05:14 +0100 Subject: [PATCH] File: Add hasReadAccess() --- modules/juce_core/files/juce_File.cpp | 7 ++ modules/juce_core/files/juce_File.h | 6 + .../native/juce_BasicNativeHeaders.h | 2 + .../juce_core/native/juce_posix_SharedCode.h | 6 + modules/juce_core/native/juce_win32_Files.cpp | 103 ++++++++++++++++-- 5 files changed, 115 insertions(+), 9 deletions(-) diff --git a/modules/juce_core/files/juce_File.cpp b/modules/juce_core/files/juce_File.cpp index 1ce3c88353..20561f3959 100644 --- a/modules/juce_core/files/juce_File.cpp +++ b/modules/juce_core/files/juce_File.cpp @@ -1146,11 +1146,18 @@ public: expect (home.getChildFile ("./../xyz") == home.getParentDirectory().getChildFile ("xyz")); expect (home.getChildFile ("a1/a2/a3/./../../a4") == home.getChildFile ("a1/a4")); + expect (! File().hasReadAccess()); + expect (! File().hasWriteAccess()); + + expect (! tempFile.hasReadAccess()); + { FileOutputStream fo (tempFile); fo.write ("0123456789", 10); } + expect (tempFile.hasReadAccess()); + expect (tempFile.exists()); expect (tempFile.getSize() == 10); expect (std::abs ((int) (tempFile.getLastModificationTime().toMilliseconds() - Time::getCurrentTime().toMilliseconds())) < 3000); diff --git a/modules/juce_core/files/juce_File.h b/modules/juce_core/files/juce_File.h index baa816c8be..f81ec27f87 100644 --- a/modules/juce_core/files/juce_File.h +++ b/modules/juce_core/files/juce_File.h @@ -342,6 +342,12 @@ public: */ bool hasWriteAccess() const; + /** Checks whether a file can be read. + + @returns true if it's possible to read this file. + */ + bool hasReadAccess() const; + /** Changes the write-permission of a file or directory. @param shouldBeReadOnly whether to add or remove write-permission diff --git a/modules/juce_core/native/juce_BasicNativeHeaders.h b/modules/juce_core/native/juce_BasicNativeHeaders.h index cc6eb47372..c45ac6e3b3 100644 --- a/modules/juce_core/native/juce_BasicNativeHeaders.h +++ b/modules/juce_core/native/juce_BasicNativeHeaders.h @@ -154,6 +154,8 @@ #include #include #include + #include + #include #if ! JUCE_CXX17_IS_AVAILABLE #pragma push_macro ("WIN_NOEXCEPT") diff --git a/modules/juce_core/native/juce_posix_SharedCode.h b/modules/juce_core/native/juce_posix_SharedCode.h index 661f90d603..700e191e9e 100644 --- a/modules/juce_core/native/juce_posix_SharedCode.h +++ b/modules/juce_core/native/juce_posix_SharedCode.h @@ -288,6 +288,12 @@ bool File::hasWriteAccess() const return false; } +bool File::hasReadAccess() const +{ + return fullPath.isNotEmpty() + && access (fullPath.toUTF8(), R_OK) == 0; +} + static bool setFileModeFlags (const String& fullPath, mode_t flags, bool shouldSet) noexcept { juce_statStruct info; diff --git a/modules/juce_core/native/juce_win32_Files.cpp b/modules/juce_core/native/juce_win32_Files.cpp index e09585a4b4..7d93f26afb 100644 --- a/modules/juce_core/native/juce_win32_Files.cpp +++ b/modules/juce_core/native/juce_win32_Files.cpp @@ -159,6 +159,83 @@ namespace WindowsFileHelpers return Result::fail (String (messageBuffer)); } + + // The docs for the Windows security API aren't very clear. Some parts of the following + // function (the flags passed to GetNamedSecurityInfo, duplicating the primary access token) + // were guided by the example at https://blog.aaronballman.com/2011/08/how-to-check-access-rights/ + static bool hasFileAccess (const File& file, DWORD accessType) + { + const auto& path = file.getFullPathName(); + + if (path.isEmpty()) + return false; + + struct PsecurityDescriptorGuard + { + ~PsecurityDescriptorGuard() { if (psecurityDescriptor != nullptr) LocalFree (psecurityDescriptor); } + PSECURITY_DESCRIPTOR psecurityDescriptor = nullptr; + }; + + PsecurityDescriptorGuard descriptorGuard; + + if (GetNamedSecurityInfo (path.toWideCharPointer(), + SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + nullptr, + nullptr, + nullptr, + nullptr, + &descriptorGuard.psecurityDescriptor) != ERROR_SUCCESS) + { + return false; + } + + struct HandleGuard + { + ~HandleGuard() { if (handle != INVALID_HANDLE_VALUE) CloseHandle (handle); } + HANDLE handle = nullptr; + }; + + HandleGuard primaryTokenGuard; + + if (! OpenProcessToken (GetCurrentProcess(), + TOKEN_IMPERSONATE | TOKEN_DUPLICATE | TOKEN_QUERY | STANDARD_RIGHTS_READ, + &primaryTokenGuard.handle)) + { + return false; + } + + HandleGuard duplicatedTokenGuard; + + if (! DuplicateToken (primaryTokenGuard.handle, + SecurityImpersonation, + &duplicatedTokenGuard.handle)) + { + return false; + } + + GENERIC_MAPPING mapping { FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS }; + + MapGenericMask (&accessType, &mapping); + DWORD allowed = 0; + BOOL granted = false; + PRIVILEGE_SET set; + DWORD setSize = sizeof (set); + + if (! AccessCheck (descriptorGuard.psecurityDescriptor, + duplicatedTokenGuard.handle, + accessType, + &mapping, + &set, + &setSize, + &allowed, + &granted)) + { + return false; + } + + return granted != FALSE; + } } // namespace WindowsFileHelpers //============================================================================== @@ -199,17 +276,25 @@ bool File::isDirectory() const bool File::hasWriteAccess() const { - if (fullPath.isEmpty()) - return true; + if (exists()) + { + const auto attr = WindowsFileHelpers::getAtts (fullPath); - auto attr = WindowsFileHelpers::getAtts (fullPath); + return WindowsFileHelpers::hasFileAccess (*this, GENERIC_WRITE) + && (attr == INVALID_FILE_ATTRIBUTES + || (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 + || (attr & FILE_ATTRIBUTE_READONLY) == 0); + } + + if ((! isDirectory()) && fullPath.containsChar (getSeparatorChar())) + return getParentDirectory().hasWriteAccess(); - // NB: According to MS, the FILE_ATTRIBUTE_READONLY attribute doesn't work for - // folders, and can be incorrectly set for some special folders, so we'll just say - // that folders are always writable. - return attr == INVALID_FILE_ATTRIBUTES - || (attr & FILE_ATTRIBUTE_DIRECTORY) != 0 - || (attr & FILE_ATTRIBUTE_READONLY) == 0; + return false; +} + +bool File::hasReadAccess() const +{ + return WindowsFileHelpers::hasFileAccess (*this, GENERIC_READ); } bool File::setFileReadOnlyInternal (bool shouldBeReadOnly) const