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
112QT_BEGIN_NAMESPACE
113
114template <class Iterator>
115class QDirIteratorPrivateIteratorStack : public QStack<Iterator *>
116{
117public:
118 ~QDirIteratorPrivateIteratorStack()
119 {
120 qDeleteAll(*this);
121 }
122};
123
124class QDirIteratorPrivate
125{
126public:
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*/
166QDirIteratorPrivate::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::anchoredPattern(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*/
202void 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
234inline 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*/
252void 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 */
296void 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
338bool 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*/
437QDirIterator::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*/
456QDirIterator::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*/
473QDirIterator::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 \note To list symlinks that point to non existing files, QDir::System must be
487 passed to the flags.
488
489 \sa hasNext(), next(), IteratorFlags
490*/
491QDirIterator::QDirIterator(const QString &path, const QStringList &nameFilters,
492 QDir::Filters filters, IteratorFlags flags)
493 : d(new QDirIteratorPrivate(QFileSystemEntry(path), nameFilters, filters, flags))
494{
495}
496
497/*!
498 Destroys the QDirIterator.
499*/
500QDirIterator::~QDirIterator()
501{
502}
503
504/*!
505 Advances the iterator to the next entry, and returns the file path of this
506 new entry. If hasNext() returns \c false, this function does nothing, and
507 returns an empty QString.
508
509 You can call fileName() or filePath() to get the current entry file name
510 or path, or fileInfo() to get a QFileInfo for the current entry.
511
512 \sa hasNext(), fileName(), filePath(), fileInfo()
513*/
514QString QDirIterator::next()
515{
516 d->advance();
517 return filePath();
518}
519
520/*!
521 Returns \c true if there is at least one more entry in the directory;
522 otherwise, false is returned.
523
524 \sa next(), fileName(), filePath(), fileInfo()
525*/
526bool QDirIterator::hasNext() const
527{
528 if (d->engine)
529 return !d->fileEngineIterators.isEmpty();
530 else
531#ifndef QT_NO_FILESYSTEMITERATOR
532 return !d->nativeIterators.isEmpty();
533#else
534 return false;
535#endif
536}
537
538/*!
539 Returns the file name for the current directory entry, without the path
540 prepended.
541
542 This function is convenient when iterating a single directory. When using
543 the QDirIterator::Subdirectories flag, you can use filePath() to get the
544 full path.
545
546 \sa filePath(), fileInfo()
547*/
548QString QDirIterator::fileName() const
549{
550 return d->currentFileInfo.fileName();
551}
552
553/*!
554 Returns the full file path for the current directory entry.
555
556 \sa fileInfo(), fileName()
557*/
558QString QDirIterator::filePath() const
559{
560 return d->currentFileInfo.filePath();
561}
562
563/*!
564 Returns a QFileInfo for the current directory entry.
565
566 \sa filePath(), fileName()
567*/
568QFileInfo QDirIterator::fileInfo() const
569{
570 return d->currentFileInfo;
571}
572
573/*!
574 Returns the base directory of the iterator.
575*/
576QString QDirIterator::path() const
577{
578 return d->dirEntry.filePath();
579}
580
581QT_END_NAMESPACE
582