diff --git a/modules/juce_core/files/juce_DirectoryIterator.cpp b/modules/juce_core/files/juce_DirectoryIterator.cpp index 767a3ac480..ea72ed7e7b 100644 --- a/modules/juce_core/files/juce_DirectoryIterator.cpp +++ b/modules/juce_core/files/juce_DirectoryIterator.cpp @@ -75,13 +75,26 @@ bool DirectoryIterator::next (bool* isDirResult, bool* isHiddenResult, int64* fi if (! filename.containsOnly (".")) { + const auto fullPath = File::createFileWithoutCheckingPath (path + filename); bool matches = false; if (isDirectory) { - if (isRecursive && ((whatToLookFor & File::ignoreHiddenFiles) == 0 || ! isHidden)) - subIterator.reset (new DirectoryIterator (File::createFileWithoutCheckingPath (path + filename), - true, wildCard, whatToLookFor)); + const auto mayRecurseIntoPossibleHiddenDir = [this, &isHidden] + { + return (whatToLookFor & File::ignoreHiddenFiles) == 0 || ! isHidden; + }; + + const auto mayRecurseIntoPossibleSymlink = [this, &fullPath] + { + return followSymlinks == File::FollowSymlinks::yes + || ! fullPath.isSymbolicLink() + || (followSymlinks == File::FollowSymlinks::noCycles + && knownPaths->find (fullPath.getLinkedTarget()) == knownPaths->end()); + }; + + if (isRecursive && mayRecurseIntoPossibleHiddenDir() && mayRecurseIntoPossibleSymlink()) + subIterator.reset (new DirectoryIterator (fullPath, true, wildCard, whatToLookFor, followSymlinks, knownPaths)); matches = (whatToLookFor & File::findDirectories) != 0; } @@ -99,7 +112,7 @@ bool DirectoryIterator::next (bool* isDirResult, bool* isHiddenResult, int64* fi if (matches) { - currentFile = File::createFileWithoutCheckingPath (path + filename); + currentFile = fullPath; if (isHiddenResult != nullptr) *isHiddenResult = isHidden; if (isDirResult != nullptr) *isDirResult = isDirectory; diff --git a/modules/juce_core/files/juce_DirectoryIterator.h b/modules/juce_core/files/juce_DirectoryIterator.h index 8b99b3bfe2..27a7aea9a6 100644 --- a/modules/juce_core/files/juce_DirectoryIterator.h +++ b/modules/juce_core/files/juce_DirectoryIterator.h @@ -34,6 +34,10 @@ namespace juce A DirectoryIterator will search through a directory and its subdirectories using a wildcard filepattern match. + The iterator keeps track of directories that it has previously traversed, and will + skip any previously-seen directories in the case of cycles caused by symbolic links. + It is also possible to avoid following symbolic links altogether. + If you may be scanning a large number of files, it's usually smarter to use this class than File::findChildFiles() because it allows you to stop at any time, rather than having to wait for the entire scan to finish before getting the results. @@ -73,17 +77,10 @@ public: DirectoryIterator (const File& directory, bool recursive, const String& pattern = "*", - int type = File::findFiles) - : wildCards (parseWildcards (pattern)), - fileFinder (directory, (recursive || wildCards.size() > 1) ? "*" : pattern), - wildCard (pattern), - path (File::addTrailingSeparator (directory.getFullPathName())), - whatToLookFor (type), - isRecursive (recursive) + int type = File::findFiles, + File::FollowSymlinks follow = File::FollowSymlinks::yes) + : DirectoryIterator (directory, recursive, pattern, type, follow, nullptr) { - // you have to specify the type of files you're looking for! - jassert ((whatToLookFor & (File::findFiles | File::findDirectories)) != 0); - jassert (whatToLookFor > 0 && whatToLookFor <= 7); } /** Moves the iterator along to the next file. @@ -126,6 +123,39 @@ public: float getEstimatedProgress() const; private: + using KnownPaths = std::set; + + DirectoryIterator (const File& directory, + bool recursive, + const String& pattern, + int type, + File::FollowSymlinks follow, + KnownPaths* seenPaths) + : wildCards (parseWildcards (pattern)), + fileFinder (directory, (recursive || wildCards.size() > 1) ? "*" : pattern), + wildCard (pattern), + path (File::addTrailingSeparator (directory.getFullPathName())), + whatToLookFor (type), + isRecursive (recursive), + followSymlinks (follow), + knownPaths (seenPaths) + { + // you have to specify the type of files you're looking for! + jassert ((whatToLookFor & (File::findFiles | File::findDirectories)) != 0); + jassert (whatToLookFor > 0 && whatToLookFor <= 7); + + if (followSymlinks == File::FollowSymlinks::noCycles) + { + if (knownPaths == nullptr) + { + heapKnownPaths = std::make_unique(); + knownPaths = heapKnownPaths.get(); + } + + knownPaths->insert (directory); + } + } + //============================================================================== struct NativeIterator { @@ -152,6 +182,9 @@ private: bool hasBeenAdvanced = false; std::unique_ptr subIterator; File currentFile; + File::FollowSymlinks followSymlinks = File::FollowSymlinks::yes; + KnownPaths* knownPaths = nullptr; + std::unique_ptr heapKnownPaths; static StringArray parseWildcards (const String& pattern); static bool fileMatches (const StringArray& wildCards, const String& filename); diff --git a/modules/juce_core/files/juce_File.cpp b/modules/juce_core/files/juce_File.cpp index e17e57633f..85fd0b7da5 100644 --- a/modules/juce_core/files/juce_File.cpp +++ b/modules/juce_core/files/juce_File.cpp @@ -561,18 +561,18 @@ void File::readLines (StringArray& destLines) const } //============================================================================== -Array File::findChildFiles (int whatToLookFor, bool searchRecursively, const String& wildcard) const +Array File::findChildFiles (int whatToLookFor, bool searchRecursively, const String& wildcard, FollowSymlinks followSymlinks) const { Array results; - findChildFiles (results, whatToLookFor, searchRecursively, wildcard); + findChildFiles (results, whatToLookFor, searchRecursively, wildcard, followSymlinks); return results; } -int File::findChildFiles (Array& results, int whatToLookFor, bool searchRecursively, const String& wildcard) const +int File::findChildFiles (Array& results, int whatToLookFor, bool searchRecursively, const String& wildcard, FollowSymlinks followSymlinks) const { int total = 0; - for (const auto& di : RangedDirectoryIterator (*this, searchRecursively, wildcard, whatToLookFor)) + for (const auto& di : RangedDirectoryIterator (*this, searchRecursively, wildcard, whatToLookFor, followSymlinks)) { results.add (di.getFile()); ++total; diff --git a/modules/juce_core/files/juce_File.h b/modules/juce_core/files/juce_File.h index aa5a2ef3f5..a6f9f71ac4 100644 --- a/modules/juce_core/files/juce_File.h +++ b/modules/juce_core/files/juce_File.h @@ -560,6 +560,23 @@ public: ignoreHiddenFiles = 4 /**< Add this flag to avoid returning any hidden files in the results. */ }; + enum class FollowSymlinks + { + /** Requests that a file system traversal should not follow any symbolic links. */ + no, + + /** Requests that a file system traversal may follow symbolic links, but should attempt to + skip any symbolic links to directories that may cause a cycle. + */ + noCycles, + + /** Requests that a file system traversal follow all symbolic links. Use with care, as this + may produce inconsistent results, or fail to terminate, if the filesystem contains cycles + due to symbolic links. + */ + yes + }; + /** Searches this directory for files matching a wildcard pattern. Assuming that this file is a directory, this method will search it @@ -572,13 +589,15 @@ public: @param searchRecursively if true, all subdirectories will be recursed into to do an exhaustive search @param wildCardPattern the filename pattern to search for, e.g. "*.txt" + @param followSymlinks the method that should be used to handle symbolic links @returns the set of files that were found @see getNumberOfChildFiles, RangedDirectoryIterator */ Array findChildFiles (int whatToLookFor, bool searchRecursively, - const String& wildCardPattern = "*") const; + const String& wildCardPattern = "*", + FollowSymlinks followSymlinks = FollowSymlinks::yes) const; /** Searches inside a directory for files matching a wildcard pattern. Note that there's a newer, better version of this method which returns the results @@ -586,7 +605,8 @@ public: mainly for legacy code to use. */ int findChildFiles (Array& results, int whatToLookFor, - bool searchRecursively, const String& wildCardPattern = "*") const; + bool searchRecursively, const String& wildCardPattern = "*", + FollowSymlinks followSymlinks = FollowSymlinks::yes) const; /** Searches inside a directory and counts how many files match a wildcard pattern. diff --git a/modules/juce_core/files/juce_RangedDirectoryIterator.cpp b/modules/juce_core/files/juce_RangedDirectoryIterator.cpp index cbf3dbd618..fa5796cb4c 100644 --- a/modules/juce_core/files/juce_RangedDirectoryIterator.cpp +++ b/modules/juce_core/files/juce_RangedDirectoryIterator.cpp @@ -39,11 +39,13 @@ float DirectoryEntry::getEstimatedProgress() const RangedDirectoryIterator::RangedDirectoryIterator (const File& directory, bool isRecursive, const String& wildCard, - int whatToLookFor) + int whatToLookFor, + File::FollowSymlinks followSymlinks) : iterator (new DirectoryIterator (directory, isRecursive, wildCard, - whatToLookFor)) + whatToLookFor, + followSymlinks)) { entry.iterator = iterator; increment(); diff --git a/modules/juce_core/files/juce_RangedDirectoryIterator.h b/modules/juce_core/files/juce_RangedDirectoryIterator.h index 0c2ba66454..db34e8938b 100644 --- a/modules/juce_core/files/juce_RangedDirectoryIterator.h +++ b/modules/juce_core/files/juce_RangedDirectoryIterator.h @@ -118,11 +118,13 @@ public: separated by a semi-colon or comma, e.g. "*.jpg;*.png" @param whatToLookFor a value from the File::TypesOfFileToFind enum, specifying whether to look for files, directories, or both. + @param followSymlinks the policy to use when symlinks are encountered */ RangedDirectoryIterator (const File& directory, bool isRecursive, const String& wildCard = "*", - int whatToLookFor = File::findFiles); + int whatToLookFor = File::findFiles, + File::FollowSymlinks followSymlinks = File::FollowSymlinks::yes); /** Returns true if both iterators are in their end/sentinel state, otherwise returns false. diff --git a/modules/juce_core/system/juce_StandardHeader.h b/modules/juce_core/system/juce_StandardHeader.h index 919c864b09..c20535ff16 100644 --- a/modules/juce_core/system/juce_StandardHeader.h +++ b/modules/juce_core/system/juce_StandardHeader.h @@ -55,13 +55,14 @@ #include #include #include -#include #include #include #include #include +#include #include #include +#include #include #include #include