1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtCore module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 /*!
41     \since 4.3
42     \class QDirIterator
43     \inmodule QtCore
44     \brief The QDirIterator class provides an iterator for directory entrylists.
45 
46     You can use QDirIterator to navigate entries of a directory one at a time.
47     It is similar to QDir::entryList() and QDir::entryInfoList(), but because
48     it lists entries one at a time instead of all at once, it scales better
49     and is more suitable for large directories. It also supports listing
50     directory contents recursively, and following symbolic links. Unlike
51     QDir::entryList(), QDirIterator does not support sorting.
52 
53     The QDirIterator constructor takes a QDir or a directory as
54     argument. After construction, the iterator is located before the first
55     directory entry. Here's how to iterate over all the entries sequentially:
56 
57     \snippet code/src_corelib_io_qdiriterator.cpp 0
58 
59     Here's how to find and read all files filtered by name, recursively:
60 
61     \snippet code/src_corelib_io_qdiriterator.cpp 1
62 
63     The next() function returns the path to the next directory entry and
64     advances the iterator. You can also call filePath() to get the current
65     file path without advancing the iterator.  The fileName() function returns
66     only the name of the file, similar to how QDir::entryList() works. You can
67     also call fileInfo() to get a QFileInfo for the current entry.
68 
69     Unlike Qt's container iterators, QDirIterator is uni-directional (i.e.,
70     you cannot iterate directories in reverse order) and does not allow random
71     access.
72 
73     \sa QDir, QDir::entryList()
74 */
75 
76 /*! \enum QDirIterator::IteratorFlag
77 
78     This enum describes flags that you can combine to configure the behavior
79     of QDirIterator.
80 
81     \value NoIteratorFlags The default value, representing no flags. The
82     iterator will return entries for the assigned path.
83 
84     \value Subdirectories List entries inside all subdirectories as well.
85 
86     \value FollowSymlinks When combined with Subdirectories, this flag
87     enables iterating through all subdirectories of the assigned path,
88     following all symbolic links. Symbolic link loops (e.g., "link" => "." or
89     "link" => "..") are automatically detected and ignored.
90 */
91 
92 #include "qdiriterator.h"
93 #include "qdir_p.h"
94 #include "qabstractfileengine_p.h"
95 
96 #include <QtCore/qregexp.h>
97 #include <QtCore/qset.h>
98 #include <QtCore/qstack.h>
99 #include <QtCore/qvariant.h>
100 #if QT_CONFIG(regularexpression)
101 #include <QtCore/qregularexpression.h>
102 #endif
103 
104 #include <QtCore/private/qfilesystemiterator_p.h>
105 #include <QtCore/private/qfilesystementry_p.h>
106 #include <QtCore/private/qfilesystemmetadata_p.h>
107 #include <QtCore/private/qfilesystemengine_p.h>
108 #include <QtCore/private/qfileinfo_p.h>
109 
110 #include <memory>
111 
112 QT_BEGIN_NAMESPACE
113 
114 template <class Iterator>
115 class QDirIteratorPrivateIteratorStack : public QStack<Iterator *>
116 {
117 public:
~QDirIteratorPrivateIteratorStack()118     ~QDirIteratorPrivateIteratorStack()
119     {
120         qDeleteAll(*this);
121     }
122 };
123 
124 class QDirIteratorPrivate
125 {
126 public:
127     QDirIteratorPrivate(const QFileSystemEntry &entry, const QStringList &nameFilters,
128                         QDir::Filters _filters, QDirIterator::IteratorFlags flags, bool resolveEngine = true);
129 
130     void advance();
131 
132     bool entryMatches(const QString & fileName, const QFileInfo &fileInfo);
133     void pushDirectory(const QFileInfo &fileInfo);
134     void checkAndPushDirectory(const QFileInfo &);
135     bool matchesFilters(const QString &fileName, const QFileInfo &fi) const;
136 
137     std::unique_ptr<QAbstractFileEngine> engine;
138 
139     QFileSystemEntry dirEntry;
140     const QStringList nameFilters;
141     const QDir::Filters filters;
142     const QDirIterator::IteratorFlags iteratorFlags;
143 
144 #if defined(QT_BOOTSTRAPPED)
145     // ### Qt6: Get rid of this once we don't bootstrap qmake anymore
146     QVector<QRegExp> nameRegExps;
147 #elif QT_CONFIG(regularexpression)
148     QVector<QRegularExpression> nameRegExps;
149 #endif
150 
151     QDirIteratorPrivateIteratorStack<QAbstractFileEngineIterator> fileEngineIterators;
152 #ifndef QT_NO_FILESYSTEMITERATOR
153     QDirIteratorPrivateIteratorStack<QFileSystemIterator> nativeIterators;
154 #endif
155 
156     QFileInfo currentFileInfo;
157     QFileInfo nextFileInfo;
158 
159     // Loop protection
160     QSet<QString> visitedLinks;
161 };
162 
163 /*!
164     \internal
165 */
QDirIteratorPrivate(const QFileSystemEntry & entry,const QStringList & nameFilters,QDir::Filters _filters,QDirIterator::IteratorFlags flags,bool resolveEngine)166 QDirIteratorPrivate::QDirIteratorPrivate(const QFileSystemEntry &entry, const QStringList &nameFilters,
167                                          QDir::Filters _filters, QDirIterator::IteratorFlags flags, bool resolveEngine)
168     : dirEntry(entry)
169       , nameFilters(nameFilters.contains(QLatin1String("*")) ? QStringList() : nameFilters)
170       , filters(QDir::NoFilter == _filters ? QDir::AllEntries : _filters)
171       , iteratorFlags(flags)
172 {
173 #if defined(QT_BOOTSTRAPPED)
174     nameRegExps.reserve(nameFilters.size());
175     for (const auto &filter : nameFilters) {
176         nameRegExps.append(
177             QRegExp(filter,
178                     (filters & QDir::CaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive,
179                     QRegExp::Wildcard));
180     }
181 #elif QT_CONFIG(regularexpression)
182     nameRegExps.reserve(nameFilters.size());
183     for (const auto &filter : nameFilters) {
184         QString re = QRegularExpression::wildcardToRegularExpression(filter);
185         nameRegExps.append(
186             QRegularExpression(re, (filters & QDir::CaseSensitive) ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption));
187     }
188 #endif
189     QFileSystemMetaData metaData;
190     if (resolveEngine)
191         engine.reset(QFileSystemEngine::resolveEntryAndCreateLegacyEngine(dirEntry, metaData));
192     QFileInfo fileInfo(new QFileInfoPrivate(dirEntry, metaData));
193 
194     // Populate fields for hasNext() and next()
195     pushDirectory(fileInfo);
196     advance();
197 }
198 
199 /*!
200     \internal
201 */
pushDirectory(const QFileInfo & fileInfo)202 void QDirIteratorPrivate::pushDirectory(const QFileInfo &fileInfo)
203 {
204     QString path = fileInfo.filePath();
205 
206 #ifdef Q_OS_WIN
207     if (fileInfo.isSymLink())
208         path = fileInfo.canonicalFilePath();
209 #endif
210 
211     if (iteratorFlags & QDirIterator::FollowSymlinks)
212         visitedLinks << fileInfo.canonicalFilePath();
213 
214     if (engine) {
215         engine->setFileName(path);
216         QAbstractFileEngineIterator *it = engine->beginEntryList(filters, nameFilters);
217         if (it) {
218             it->setPath(path);
219             fileEngineIterators << it;
220         } else {
221             // No iterator; no entry list.
222         }
223     } else {
224 #ifndef QT_NO_FILESYSTEMITERATOR
225         QFileSystemIterator *it = new QFileSystemIterator(fileInfo.d_ptr->fileEntry,
226             filters, nameFilters, iteratorFlags);
227         nativeIterators << it;
228 #else
229         qWarning("Qt was built with -no-feature-filesystemiterator: no files/plugins will be found!");
230 #endif
231     }
232 }
233 
entryMatches(const QString & fileName,const QFileInfo & fileInfo)234 inline bool QDirIteratorPrivate::entryMatches(const QString & fileName, const QFileInfo &fileInfo)
235 {
236     checkAndPushDirectory(fileInfo);
237 
238     if (matchesFilters(fileName, fileInfo)) {
239         currentFileInfo = nextFileInfo;
240         nextFileInfo = fileInfo;
241 
242         //We found a matching entry.
243         return true;
244     }
245 
246     return false;
247 }
248 
249 /*!
250     \internal
251 */
advance()252 void QDirIteratorPrivate::advance()
253 {
254     if (engine) {
255         while (!fileEngineIterators.isEmpty()) {
256             // Find the next valid iterator that matches the filters.
257             QAbstractFileEngineIterator *it;
258             while (it = fileEngineIterators.top(), it->hasNext()) {
259                 it->next();
260                 if (entryMatches(it->currentFileName(), it->currentFileInfo()))
261                     return;
262             }
263 
264             fileEngineIterators.pop();
265             delete it;
266         }
267     } else {
268 #ifndef QT_NO_FILESYSTEMITERATOR
269         QFileSystemEntry nextEntry;
270         QFileSystemMetaData nextMetaData;
271 
272         while (!nativeIterators.isEmpty()) {
273             // Find the next valid iterator that matches the filters.
274             QFileSystemIterator *it;
275             while (it = nativeIterators.top(), it->advance(nextEntry, nextMetaData)) {
276                 QFileInfo info(new QFileInfoPrivate(nextEntry, nextMetaData));
277 
278                 if (entryMatches(nextEntry.fileName(), info))
279                     return;
280                 nextMetaData = QFileSystemMetaData();
281             }
282 
283             nativeIterators.pop();
284             delete it;
285         }
286 #endif
287     }
288 
289     currentFileInfo = nextFileInfo;
290     nextFileInfo = QFileInfo();
291 }
292 
293 /*!
294     \internal
295  */
checkAndPushDirectory(const QFileInfo & fileInfo)296 void QDirIteratorPrivate::checkAndPushDirectory(const QFileInfo &fileInfo)
297 {
298     // If we're doing flat iteration, we're done.
299     if (!(iteratorFlags & QDirIterator::Subdirectories))
300         return;
301 
302     // Never follow non-directory entries
303     if (!fileInfo.isDir())
304         return;
305 
306     // Follow symlinks only when asked
307     if (!(iteratorFlags & QDirIterator::FollowSymlinks) && fileInfo.isSymLink())
308         return;
309 
310     // Never follow . and ..
311     QString fileName = fileInfo.fileName();
312     if (QLatin1String(".") == fileName || QLatin1String("..") == fileName)
313         return;
314 
315     // No hidden directories unless requested
316     if (!(filters & QDir::AllDirs) && !(filters & QDir::Hidden) && fileInfo.isHidden())
317         return;
318 
319     // Stop link loops
320     if (!visitedLinks.isEmpty() &&
321         visitedLinks.contains(fileInfo.canonicalFilePath()))
322         return;
323 
324     pushDirectory(fileInfo);
325 }
326 
327 /*!
328     \internal
329 
330     This convenience function implements the iterator's filtering logics and
331     applies then to the current directory entry.
332 
333     It returns \c true if the current entry matches the filters (i.e., the
334     current entry will be returned as part of the directory iteration);
335     otherwise, false is returned.
336 */
337 
matchesFilters(const QString & fileName,const QFileInfo & fi) const338 bool QDirIteratorPrivate::matchesFilters(const QString &fileName, const QFileInfo &fi) const
339 {
340     if (fileName.isEmpty())
341         return false;
342 
343     // filter . and ..?
344     const int fileNameSize = fileName.size();
345     const bool dotOrDotDot = fileName[0] == QLatin1Char('.')
346                              && ((fileNameSize == 1)
347                                  ||(fileNameSize == 2 && fileName[1] == QLatin1Char('.')));
348     if ((filters & QDir::NoDot) && dotOrDotDot && fileNameSize == 1)
349         return false;
350     if ((filters & QDir::NoDotDot) && dotOrDotDot && fileNameSize == 2)
351         return false;
352 
353     // name filter
354 #if QT_CONFIG(regularexpression) || defined(QT_BOOTSTRAPPED)
355     // Pass all entries through name filters, except dirs if the AllDirs
356     if (!nameFilters.isEmpty() && !((filters & QDir::AllDirs) && fi.isDir())) {
357         bool matched = false;
358         for (const auto &re : nameRegExps) {
359 #if defined(QT_BOOTSTRAPPED)
360             QRegExp copy = re;
361             if (copy.exactMatch(fileName)) {
362                 matched = true;
363                 break;
364             }
365 #else
366             if (re.match(fileName).hasMatch()) {
367                 matched = true;
368                 break;
369             }
370 #endif
371         }
372         if (!matched)
373             return false;
374     }
375 #endif
376     // skip symlinks
377     const bool skipSymlinks = (filters & QDir::NoSymLinks);
378     const bool includeSystem = (filters & QDir::System);
379     if(skipSymlinks && fi.isSymLink()) {
380         // The only reason to save this file is if it is a broken link and we are requesting system files.
381         if(!includeSystem || fi.exists())
382             return false;
383     }
384 
385     // filter hidden
386     const bool includeHidden = (filters & QDir::Hidden);
387     if (!includeHidden && !dotOrDotDot && fi.isHidden())
388         return false;
389 
390     // filter system files
391     if (!includeSystem && (!(fi.isFile() || fi.isDir() || fi.isSymLink())
392                     || (!fi.exists() && fi.isSymLink())))
393         return false;
394 
395     // skip directories
396     const bool skipDirs = !(filters & (QDir::Dirs | QDir::AllDirs));
397     if (skipDirs && fi.isDir())
398         return false;
399 
400     // skip files
401     const bool skipFiles    = !(filters & QDir::Files);
402     if (skipFiles && fi.isFile())
403         // Basically we need a reason not to exclude this file otherwise we just eliminate it.
404         return false;
405 
406     // filter permissions
407     const bool filterPermissions = ((filters & QDir::PermissionMask)
408                                     && (filters & QDir::PermissionMask) != QDir::PermissionMask);
409     const bool doWritable = !filterPermissions || (filters & QDir::Writable);
410     const bool doExecutable = !filterPermissions || (filters & QDir::Executable);
411     const bool doReadable = !filterPermissions || (filters & QDir::Readable);
412     if (filterPermissions
413         && ((doReadable && !fi.isReadable())
414             || (doWritable && !fi.isWritable())
415             || (doExecutable && !fi.isExecutable()))) {
416         return false;
417     }
418 
419     return true;
420 }
421 
422 /*!
423     Constructs a QDirIterator that can iterate over \a dir's entrylist, using
424     \a dir's name filters and regular filters. You can pass options via \a
425     flags to decide how the directory should be iterated.
426 
427     By default, \a flags is NoIteratorFlags, which provides the same behavior
428     as in QDir::entryList().
429 
430     The sorting in \a dir is ignored.
431 
432     \note To list symlinks that point to non existing files, QDir::System must be
433      passed to the flags.
434 
435     \sa hasNext(), next(), IteratorFlags
436 */
QDirIterator(const QDir & dir,IteratorFlags flags)437 QDirIterator::QDirIterator(const QDir &dir, IteratorFlags flags)
438 {
439     const QDirPrivate *other = dir.d_ptr.constData();
440     d.reset(new QDirIteratorPrivate(other->dirEntry, other->nameFilters, other->filters, flags, bool(other->fileEngine)));
441 }
442 
443 /*!
444     Constructs a QDirIterator that can iterate over \a path, with no name
445     filtering and \a filters for entry filtering. You can pass options via \a
446     flags to decide how the directory should be iterated.
447 
448     By default, \a filters is QDir::NoFilter, and \a flags is NoIteratorFlags,
449     which provides the same behavior as in QDir::entryList().
450 
451     \note To list symlinks that point to non existing files, QDir::System must be
452      passed to the flags.
453 
454     \sa hasNext(), next(), IteratorFlags
455 */
QDirIterator(const QString & path,QDir::Filters filters,IteratorFlags flags)456 QDirIterator::QDirIterator(const QString &path, QDir::Filters filters, IteratorFlags flags)
457     : d(new QDirIteratorPrivate(QFileSystemEntry(path), QStringList(), filters, flags))
458 {
459 }
460 
461 /*!
462     Constructs a QDirIterator that can iterate over \a path. You can pass
463     options via \a flags to decide how the directory should be iterated.
464 
465     By default, \a flags is NoIteratorFlags, which provides the same behavior
466     as in QDir::entryList().
467 
468     \note To list symlinks that point to non existing files, QDir::System must be
469      passed to the flags.
470 
471     \sa hasNext(), next(), IteratorFlags
472 */
QDirIterator(const QString & path,IteratorFlags flags)473 QDirIterator::QDirIterator(const QString &path, IteratorFlags flags)
474     : d(new QDirIteratorPrivate(QFileSystemEntry(path), QStringList(), QDir::NoFilter, flags))
475 {
476 }
477 
478 /*!
479     Constructs a QDirIterator that can iterate over \a path, using \a
480     nameFilters and \a filters. You can pass options via \a flags to decide
481     how the directory should be iterated.
482 
483     By default, \a flags is NoIteratorFlags, which provides the same behavior
484     as QDir::entryList().
485 
486     For example, the following iterator could be used to iterate over audio
487     files:
488 
489     \snippet code/src_corelib_io_qdiriterator.cpp 2
490 
491     \note To list symlinks that point to non existing files, QDir::System must be
492      passed to the flags.
493 
494     \sa hasNext(), next(), IteratorFlags, QDir::setNameFilters()
495 */
QDirIterator(const QString & path,const QStringList & nameFilters,QDir::Filters filters,IteratorFlags flags)496 QDirIterator::QDirIterator(const QString &path, const QStringList &nameFilters,
497                            QDir::Filters filters, IteratorFlags flags)
498     : d(new QDirIteratorPrivate(QFileSystemEntry(path), nameFilters, filters, flags))
499 {
500 }
501 
502 /*!
503     Destroys the QDirIterator.
504 */
~QDirIterator()505 QDirIterator::~QDirIterator()
506 {
507 }
508 
509 /*!
510     Advances the iterator to the next entry, and returns the file path of this
511     new entry. If hasNext() returns \c false, this function does nothing, and
512     returns an empty QString.
513 
514     You can call fileName() or filePath() to get the current entry file name
515     or path, or fileInfo() to get a QFileInfo for the current entry.
516 
517     \sa hasNext(), fileName(), filePath(), fileInfo()
518 */
next()519 QString QDirIterator::next()
520 {
521     d->advance();
522     return filePath();
523 }
524 
525 /*!
526     Returns \c true if there is at least one more entry in the directory;
527     otherwise, false is returned.
528 
529     \sa next(), fileName(), filePath(), fileInfo()
530 */
hasNext() const531 bool QDirIterator::hasNext() const
532 {
533     if (d->engine)
534         return !d->fileEngineIterators.isEmpty();
535     else
536 #ifndef QT_NO_FILESYSTEMITERATOR
537         return !d->nativeIterators.isEmpty();
538 #else
539         return false;
540 #endif
541 }
542 
543 /*!
544     Returns the file name for the current directory entry, without the path
545     prepended.
546 
547     This function is convenient when iterating a single directory. When using
548     the QDirIterator::Subdirectories flag, you can use filePath() to get the
549     full path.
550 
551     \sa filePath(), fileInfo()
552 */
fileName() const553 QString QDirIterator::fileName() const
554 {
555     return d->currentFileInfo.fileName();
556 }
557 
558 /*!
559     Returns the full file path for the current directory entry.
560 
561     \sa fileInfo(), fileName()
562 */
filePath() const563 QString QDirIterator::filePath() const
564 {
565     return d->currentFileInfo.filePath();
566 }
567 
568 /*!
569     Returns a QFileInfo for the current directory entry.
570 
571     \sa filePath(), fileName()
572 */
fileInfo() const573 QFileInfo QDirIterator::fileInfo() const
574 {
575     return d->currentFileInfo;
576 }
577 
578 /*!
579     Returns the base directory of the iterator.
580 */
path() const581 QString QDirIterator::path() const
582 {
583     return d->dirEntry.filePath();
584 }
585 
586 QT_END_NAMESPACE
587