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/qset.h>
97#include <QtCore/qstack.h>
98#include <QtCore/qvariant.h>
99#if QT_CONFIG(regularexpression)
100#include <QtCore/qregularexpression.h>
101#endif
102
103#include <QtCore/private/qfilesystemiterator_p.h>
104#include <QtCore/private/qfilesystementry_p.h>
105#include <QtCore/private/qfilesystemmetadata_p.h>
106#include <QtCore/private/qfilesystemengine_p.h>
107#include <QtCore/private/qfileinfo_p.h>
108#include <QtCore/private/qduplicatetracker_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 QT_CONFIG(regularexpression)
145 QList<QRegularExpression> nameRegExps;
146#endif
147
148 QDirIteratorPrivateIteratorStack<QAbstractFileEngineIterator> fileEngineIterators;
149#ifndef QT_NO_FILESYSTEMITERATOR
150 QDirIteratorPrivateIteratorStack<QFileSystemIterator> nativeIterators;
151#endif
152
153 QFileInfo currentFileInfo;
154 QFileInfo nextFileInfo;
155
156 // Loop protection
157 QDuplicateTracker<QString> visitedLinks;
158};
159
160/*!
161 \internal
162*/
163QDirIteratorPrivate::QDirIteratorPrivate(const QFileSystemEntry &entry, const QStringList &nameFilters,
164 QDir::Filters filters, QDirIterator::IteratorFlags flags, bool resolveEngine)
165 : dirEntry(entry)
166 , nameFilters(nameFilters.contains(QLatin1String("*")) ? QStringList() : nameFilters)
167 , filters(QDir::NoFilter == filters ? QDir::AllEntries : filters)
168 , iteratorFlags(flags)
169{
170#if QT_CONFIG(regularexpression)
171 nameRegExps.reserve(nameFilters.size());
172 for (const auto &filter : nameFilters) {
173 auto re = QRegularExpression::fromWildcard(filter, (filters & QDir::CaseSensitive ?
174 Qt::CaseSensitive : Qt::CaseInsensitive));
175 nameRegExps.append(re);
176 }
177#endif
178 QFileSystemMetaData metaData;
179 if (resolveEngine)
180 engine.reset(QFileSystemEngine::resolveEntryAndCreateLegacyEngine(dirEntry, metaData));
181 QFileInfo fileInfo(new QFileInfoPrivate(dirEntry, metaData));
182
183 // Populate fields for hasNext() and next()
184 pushDirectory(fileInfo);
185 advance();
186}
187
188/*!
189 \internal
190*/
191void QDirIteratorPrivate::pushDirectory(const QFileInfo &fileInfo)
192{
193 QString path = fileInfo.filePath();
194
195#ifdef Q_OS_WIN
196 if (fileInfo.isSymLink())
197 path = fileInfo.canonicalFilePath();
198#endif
199
200 if ((iteratorFlags & QDirIterator::FollowSymlinks)) {
201 // Stop link loops
202 if (visitedLinks.hasSeen(fileInfo.canonicalFilePath()))
203 return;
204 }
205
206 if (engine) {
207 engine->setFileName(path);
208 QAbstractFileEngineIterator *it = engine->beginEntryList(filters, nameFilters);
209 if (it) {
210 it->setPath(path);
211 fileEngineIterators << it;
212 } else {
213 // No iterator; no entry list.
214 }
215 } else {
216#ifndef QT_NO_FILESYSTEMITERATOR
217 QFileSystemIterator *it = new QFileSystemIterator(fileInfo.d_ptr->fileEntry,
218 filters, nameFilters, iteratorFlags);
219 nativeIterators << it;
220#else
221 qWarning("Qt was built with -no-feature-filesystemiterator: no files/plugins will be found!");
222#endif
223 }
224}
225
226inline bool QDirIteratorPrivate::entryMatches(const QString & fileName, const QFileInfo &fileInfo)
227{
228 checkAndPushDirectory(fileInfo);
229
230 if (matchesFilters(fileName, fileInfo)) {
231 currentFileInfo = nextFileInfo;
232 nextFileInfo = fileInfo;
233
234 //We found a matching entry.
235 return true;
236 }
237
238 return false;
239}
240
241/*!
242 \internal
243*/
244void QDirIteratorPrivate::advance()
245{
246 if (engine) {
247 while (!fileEngineIterators.isEmpty()) {
248 // Find the next valid iterator that matches the filters.
249 QAbstractFileEngineIterator *it;
250 while (it = fileEngineIterators.top(), it->hasNext()) {
251 it->next();
252 if (entryMatches(it->currentFileName(), it->currentFileInfo()))
253 return;
254 }
255
256 fileEngineIterators.pop();
257 delete it;
258 }
259 } else {
260#ifndef QT_NO_FILESYSTEMITERATOR
261 QFileSystemEntry nextEntry;
262 QFileSystemMetaData nextMetaData;
263
264 while (!nativeIterators.isEmpty()) {
265 // Find the next valid iterator that matches the filters.
266 QFileSystemIterator *it;
267 while (it = nativeIterators.top(), it->advance(nextEntry, nextMetaData)) {
268 QFileInfo info(new QFileInfoPrivate(nextEntry, nextMetaData));
269
270 if (entryMatches(nextEntry.fileName(), info))
271 return;
272 nextMetaData = QFileSystemMetaData();
273 }
274
275 nativeIterators.pop();
276 delete it;
277 }
278#endif
279 }
280
281 currentFileInfo = nextFileInfo;
282 nextFileInfo = QFileInfo();
283}
284
285/*!
286 \internal
287 */
288void QDirIteratorPrivate::checkAndPushDirectory(const QFileInfo &fileInfo)
289{
290 // If we're doing flat iteration, we're done.
291 if (!(iteratorFlags & QDirIterator::Subdirectories))
292 return;
293
294 // Never follow non-directory entries
295 if (!fileInfo.isDir())
296 return;
297
298 // Follow symlinks only when asked
299 if (!(iteratorFlags & QDirIterator::FollowSymlinks) && fileInfo.isSymLink())
300 return;
301
302 // Never follow . and ..
303 QString fileName = fileInfo.fileName();
304 if (QLatin1String(".") == fileName || QLatin1String("..") == fileName)
305 return;
306
307 // No hidden directories unless requested
308 if (!(filters & QDir::AllDirs) && !(filters & QDir::Hidden) && fileInfo.isHidden())
309 return;
310
311 pushDirectory(fileInfo);
312}
313
314/*!
315 \internal
316
317 This convenience function implements the iterator's filtering logics and
318 applies then to the current directory entry.
319
320 It returns \c true if the current entry matches the filters (i.e., the
321 current entry will be returned as part of the directory iteration);
322 otherwise, false is returned.
323*/
324
325bool QDirIteratorPrivate::matchesFilters(const QString &fileName, const QFileInfo &fi) const
326{
327 if (fileName.isEmpty())
328 return false;
329
330 // filter . and ..?
331 const int fileNameSize = fileName.size();
332 const bool dotOrDotDot = fileName[0] == QLatin1Char('.')
333 && ((fileNameSize == 1)
334 ||(fileNameSize == 2 && fileName[1] == QLatin1Char('.')));
335 if ((filters & QDir::NoDot) && dotOrDotDot && fileNameSize == 1)
336 return false;
337 if ((filters & QDir::NoDotDot) && dotOrDotDot && fileNameSize == 2)
338 return false;
339
340 // name filter
341#if QT_CONFIG(regularexpression)
342 // Pass all entries through name filters, except dirs if the AllDirs
343 if (!nameFilters.isEmpty() && !((filters & QDir::AllDirs) && fi.isDir())) {
344 bool matched = false;
345 for (const auto &re : nameRegExps) {
346 if (re.match(fileName).hasMatch()) {
347 matched = true;
348 break;
349 }
350 }
351 if (!matched)
352 return false;
353 }
354#endif
355 // skip symlinks
356 const bool skipSymlinks = (filters & QDir::NoSymLinks);
357 const bool includeSystem = (filters & QDir::System);
358 if(skipSymlinks && fi.isSymLink()) {
359 // The only reason to save this file is if it is a broken link and we are requesting system files.
360 if(!includeSystem || fi.exists())
361 return false;
362 }
363
364 // filter hidden
365 const bool includeHidden = (filters & QDir::Hidden);
366 if (!includeHidden && !dotOrDotDot && fi.isHidden())
367 return false;
368
369 // filter system files
370 if (!includeSystem && (!(fi.isFile() || fi.isDir() || fi.isSymLink())
371 || (!fi.exists() && fi.isSymLink())))
372 return false;
373
374 // skip directories
375 const bool skipDirs = !(filters & (QDir::Dirs | QDir::AllDirs));
376 if (skipDirs && fi.isDir())
377 return false;
378
379 // skip files
380 const bool skipFiles = !(filters & QDir::Files);
381 if (skipFiles && fi.isFile())
382 // Basically we need a reason not to exclude this file otherwise we just eliminate it.
383 return false;
384
385 // filter permissions
386 const bool filterPermissions = ((filters & QDir::PermissionMask)
387 && (filters & QDir::PermissionMask) != QDir::PermissionMask);
388 const bool doWritable = !filterPermissions || (filters & QDir::Writable);
389 const bool doExecutable = !filterPermissions || (filters & QDir::Executable);
390 const bool doReadable = !filterPermissions || (filters & QDir::Readable);
391 if (filterPermissions
392 && ((doReadable && !fi.isReadable())
393 || (doWritable && !fi.isWritable())
394 || (doExecutable && !fi.isExecutable()))) {
395 return false;
396 }
397
398 return true;
399}
400
401/*!
402 Constructs a QDirIterator that can iterate over \a dir's entrylist, using
403 \a dir's name filters and regular filters. You can pass options via \a
404 flags to decide how the directory should be iterated.
405
406 By default, \a flags is NoIteratorFlags, which provides the same behavior
407 as in QDir::entryList().
408
409 The sorting in \a dir is ignored.
410
411 \note To list symlinks that point to non existing files, QDir::System must be
412 passed to the flags.
413
414 \sa hasNext(), next(), IteratorFlags
415*/
416QDirIterator::QDirIterator(const QDir &dir, IteratorFlags flags)
417{
418 const QDirPrivate *other = dir.d_ptr.constData();
419 d.reset(new QDirIteratorPrivate(other->dirEntry, other->nameFilters, other->filters, flags, bool(other->fileEngine)));
420}
421
422/*!
423 Constructs a QDirIterator that can iterate over \a path, with no name
424 filtering and \a filters for entry filtering. You can pass options via \a
425 flags to decide how the directory should be iterated.
426
427 By default, \a filters is QDir::NoFilter, and \a flags is NoIteratorFlags,
428 which provides the same behavior as in QDir::entryList().
429
430 \note To list symlinks that point to non existing files, QDir::System must be
431 passed to the flags.
432
433 \sa hasNext(), next(), IteratorFlags
434*/
435QDirIterator::QDirIterator(const QString &path, QDir::Filters filters, IteratorFlags flags)
436 : d(new QDirIteratorPrivate(QFileSystemEntry(path), QStringList(), filters, flags))
437{
438}
439
440/*!
441 Constructs a QDirIterator that can iterate over \a path. You can pass
442 options via \a flags to decide how the directory should be iterated.
443
444 By default, \a flags is NoIteratorFlags, which provides the same behavior
445 as in QDir::entryList().
446
447 \note To list symlinks that point to non existing files, QDir::System must be
448 passed to the flags.
449
450 \sa hasNext(), next(), IteratorFlags
451*/
452QDirIterator::QDirIterator(const QString &path, IteratorFlags flags)
453 : d(new QDirIteratorPrivate(QFileSystemEntry(path), QStringList(), QDir::NoFilter, flags))
454{
455}
456
457/*!
458 Constructs a QDirIterator that can iterate over \a path, using \a
459 nameFilters and \a filters. You can pass options via \a flags to decide
460 how the directory should be iterated.
461
462 By default, \a flags is NoIteratorFlags, which provides the same behavior
463 as QDir::entryList().
464
465 For example, the following iterator could be used to iterate over audio
466 files:
467
468 \snippet code/src_corelib_io_qdiriterator.cpp 2
469
470 \note To list symlinks that point to non existing files, QDir::System must be
471 passed to the flags.
472
473 \sa hasNext(), next(), IteratorFlags, QDir::setNameFilters()
474*/
475QDirIterator::QDirIterator(const QString &path, const QStringList &nameFilters,
476 QDir::Filters filters, IteratorFlags flags)
477 : d(new QDirIteratorPrivate(QFileSystemEntry(path), nameFilters, filters, flags))
478{
479}
480
481/*!
482 Destroys the QDirIterator.
483*/
484QDirIterator::~QDirIterator()
485{
486}
487
488/*!
489 Advances the iterator to the next entry, and returns the file path of this
490 new entry. If hasNext() returns \c false, this function does nothing, and
491 returns an empty QString.
492
493 You can call fileName() or filePath() to get the current entry file name
494 or path, or fileInfo() to get a QFileInfo for the current entry.
495
496 \sa hasNext(), fileName(), filePath(), fileInfo()
497*/
498QString QDirIterator::next()
499{
500 d->advance();
501 return filePath();
502}
503
504/*!
505 Returns \c true if there is at least one more entry in the directory;
506 otherwise, false is returned.
507
508 \sa next(), fileName(), filePath(), fileInfo()
509*/
510bool QDirIterator::hasNext() const
511{
512 if (d->engine)
513 return !d->fileEngineIterators.isEmpty();
514 else
515#ifndef QT_NO_FILESYSTEMITERATOR
516 return !d->nativeIterators.isEmpty();
517#else
518 return false;
519#endif
520}
521
522/*!
523 Returns the file name for the current directory entry, without the path
524 prepended.
525
526 This function is convenient when iterating a single directory. When using
527 the QDirIterator::Subdirectories flag, you can use filePath() to get the
528 full path.
529
530 \sa filePath(), fileInfo()
531*/
532QString QDirIterator::fileName() const
533{
534 return d->currentFileInfo.fileName();
535}
536
537/*!
538 Returns the full file path for the current directory entry.
539
540 \sa fileInfo(), fileName()
541*/
542QString QDirIterator::filePath() const
543{
544 return d->currentFileInfo.filePath();
545}
546
547/*!
548 Returns a QFileInfo for the current directory entry.
549
550 \sa filePath(), fileName()
551*/
552QFileInfo QDirIterator::fileInfo() const
553{
554 return d->currentFileInfo;
555}
556
557/*!
558 Returns the base directory of the iterator.
559*/
560QString QDirIterator::path() const
561{
562 return d->dirEntry.filePath();
563}
564
565QT_END_NAMESPACE
566