1 /*
2     This file is part of the KDE project
3     SPDX-FileCopyrightText: 1999-2011 David Faure <faure@kde.org>
4     SPDX-FileCopyrightText: 2001 Carsten Pfeiffer <pfeiffer@kde.org>
5 
6     SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8 
9 #include "kfileitem.h"
10 
11 #include "config-kiocore.h"
12 
13 #if HAVE_POSIX_ACL
14 #include "../aclhelpers_p.h"
15 #endif
16 
17 #include "../pathhelpers_p.h"
18 #include "kiocoredebug.h"
19 #include "kioglobal_p.h"
20 
21 #include <QDataStream>
22 #include <QDate>
23 #include <QDebug>
24 #include <QDir>
25 #include <QDirIterator>
26 #include <QLocale>
27 #include <QMimeDatabase>
28 
29 #include <KConfigGroup>
30 #include <KDesktopFile>
31 #include <KLocalizedString>
32 #include <kmountpoint.h>
33 #ifndef Q_OS_WIN
34 #include <knfsshare.h>
35 #include <ksambashare.h>
36 #endif
37 #include <KFileSystemType>
38 #include <KProtocolManager>
39 
40 #define KFILEITEM_DEBUG 0
41 
42 class KFileItemPrivate : public QSharedData
43 {
44 public:
KFileItemPrivate(const KIO::UDSEntry & entry,mode_t mode,mode_t permissions,const QUrl & itemOrDirUrl,bool urlIsDirectory,bool delayedMimeTypes,KFileItem::MimeTypeDetermination mimeTypeDetermination)45     KFileItemPrivate(const KIO::UDSEntry &entry,
46                      mode_t mode,
47                      mode_t permissions,
48                      const QUrl &itemOrDirUrl,
49                      bool urlIsDirectory,
50                      bool delayedMimeTypes,
51                      KFileItem::MimeTypeDetermination mimeTypeDetermination)
52         : m_entry(entry)
53         , m_url(itemOrDirUrl)
54         , m_strName()
55         , m_strText()
56         , m_iconName()
57         , m_strLowerCaseName()
58         , m_mimeType()
59         , m_fileMode(mode)
60         , m_permissions(permissions)
61         , m_addACL(false)
62         , m_bLink(false)
63         , m_bIsLocalUrl(itemOrDirUrl.isLocalFile())
64         , m_bMimeTypeKnown(false)
65         , m_delayedMimeTypes(delayedMimeTypes)
66         , m_useIconNameCache(false)
67         , m_hidden(Auto)
68         , m_slow(SlowUnknown)
69         , m_bSkipMimeTypeFromContent(mimeTypeDetermination == KFileItem::SkipMimeTypeFromContent)
70         , m_bInitCalled(false)
71     {
72         if (entry.count() != 0) {
73             readUDSEntry(urlIsDirectory);
74         } else {
75             Q_ASSERT(!urlIsDirectory);
76             m_strName = itemOrDirUrl.fileName();
77             m_strText = KIO::decodeFileName(m_strName);
78         }
79     }
80 
81     /**
82      * Call init() if not yet done.
83      */
84     void ensureInitialized() const;
85 
86     /**
87      * Computes the text and mode from the UDSEntry.
88      */
89     void init() const;
90 
91     QString localPath() const;
92     KIO::filesize_t size() const;
93     KIO::filesize_t recursiveSize() const;
94     QDateTime time(KFileItem::FileTimes which) const;
95     void setTime(KFileItem::FileTimes which, uint time_t_val) const;
96     void setTime(KFileItem::FileTimes which, const QDateTime &val) const;
97     bool cmp(const KFileItemPrivate &item) const;
98     void printCompareDebug(const KFileItemPrivate &item) const;
99     bool isSlow() const;
100 
101     /**
102      * Extracts the data from the UDSEntry member and updates the KFileItem
103      * accordingly.
104      */
105     void readUDSEntry(bool _urlIsDirectory);
106 
107     /**
108      * Parses the given permission set and provides it for access()
109      */
110     QString parsePermissions(mode_t perm) const;
111 
112     /**
113      * Mime type helper
114      */
115     void determineMimeTypeHelper(const QUrl &url) const;
116 
117     /**
118      * The UDSEntry that contains the data for this fileitem, if it came from a directory listing.
119      */
120     mutable KIO::UDSEntry m_entry;
121     /**
122      * The url of the file
123      */
124     QUrl m_url;
125 
126     /**
127      * The text for this item, i.e. the file name without path,
128      */
129     QString m_strName;
130 
131     /**
132      * The text for this item, i.e. the file name without path, decoded
133      * ('%%' becomes '%', '%2F' becomes '/')
134      */
135     QString m_strText;
136 
137     /**
138      * The icon name for this item.
139      */
140     mutable QString m_iconName;
141 
142     /**
143      * The filename in lower case (to speed up sorting)
144      */
145     mutable QString m_strLowerCaseName;
146 
147     /**
148      * The MIME type of the file
149      */
150     mutable QMimeType m_mimeType;
151 
152     /**
153      * The file mode
154      */
155     mutable mode_t m_fileMode;
156     /**
157      * The permissions
158      */
159     mutable mode_t m_permissions;
160 
161     /**
162      * Whether the UDSEntry ACL fields should be added to m_entry.
163      */
164     mutable bool m_addACL : 1;
165 
166     /**
167      * Whether the file is a link
168      */
169     mutable bool m_bLink : 1;
170     /**
171      * True if local file
172      */
173     bool m_bIsLocalUrl : 1;
174 
175     mutable bool m_bMimeTypeKnown : 1;
176     mutable bool m_delayedMimeTypes : 1;
177 
178     /** True if m_iconName should be used as cache. */
179     mutable bool m_useIconNameCache : 1;
180 
181     // Auto: check leading dot.
182     enum { Auto, Hidden, Shown } m_hidden : 3;
183 
184     // Slow? (nfs/smb/ssh)
185     mutable enum { SlowUnknown, Fast, Slow } m_slow : 3;
186 
187     /**
188      * True if MIME type determination by content should be skipped
189      */
190     bool m_bSkipMimeTypeFromContent : 1;
191 
192     /**
193      * True if init() was called on demand
194      */
195     mutable bool m_bInitCalled : 1;
196 
197     // For special case like link to dirs over FTP
198     QString m_guessedMimeType;
199     mutable QString m_access;
200 };
201 
ensureInitialized() const202 void KFileItemPrivate::ensureInitialized() const
203 {
204     if (!m_bInitCalled) {
205         init();
206     }
207 }
208 
init() const209 void KFileItemPrivate::init() const
210 {
211     m_access.clear();
212     //  metaInfo = KFileMetaInfo();
213 
214     // stat() local files if needed
215     const bool shouldStat = (m_fileMode == KFileItem::Unknown || m_permissions == KFileItem::Unknown || m_entry.count() == 0) && m_url.isLocalFile();
216     if (shouldStat) {
217         /* directories may not have a slash at the end if we want to stat()
218          * them; it requires that we change into it .. which may not be allowed
219          * stat("/is/unaccessible")  -> rwx------
220          * stat("/is/unaccessible/") -> EPERM            H.Z.
221          * This is the reason for the StripTrailingSlash
222          */
223         QT_STATBUF buf;
224         const QString path = m_url.adjusted(QUrl::StripTrailingSlash).toLocalFile();
225         const QByteArray pathBA = QFile::encodeName(path);
226         if (QT_LSTAT(pathBA.constData(), &buf) == 0) {
227             m_entry.reserve(9);
228             m_entry.replace(KIO::UDSEntry::UDS_DEVICE_ID, buf.st_dev);
229             m_entry.replace(KIO::UDSEntry::UDS_INODE, buf.st_ino);
230 
231             mode_t mode = buf.st_mode;
232             if ((buf.st_mode & QT_STAT_MASK) == QT_STAT_LNK) {
233                 m_bLink = true;
234                 if (QT_STAT(pathBA.constData(), &buf) == 0) {
235                     mode = buf.st_mode;
236                 } else { // link pointing to nowhere (see FileProtocol::createUDSEntry() in ioslaves/file/file.cpp)
237                     mode = (QT_STAT_MASK - 1) | S_IRWXU | S_IRWXG | S_IRWXO;
238                 }
239             }
240 
241             const mode_t type = mode & QT_STAT_MASK;
242 
243             m_entry.replace(KIO::UDSEntry::UDS_SIZE, buf.st_size);
244             m_entry.replace(KIO::UDSEntry::UDS_FILE_TYPE, type); // extract file type
245             m_entry.replace(KIO::UDSEntry::UDS_ACCESS, mode & 07777); // extract permissions
246             m_entry.replace(KIO::UDSEntry::UDS_MODIFICATION_TIME, buf.st_mtime); // TODO: we could use msecs too...
247             m_entry.replace(KIO::UDSEntry::UDS_ACCESS_TIME, buf.st_atime);
248 #ifndef Q_OS_WIN
249             m_entry.replace(KIO::UDSEntry::UDS_USER, KUser(buf.st_uid).loginName());
250             m_entry.replace(KIO::UDSEntry::UDS_GROUP, KUserGroup(buf.st_gid).name());
251 #endif
252 
253             // TODO: these can be removed, we can use UDS_FILE_TYPE and UDS_ACCESS everywhere
254             if (m_fileMode == KFileItem::Unknown) {
255                 m_fileMode = type; // extract file type
256             }
257             if (m_permissions == KFileItem::Unknown) {
258                 m_permissions = mode & 07777; // extract permissions
259             }
260 
261 #if HAVE_POSIX_ACL
262             if (m_addACL) {
263                 appendACLAtoms(pathBA, m_entry, type);
264             }
265 #endif
266         }
267     }
268 
269     m_bInitCalled = true;
270 }
271 
readUDSEntry(bool _urlIsDirectory)272 void KFileItemPrivate::readUDSEntry(bool _urlIsDirectory)
273 {
274     // extract fields from the KIO::UDS Entry
275 
276     m_fileMode = m_entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE, KFileItem::Unknown);
277     m_permissions = m_entry.numberValue(KIO::UDSEntry::UDS_ACCESS, KFileItem::Unknown);
278     m_strName = m_entry.stringValue(KIO::UDSEntry::UDS_NAME);
279 
280     const QString displayName = m_entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
281     if (!displayName.isEmpty()) {
282         m_strText = displayName;
283     } else {
284         m_strText = KIO::decodeFileName(m_strName);
285     }
286 
287     const QString urlStr = m_entry.stringValue(KIO::UDSEntry::UDS_URL);
288     const bool UDS_URL_seen = !urlStr.isEmpty();
289     if (UDS_URL_seen) {
290         m_url = QUrl(urlStr);
291         if (m_url.isLocalFile()) {
292             m_bIsLocalUrl = true;
293         }
294     }
295     QMimeDatabase db;
296     const QString mimeTypeStr = m_entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE);
297     m_bMimeTypeKnown = !mimeTypeStr.isEmpty();
298     if (m_bMimeTypeKnown) {
299         m_mimeType = db.mimeTypeForName(mimeTypeStr);
300     }
301 
302     m_guessedMimeType = m_entry.stringValue(KIO::UDSEntry::UDS_GUESSED_MIME_TYPE);
303     m_bLink = !m_entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST).isEmpty(); // we don't store the link dest
304 
305     const int hiddenVal = m_entry.numberValue(KIO::UDSEntry::UDS_HIDDEN, -1);
306     m_hidden = hiddenVal == 1 ? Hidden : (hiddenVal == 0 ? Shown : Auto);
307 
308     if (_urlIsDirectory && !UDS_URL_seen && !m_strName.isEmpty() && m_strName != QLatin1String(".")) {
309         m_url.setPath(concatPaths(m_url.path(), m_strName));
310     }
311 
312     m_iconName.clear();
313 }
314 
315 // Inlined because it is used only in one place
size() const316 inline KIO::filesize_t KFileItemPrivate::size() const
317 {
318     ensureInitialized();
319 
320     // Extract it from the KIO::UDSEntry
321     long long fieldVal = m_entry.numberValue(KIO::UDSEntry::UDS_SIZE, -1);
322     if (fieldVal != -1) {
323         return fieldVal;
324     }
325 
326     // If not in the KIO::UDSEntry, or if UDSEntry empty, use stat() [if local URL]
327     if (m_bIsLocalUrl) {
328         return QFileInfo(m_url.toLocalFile()).size();
329     }
330     return 0;
331 }
332 
recursiveSize() const333 KIO::filesize_t KFileItemPrivate::recursiveSize() const
334 {
335     // Extract it from the KIO::UDSEntry
336     long long fieldVal = m_entry.numberValue(KIO::UDSEntry::UDS_RECURSIVE_SIZE, -1);
337     if (fieldVal != -1) {
338         return static_cast<KIO::filesize_t>(fieldVal);
339     }
340 
341     return 0;
342 }
343 
udsFieldForTime(KFileItem::FileTimes mappedWhich)344 static uint udsFieldForTime(KFileItem::FileTimes mappedWhich)
345 {
346     switch (mappedWhich) {
347     case KFileItem::ModificationTime:
348         return KIO::UDSEntry::UDS_MODIFICATION_TIME;
349     case KFileItem::AccessTime:
350         return KIO::UDSEntry::UDS_ACCESS_TIME;
351     case KFileItem::CreationTime:
352         return KIO::UDSEntry::UDS_CREATION_TIME;
353     }
354     return 0;
355 }
356 
setTime(KFileItem::FileTimes mappedWhich,uint time_t_val) const357 void KFileItemPrivate::setTime(KFileItem::FileTimes mappedWhich, uint time_t_val) const
358 {
359     m_entry.replace(udsFieldForTime(mappedWhich), time_t_val);
360 }
361 
setTime(KFileItem::FileTimes mappedWhich,const QDateTime & val) const362 void KFileItemPrivate::setTime(KFileItem::FileTimes mappedWhich, const QDateTime &val) const
363 {
364     const QDateTime dt = val.toLocalTime(); // #160979
365     setTime(mappedWhich, dt.toSecsSinceEpoch());
366 }
367 
time(KFileItem::FileTimes mappedWhich) const368 QDateTime KFileItemPrivate::time(KFileItem::FileTimes mappedWhich) const
369 {
370     ensureInitialized();
371 
372     // Extract it from the KIO::UDSEntry
373     const uint uds = udsFieldForTime(mappedWhich);
374     if (uds > 0) {
375         const long long fieldVal = m_entry.numberValue(uds, -1);
376         if (fieldVal != -1) {
377             return QDateTime::fromMSecsSinceEpoch(1000 * fieldVal);
378         }
379     }
380 
381     return QDateTime();
382 }
383 
printCompareDebug(const KFileItemPrivate & item) const384 void KFileItemPrivate::printCompareDebug(const KFileItemPrivate &item) const
385 {
386     Q_UNUSED(item);
387 
388 #if KFILEITEM_DEBUG
389     const KIO::UDSEntry &otherEntry = item.m_entry;
390 
391     qDebug() << "Comparing" << m_url << "and" << item.m_url;
392     qDebug() << " name" << (m_strName == item.m_strName);
393     qDebug() << " local" << (m_bIsLocalUrl == item.m_bIsLocalUrl);
394 
395     qDebug() << " mode" << (m_fileMode == item.m_fileMode);
396     qDebug() << " perm" << (m_permissions == item.m_permissions);
397     qDebug() << " group" << (m_entry.stringValue(KIO::UDSEntry::UDS_GROUP) == otherEntry.stringValue(KIO::UDSEntry::UDS_GROUP));
398     qDebug() << " user" << (m_entry.stringValue(KIO::UDSEntry::UDS_USER) == otherEntry.stringValue(KIO::UDSEntry::UDS_USER));
399 
400     qDebug() << " UDS_EXTENDED_ACL" << (m_entry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL) == otherEntry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL));
401     qDebug() << " UDS_ACL_STRING" << (m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING) == otherEntry.stringValue(KIO::UDSEntry::UDS_ACL_STRING));
402     qDebug() << " UDS_DEFAULT_ACL_STRING"
403              << (m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING) == otherEntry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING));
404 
405     qDebug() << " m_bLink" << (m_bLink == item.m_bLink);
406     qDebug() << " m_hidden" << (m_hidden == item.m_hidden);
407 
408     qDebug() << " size" << (size() == item.size());
409 
410     qDebug() << " ModificationTime" << m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME)
411              << otherEntry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME);
412 
413     qDebug() << " UDS_ICON_NAME" << (m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME) == otherEntry.stringValue(KIO::UDSEntry::UDS_ICON_NAME));
414 #endif
415 }
416 
417 // Inlined because it is used only in one place
cmp(const KFileItemPrivate & item) const418 inline bool KFileItemPrivate::cmp(const KFileItemPrivate &item) const
419 {
420     if (item.m_bInitCalled) {
421         ensureInitialized();
422     }
423 
424     if (m_bInitCalled) {
425         item.ensureInitialized();
426     }
427 
428 #if KFILEITEM_DEBUG
429     printCompareDebug(item);
430 #endif
431 
432     /* clang-format off */
433     return (m_strName == item.m_strName
434             && m_bIsLocalUrl == item.m_bIsLocalUrl
435             && m_fileMode == item.m_fileMode
436             && m_permissions == item.m_permissions
437             && m_entry.stringValue(KIO::UDSEntry::UDS_GROUP) == item.m_entry.stringValue(KIO::UDSEntry::UDS_GROUP)
438             && m_entry.stringValue(KIO::UDSEntry::UDS_USER) == item.m_entry.stringValue(KIO::UDSEntry::UDS_USER)
439             && m_entry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL) == item.m_entry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL)
440             && m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING) == item.m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING)
441             && m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING) == item.m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING)
442             && m_bLink == item.m_bLink
443             && m_hidden == item.m_hidden
444             && size() == item.size()
445             && m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME) == item.m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME)
446             && m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME) == item.m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME)
447             && m_entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL) == item.m_entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL)
448             && m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH) == item.m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH));
449     /* clang-format on */
450     // Don't compare the MIME types here. They might not be known, and we don't want to
451     // do the slow operation of determining them here.
452 }
453 
454 // Inlined because it is used only in one place
parsePermissions(mode_t perm) const455 inline QString KFileItemPrivate::parsePermissions(mode_t perm) const
456 {
457     ensureInitialized();
458 
459     static char buffer[12];
460 
461     char uxbit;
462     char gxbit;
463     char oxbit;
464 
465     if ((perm & (S_IXUSR | S_ISUID)) == (S_IXUSR | S_ISUID)) {
466         uxbit = 's';
467     } else if ((perm & (S_IXUSR | S_ISUID)) == S_ISUID) {
468         uxbit = 'S';
469     } else if ((perm & (S_IXUSR | S_ISUID)) == S_IXUSR) {
470         uxbit = 'x';
471     } else {
472         uxbit = '-';
473     }
474 
475     if ((perm & (S_IXGRP | S_ISGID)) == (S_IXGRP | S_ISGID)) {
476         gxbit = 's';
477     } else if ((perm & (S_IXGRP | S_ISGID)) == S_ISGID) {
478         gxbit = 'S';
479     } else if ((perm & (S_IXGRP | S_ISGID)) == S_IXGRP) {
480         gxbit = 'x';
481     } else {
482         gxbit = '-';
483     }
484 
485     if ((perm & (S_IXOTH | S_ISVTX)) == (S_IXOTH | S_ISVTX)) {
486         oxbit = 't';
487     } else if ((perm & (S_IXOTH | S_ISVTX)) == S_ISVTX) {
488         oxbit = 'T';
489     } else if ((perm & (S_IXOTH | S_ISVTX)) == S_IXOTH) {
490         oxbit = 'x';
491     } else {
492         oxbit = '-';
493     }
494 
495     // Include the type in the first char like ls does; people are more used to seeing it,
496     // even though it's not really part of the permissions per se.
497     if (m_bLink) {
498         buffer[0] = 'l';
499     } else if (m_fileMode != KFileItem::Unknown) {
500         if ((m_fileMode & QT_STAT_MASK) == QT_STAT_DIR) {
501             buffer[0] = 'd';
502         }
503 #ifdef Q_OS_UNIX
504         else if (S_ISSOCK(m_fileMode)) {
505             buffer[0] = 's';
506         } else if (S_ISCHR(m_fileMode)) {
507             buffer[0] = 'c';
508         } else if (S_ISBLK(m_fileMode)) {
509             buffer[0] = 'b';
510         } else if (S_ISFIFO(m_fileMode)) {
511             buffer[0] = 'p';
512         }
513 #endif // Q_OS_UNIX
514         else {
515             buffer[0] = '-';
516         }
517     } else {
518         buffer[0] = '-';
519     }
520 
521     buffer[1] = (((perm & S_IRUSR) == S_IRUSR) ? 'r' : '-');
522     buffer[2] = (((perm & S_IWUSR) == S_IWUSR) ? 'w' : '-');
523     buffer[3] = uxbit;
524     buffer[4] = (((perm & S_IRGRP) == S_IRGRP) ? 'r' : '-');
525     buffer[5] = (((perm & S_IWGRP) == S_IWGRP) ? 'w' : '-');
526     buffer[6] = gxbit;
527     buffer[7] = (((perm & S_IROTH) == S_IROTH) ? 'r' : '-');
528     buffer[8] = (((perm & S_IWOTH) == S_IWOTH) ? 'w' : '-');
529     buffer[9] = oxbit;
530     // if (hasExtendedACL())
531     if (m_entry.contains(KIO::UDSEntry::UDS_EXTENDED_ACL)) {
532         buffer[10] = '+';
533         buffer[11] = 0;
534     } else {
535         buffer[10] = 0;
536     }
537 
538     return QString::fromLatin1(buffer);
539 }
540 
determineMimeTypeHelper(const QUrl & url) const541 void KFileItemPrivate::determineMimeTypeHelper(const QUrl &url) const
542 {
543     QMimeDatabase db;
544     if (m_bSkipMimeTypeFromContent) {
545         const QString scheme = url.scheme();
546         if (scheme.startsWith(QLatin1String("http")) || scheme == QLatin1String("mailto")) {
547             m_mimeType = db.mimeTypeForName(QLatin1String("application/octet-stream"));
548         } else {
549             m_mimeType = db.mimeTypeForFile(url.path(), QMimeDatabase::MatchMode::MatchExtension);
550         }
551     } else {
552         m_mimeType = db.mimeTypeForUrl(url);
553     }
554 }
555 
556 ///////
557 
KFileItem()558 KFileItem::KFileItem()
559     : d(nullptr)
560 {
561 }
562 
KFileItem(const KIO::UDSEntry & entry,const QUrl & itemOrDirUrl,bool delayedMimeTypes,bool urlIsDirectory)563 KFileItem::KFileItem(const KIO::UDSEntry &entry, const QUrl &itemOrDirUrl, bool delayedMimeTypes, bool urlIsDirectory)
564     : d(new KFileItemPrivate(entry,
565                              KFileItem::Unknown,
566                              KFileItem::Unknown,
567                              itemOrDirUrl,
568                              urlIsDirectory,
569                              delayedMimeTypes,
570                              KFileItem::NormalMimeTypeDetermination))
571 {
572 }
573 
574 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 0)
KFileItem(mode_t mode,mode_t permissions,const QUrl & url,bool delayedMimeTypes)575 KFileItem::KFileItem(mode_t mode, mode_t permissions, const QUrl &url, bool delayedMimeTypes)
576     : d(new KFileItemPrivate(KIO::UDSEntry(), mode, permissions, url, false, delayedMimeTypes, KFileItem::NormalMimeTypeDetermination))
577 {
578 }
579 #endif
580 
KFileItem(const QUrl & url,const QString & mimeType,mode_t mode)581 KFileItem::KFileItem(const QUrl &url, const QString &mimeType, mode_t mode)
582     : d(new KFileItemPrivate(KIO::UDSEntry(), mode, KFileItem::Unknown, url, false, false, KFileItem::NormalMimeTypeDetermination))
583 {
584     d->m_bMimeTypeKnown = !mimeType.isEmpty();
585     if (d->m_bMimeTypeKnown) {
586         QMimeDatabase db;
587         d->m_mimeType = db.mimeTypeForName(mimeType);
588     }
589 }
590 
KFileItem(const QUrl & url,KFileItem::MimeTypeDetermination mimeTypeDetermination)591 KFileItem::KFileItem(const QUrl &url, KFileItem::MimeTypeDetermination mimeTypeDetermination)
592     : d(new KFileItemPrivate(KIO::UDSEntry(), KFileItem::Unknown, KFileItem::Unknown, url, false, false, mimeTypeDetermination))
593 {
594 }
595 
596 // Default implementations for:
597 // - Copy constructor
598 // - Move constructor
599 // - Copy assignment
600 // - Move assignment
601 // - Destructor
602 // The compiler will now generate the content of those.
603 KFileItem::KFileItem(const KFileItem &) = default;
604 KFileItem::~KFileItem() = default;
605 KFileItem::KFileItem(KFileItem &&) = default;
606 KFileItem &KFileItem::operator=(const KFileItem &) = default;
607 KFileItem &KFileItem::operator=(KFileItem &&) = default;
608 
refresh()609 void KFileItem::refresh()
610 {
611     if (!d) {
612         qCWarning(KIO_CORE) << "null item";
613         return;
614     }
615 
616     d->m_fileMode = KFileItem::Unknown;
617     d->m_permissions = KFileItem::Unknown;
618     d->m_hidden = KFileItemPrivate::Auto;
619     refreshMimeType();
620 
621 #if HAVE_POSIX_ACL
622     // If the item had ACL, re-add them in init()
623     d->m_addACL = !d->m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING).isEmpty();
624 #endif
625 
626     // Basically, we can't trust any information we got while listing.
627     // Everything could have changed...
628     // Clearing m_entry makes it possible to detect changes in the size of the file,
629     // the time information, etc.
630     d->m_entry.clear();
631     d->init(); // re-populates d->m_entry
632 }
633 
refreshMimeType()634 void KFileItem::refreshMimeType()
635 {
636     if (!d) {
637         return;
638     }
639 
640     d->m_mimeType = QMimeType();
641     d->m_bMimeTypeKnown = false;
642     d->m_iconName.clear();
643 }
644 
setDelayedMimeTypes(bool b)645 void KFileItem::setDelayedMimeTypes(bool b)
646 {
647     if (!d) {
648         return;
649     }
650     d->m_delayedMimeTypes = b;
651 }
652 
setUrl(const QUrl & url)653 void KFileItem::setUrl(const QUrl &url)
654 {
655     if (!d) {
656         qCWarning(KIO_CORE) << "null item";
657         return;
658     }
659 
660     d->m_url = url;
661     setName(url.fileName());
662 }
663 
setLocalPath(const QString & path)664 void KFileItem::setLocalPath(const QString &path)
665 {
666     if (!d) {
667         qCWarning(KIO_CORE) << "null item";
668         return;
669     }
670 
671     d->m_entry.replace(KIO::UDSEntry::UDS_LOCAL_PATH, path);
672 }
673 
setName(const QString & name)674 void KFileItem::setName(const QString &name)
675 {
676     if (!d) {
677         qCWarning(KIO_CORE) << "null item";
678         return;
679     }
680 
681     d->ensureInitialized();
682 
683     d->m_strName = name;
684     if (!d->m_strName.isEmpty()) {
685         d->m_strText = KIO::decodeFileName(d->m_strName);
686     }
687     if (d->m_entry.contains(KIO::UDSEntry::UDS_NAME)) {
688         d->m_entry.replace(KIO::UDSEntry::UDS_NAME, d->m_strName); // #195385
689     }
690 }
691 
linkDest() const692 QString KFileItem::linkDest() const
693 {
694     if (!d) {
695         return QString();
696     }
697 
698     d->ensureInitialized();
699 
700     // Extract it from the KIO::UDSEntry
701     const QString linkStr = d->m_entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
702     if (!linkStr.isEmpty()) {
703         return linkStr;
704     }
705 
706     // If not in the KIO::UDSEntry, or if UDSEntry empty, use readlink() [if local URL]
707     if (d->m_bIsLocalUrl) {
708         return QFile::symLinkTarget(d->m_url.adjusted(QUrl::StripTrailingSlash).toLocalFile());
709     }
710     return QString();
711 }
712 
localPath() const713 QString KFileItemPrivate::localPath() const
714 {
715     if (m_bIsLocalUrl) {
716         return m_url.toLocalFile();
717     }
718 
719     ensureInitialized();
720 
721     // Extract the local path from the KIO::UDSEntry
722     return m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
723 }
724 
localPath() const725 QString KFileItem::localPath() const
726 {
727     if (!d) {
728         return QString();
729     }
730 
731     return d->localPath();
732 }
733 
size() const734 KIO::filesize_t KFileItem::size() const
735 {
736     if (!d) {
737         return 0;
738     }
739 
740     return d->size();
741 }
742 
recursiveSize() const743 KIO::filesize_t KFileItem::recursiveSize() const
744 {
745     if (!d) {
746         return 0;
747     }
748 
749     return d->recursiveSize();
750 }
751 
hasExtendedACL() const752 bool KFileItem::hasExtendedACL() const
753 {
754     if (!d) {
755         return false;
756     }
757 
758     // Check if the field exists; its value doesn't matter
759     return entry().contains(KIO::UDSEntry::UDS_EXTENDED_ACL);
760 }
761 
ACL() const762 KACL KFileItem::ACL() const
763 {
764     if (!d) {
765         return KACL();
766     }
767 
768     if (hasExtendedACL()) {
769         // Extract it from the KIO::UDSEntry
770         const QString fieldVal = d->m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING);
771         if (!fieldVal.isEmpty()) {
772             return KACL(fieldVal);
773         }
774     }
775 
776     // create one from the basic permissions
777     return KACL(d->m_permissions);
778 }
779 
defaultACL() const780 KACL KFileItem::defaultACL() const
781 {
782     if (!d) {
783         return KACL();
784     }
785 
786     // Extract it from the KIO::UDSEntry
787     const QString fieldVal = entry().stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING);
788     if (!fieldVal.isEmpty()) {
789         return KACL(fieldVal);
790     } else {
791         return KACL();
792     }
793 }
794 
time(FileTimes which) const795 QDateTime KFileItem::time(FileTimes which) const
796 {
797     if (!d) {
798         return QDateTime();
799     }
800 
801     return d->time(which);
802 }
803 
user() const804 QString KFileItem::user() const
805 {
806     if (!d) {
807         return QString();
808     }
809 
810     return entry().stringValue(KIO::UDSEntry::UDS_USER);
811 }
812 
group() const813 QString KFileItem::group() const
814 {
815     if (!d) {
816         return QString();
817     }
818 
819     return entry().stringValue(KIO::UDSEntry::UDS_GROUP);
820 }
821 
isSlow() const822 bool KFileItemPrivate::isSlow() const
823 {
824     if (m_slow == SlowUnknown) {
825         const QString path = localPath();
826         if (!path.isEmpty()) {
827             const KFileSystemType::Type fsType = KFileSystemType::fileSystemType(path);
828             m_slow = (fsType == KFileSystemType::Nfs || fsType == KFileSystemType::Smb) ? Slow : Fast;
829         } else {
830             m_slow = Slow;
831         }
832     }
833     return m_slow == Slow;
834 }
835 
isSlow() const836 bool KFileItem::isSlow() const
837 {
838     if (!d) {
839         return false;
840     }
841 
842     return d->isSlow();
843 }
844 
mimetype() const845 QString KFileItem::mimetype() const
846 {
847     if (!d) {
848         return QString();
849     }
850 
851     KFileItem *that = const_cast<KFileItem *>(this);
852     return that->determineMimeType().name();
853 }
854 
determineMimeType() const855 QMimeType KFileItem::determineMimeType() const
856 {
857     if (!d) {
858         return QMimeType();
859     }
860 
861     if (!d->m_mimeType.isValid() || !d->m_bMimeTypeKnown) {
862         QMimeDatabase db;
863         if (isDir()) {
864             d->m_mimeType = db.mimeTypeForName(QStringLiteral("inode/directory"));
865         } else {
866             bool isLocalUrl;
867             const QUrl url = mostLocalUrl(&isLocalUrl);
868             d->determineMimeTypeHelper(url);
869 
870             // was:  d->m_mimeType = KMimeType::findByUrl( url, d->m_fileMode, isLocalUrl );
871             // => we are no longer using d->m_fileMode for remote URLs.
872             Q_ASSERT(d->m_mimeType.isValid());
873             // qDebug() << d << "finding final MIME type for" << url << ":" << d->m_mimeType.name();
874         }
875         d->m_bMimeTypeKnown = true;
876     }
877 
878     if (d->m_delayedMimeTypes) { // if we delayed getting the iconName up till now, this is the right point in time to do so
879         d->m_delayedMimeTypes = false;
880         d->m_useIconNameCache = false;
881         (void)iconName();
882     }
883 
884     return d->m_mimeType;
885 }
886 
isMimeTypeKnown() const887 bool KFileItem::isMimeTypeKnown() const
888 {
889     if (!d) {
890         return false;
891     }
892 
893     // The MIME type isn't known if determineMimeType was never called (on-demand determination)
894     // or if this fileitem has a guessed MIME type (e.g. ftp symlink) - in which case
895     // it always remains "not fully determined"
896     return d->m_bMimeTypeKnown && d->m_guessedMimeType.isEmpty();
897 }
898 
isDirectoryMounted(const QUrl & url)899 static bool isDirectoryMounted(const QUrl &url)
900 {
901     // Stating .directory files can cause long freezes when e.g. /home
902     // uses autofs for every user's home directory, i.e. opening /home
903     // in a file dialog will mount every single home directory.
904     // These non-mounted directories can be identified by having 0 size.
905     // There are also other directories with 0 size, such as /proc, that may
906     // be mounted, but those are unlikely to contain .directory (and checking
907     // this would require checking with KMountPoint).
908 
909     // TODO: maybe this could be checked with KFileSystemType instead?
910     QFileInfo info(url.toLocalFile());
911     if (info.isDir() && info.size() == 0) {
912         return false;
913     }
914     return true;
915 }
916 
isFinalIconKnown() const917 bool KFileItem::isFinalIconKnown() const
918 {
919     if (!d) {
920         return false;
921     }
922     return d->m_bMimeTypeKnown && (!d->m_delayedMimeTypes);
923 }
924 
925 // KDE5 TODO: merge with comment()? Need to see what lxr says about the usage of both.
mimeComment() const926 QString KFileItem::mimeComment() const
927 {
928     if (!d) {
929         return QString();
930     }
931 
932     const QString displayType = d->m_entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_TYPE);
933     if (!displayType.isEmpty()) {
934         return displayType;
935     }
936 
937     bool isLocalUrl;
938     QUrl url = mostLocalUrl(&isLocalUrl);
939 
940     QMimeType mime = currentMimeType();
941     // This cannot move to kio_file (with UDS_DISPLAY_TYPE) because it needs
942     // the MIME type to be determined, which is done here, and possibly delayed...
943     if (isLocalUrl && !d->isSlow() && mime.inherits(QStringLiteral("application/x-desktop"))) {
944         KDesktopFile cfg(url.toLocalFile());
945         QString comment = cfg.desktopGroup().readEntry("Comment");
946         if (!comment.isEmpty()) {
947             return comment;
948         }
949     }
950 
951     // Support for .directory file in directories
952     if (isLocalUrl && isDir() && !d->isSlow() && isDirectoryMounted(url)) {
953         QUrl u(url);
954         u.setPath(concatPaths(u.path(), QStringLiteral(".directory")));
955         const KDesktopFile cfg(u.toLocalFile());
956         const QString comment = cfg.readComment();
957         if (!comment.isEmpty()) {
958             return comment;
959         }
960     }
961 
962     const QString comment = mime.comment();
963     // qDebug() << "finding comment for " << url.url() << " : " << d->m_mimeType->name();
964     if (!comment.isEmpty()) {
965         return comment;
966     } else {
967         return mime.name();
968     }
969 }
970 
iconFromDirectoryFile(const QString & path)971 static QString iconFromDirectoryFile(const QString &path)
972 {
973     const QString filePath = path + QLatin1String("/.directory");
974     if (!QFileInfo(filePath).isFile()) { // exists -and- is a file
975         return QString();
976     }
977 
978     KDesktopFile cfg(filePath);
979     QString icon = cfg.readIcon();
980 
981     const KConfigGroup group = cfg.desktopGroup();
982     const QString emptyIcon = group.readEntry("EmptyIcon");
983     if (!emptyIcon.isEmpty()) {
984         bool isDirEmpty = true;
985         QDirIterator dirIt(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
986         while (dirIt.hasNext()) {
987             dirIt.next();
988             if (dirIt.fileName() != QLatin1String(".directory")) {
989                 isDirEmpty = false;
990                 break;
991             }
992         }
993         if (isDirEmpty) {
994             icon = emptyIcon;
995         }
996     }
997 
998     if (icon.startsWith(QLatin1String("./"))) {
999         // path is relative with respect to the location of the .directory file (#73463)
1000         return path + QStringView(icon).mid(1);
1001     }
1002     return icon;
1003 }
1004 
iconFromDesktopFile(const QString & path)1005 static QString iconFromDesktopFile(const QString &path)
1006 {
1007     KDesktopFile cfg(path);
1008     const QString icon = cfg.readIcon();
1009     if (cfg.hasLinkType()) {
1010         const KConfigGroup group = cfg.desktopGroup();
1011         const QString emptyIcon = group.readEntry("EmptyIcon");
1012         if (!emptyIcon.isEmpty()) {
1013             const QString u = cfg.readUrl();
1014             const QUrl url(u);
1015             if (url.scheme() == QLatin1String("trash")) {
1016                 // We need to find if the trash is empty, preferably without using a KIO job.
1017                 // So instead kio_trash leaves an entry in its config file for us.
1018                 KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig);
1019                 if (trashConfig.group("Status").readEntry("Empty", true)) {
1020                     return emptyIcon;
1021                 }
1022             }
1023         }
1024     }
1025     return icon;
1026 }
1027 
iconName() const1028 QString KFileItem::iconName() const
1029 {
1030     if (!d) {
1031         return QString();
1032     }
1033 
1034     if (d->m_useIconNameCache && !d->m_iconName.isEmpty()) {
1035         return d->m_iconName;
1036     }
1037 
1038     d->m_iconName = d->m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME);
1039     if (!d->m_iconName.isEmpty()) {
1040         d->m_useIconNameCache = d->m_bMimeTypeKnown;
1041         return d->m_iconName;
1042     }
1043 
1044     bool isLocalUrl;
1045     QUrl url = mostLocalUrl(&isLocalUrl);
1046 
1047     QMimeDatabase db;
1048     QMimeType mime;
1049     // Use guessed MIME type for the icon
1050     if (!d->m_guessedMimeType.isEmpty()) {
1051         mime = db.mimeTypeForName(d->m_guessedMimeType);
1052     } else {
1053         mime = currentMimeType();
1054     }
1055 
1056     const bool delaySlowOperations = d->m_delayedMimeTypes;
1057 
1058     if (isLocalUrl && !delaySlowOperations && mime.inherits(QStringLiteral("application/x-desktop"))) {
1059         d->m_iconName = iconFromDesktopFile(url.toLocalFile());
1060         if (!d->m_iconName.isEmpty()) {
1061             d->m_useIconNameCache = d->m_bMimeTypeKnown;
1062             return d->m_iconName;
1063         }
1064     }
1065 
1066     if (isLocalUrl && !delaySlowOperations && isDir()) {
1067         if (isDirectoryMounted(url)) {
1068             d->m_iconName = iconFromDirectoryFile(url.toLocalFile());
1069             if (!d->m_iconName.isEmpty()) {
1070                 d->m_useIconNameCache = d->m_bMimeTypeKnown;
1071                 return d->m_iconName;
1072             }
1073         }
1074 
1075         d->m_iconName = KIOPrivate::iconForStandardPath(url.toLocalFile());
1076         if (!d->m_iconName.isEmpty()) {
1077             d->m_useIconNameCache = d->m_bMimeTypeKnown;
1078             return d->m_iconName;
1079         }
1080     }
1081 
1082     d->m_iconName = mime.iconName();
1083     d->m_useIconNameCache = d->m_bMimeTypeKnown;
1084     return d->m_iconName;
1085 }
1086 
1087 /**
1088  * Returns true if this is a desktop file.
1089  * MIME type determination is optional.
1090  */
checkDesktopFile(const KFileItem & item,bool _determineMimeType)1091 static bool checkDesktopFile(const KFileItem &item, bool _determineMimeType)
1092 {
1093     // only local files
1094     bool isLocalUrl;
1095     item.mostLocalUrl(&isLocalUrl);
1096     if (!isLocalUrl) {
1097         return false;
1098     }
1099 
1100     // only regular files
1101     if (!item.isRegularFile()) {
1102         return false;
1103     }
1104 
1105     // only if readable
1106     if (!item.isReadable()) {
1107         return false;
1108     }
1109 
1110     // return true if desktop file
1111     QMimeType mime = _determineMimeType ? item.determineMimeType() : item.currentMimeType();
1112     return mime.inherits(QStringLiteral("application/x-desktop"));
1113 }
1114 
overlays() const1115 QStringList KFileItem::overlays() const
1116 {
1117     if (!d) {
1118         return QStringList();
1119     }
1120 
1121     d->ensureInitialized();
1122 
1123     QStringList names = d->m_entry.stringValue(KIO::UDSEntry::UDS_ICON_OVERLAY_NAMES).split(QLatin1Char(','), Qt::SkipEmptyParts);
1124 
1125     if (d->m_bLink) {
1126         names.append(QStringLiteral("emblem-symbolic-link"));
1127     }
1128 
1129     if (!isReadable()) {
1130         names.append(QStringLiteral("emblem-locked"));
1131     }
1132 
1133     if (checkDesktopFile(*this, false)) {
1134         KDesktopFile cfg(localPath());
1135         const KConfigGroup group = cfg.desktopGroup();
1136 
1137         // Add a warning emblem if this is an executable desktop file
1138         // which is untrusted.
1139         if (group.hasKey("Exec") && !KDesktopFile::isAuthorizedDesktopFile(localPath())) {
1140             names.append(QStringLiteral("emblem-important"));
1141         }
1142 
1143 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 82)
1144         if (cfg.hasDeviceType()) {
1145             QT_WARNING_PUSH
1146             QT_WARNING_DISABLE_DEPRECATED
1147             const QString dev = cfg.readDevice();
1148             QT_WARNING_POP
1149             if (!dev.isEmpty()) {
1150                 KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByDevice(dev);
1151                 if (mountPoint) { // mounted?
1152                     names.append(QStringLiteral("emblem-mounted"));
1153                 }
1154             }
1155         }
1156 #endif
1157     }
1158 
1159     if (isHidden()) {
1160         names.append(QStringLiteral("hidden"));
1161     }
1162 #ifndef Q_OS_WIN
1163     if (isDir()) {
1164         bool isLocalUrl;
1165         const QUrl url = mostLocalUrl(&isLocalUrl);
1166         if (isLocalUrl) {
1167             const QString path = url.toLocalFile();
1168             if (KSambaShare::instance()->isDirectoryShared(path) || KNFSShare::instance()->isDirectoryShared(path)) {
1169                 names.append(QStringLiteral("emblem-shared"));
1170             }
1171         }
1172     }
1173 #endif // Q_OS_WIN
1174 
1175     return names;
1176 }
1177 
comment() const1178 QString KFileItem::comment() const
1179 {
1180     if (!d) {
1181         return QString();
1182     }
1183 
1184     return d->m_entry.stringValue(KIO::UDSEntry::UDS_COMMENT);
1185 }
1186 
isReadable() const1187 bool KFileItem::isReadable() const
1188 {
1189     if (!d) {
1190         return false;
1191     }
1192 
1193     d->ensureInitialized();
1194 
1195     /*
1196       struct passwd * user = getpwuid( geteuid() );
1197       bool isMyFile = (QString::fromLocal8Bit(user->pw_name) == d->m_user);
1198       // This gets ugly for the group....
1199       // Maybe we want a static QString for the user and a static QStringList
1200       // for the groups... then we need to handle the deletion properly...
1201       */
1202 
1203     if (d->m_permissions != KFileItem::Unknown) {
1204         const mode_t readMask = S_IRUSR | S_IRGRP | S_IROTH;
1205         // No read permission at all
1206         if ((d->m_permissions & readMask) == 0) {
1207             return false;
1208         }
1209 
1210         // Read permissions for all: save a stat call
1211         if ((d->m_permissions & readMask) == readMask) {
1212             return true;
1213         }
1214     }
1215 
1216     // Or if we can't read it - not network transparent
1217     if (d->m_bIsLocalUrl && !QFileInfo(d->m_url.toLocalFile()).isReadable()) {
1218         return false;
1219     }
1220 
1221     return true;
1222 }
1223 
isWritable() const1224 bool KFileItem::isWritable() const
1225 {
1226     if (!d) {
1227         return false;
1228     }
1229 
1230     d->ensureInitialized();
1231 
1232     /*
1233       struct passwd * user = getpwuid( geteuid() );
1234       bool isMyFile = (QString::fromLocal8Bit(user->pw_name) == d->m_user);
1235       // This gets ugly for the group....
1236       // Maybe we want a static QString for the user and a static QStringList
1237       // for the groups... then we need to handle the deletion properly...
1238       */
1239 
1240     if (d->m_permissions != KFileItem::Unknown) {
1241         // No write permission at all
1242         if (!(S_IWUSR & d->m_permissions) && !(S_IWGRP & d->m_permissions) && !(S_IWOTH & d->m_permissions)) {
1243             return false;
1244         }
1245     }
1246 
1247     // Or if we can't write it - not network transparent
1248     if (d->m_bIsLocalUrl) {
1249         return QFileInfo(d->m_url.toLocalFile()).isWritable();
1250     } else {
1251         return KProtocolManager::supportsWriting(d->m_url);
1252     }
1253 }
1254 
isHidden() const1255 bool KFileItem::isHidden() const
1256 {
1257     if (!d) {
1258         return false;
1259     }
1260 
1261     // The kioslave can specify explicitly that a file is hidden or shown
1262     if (d->m_hidden != KFileItemPrivate::Auto) {
1263         return d->m_hidden == KFileItemPrivate::Hidden;
1264     }
1265 
1266     // Prefer the filename that is part of the URL, in case the display name is different.
1267     QString fileName = d->m_url.fileName();
1268     if (fileName.isEmpty()) { // e.g. "trash:/"
1269         fileName = d->m_strName;
1270     }
1271     return fileName.length() > 1 && fileName[0] == QLatin1Char('.'); // Just "." is current directory, not hidden.
1272 }
1273 
setHidden()1274 void KFileItem::setHidden()
1275 {
1276     if (d) {
1277         d->m_hidden = KFileItemPrivate::Hidden;
1278     }
1279 }
1280 
isDir() const1281 bool KFileItem::isDir() const
1282 {
1283     if (!d) {
1284         return false;
1285     }
1286 
1287     if (d->m_bSkipMimeTypeFromContent) {
1288         return false;
1289     }
1290 
1291     d->ensureInitialized();
1292 
1293     if (d->m_fileMode == KFileItem::Unknown) {
1294         // Probably the file was deleted already, and KDirLister hasn't told the world yet.
1295         // qDebug() << d << url() << "can't say -> false";
1296         return false; // can't say for sure, so no
1297     }
1298     return (d->m_fileMode & QT_STAT_MASK) == QT_STAT_DIR;
1299 }
1300 
isFile() const1301 bool KFileItem::isFile() const
1302 {
1303     if (!d) {
1304         return false;
1305     }
1306 
1307     return !isDir();
1308 }
1309 
1310 #if KIOCORE_BUILD_DEPRECATED_SINCE(4, 0)
acceptsDrops() const1311 bool KFileItem::acceptsDrops() const
1312 {
1313     // A directory ?
1314     if (isDir()) {
1315         return isWritable();
1316     }
1317 
1318     // But only local .desktop files and executables
1319     if (!d->m_bIsLocalUrl) {
1320         return false;
1321     }
1322 
1323     if (mimetype() == QLatin1String("application/x-desktop")) {
1324         return true;
1325     }
1326 
1327     // Executable, shell script ... ?
1328     if (QFileInfo(d->m_url.toLocalFile()).isExecutable()) {
1329         return true;
1330     }
1331 
1332     return false;
1333 }
1334 #endif
1335 
getStatusBarInfo() const1336 QString KFileItem::getStatusBarInfo() const
1337 {
1338     if (!d) {
1339         return QString();
1340     }
1341 
1342     QString text = d->m_strText;
1343     const QString comment = mimeComment();
1344 
1345     if (d->m_bLink) {
1346         text += QLatin1Char(' ');
1347         if (comment.isEmpty()) {
1348             text += i18n("(Symbolic Link to %1)", linkDest());
1349         } else {
1350             text += i18n("(%1, Link to %2)", comment, linkDest());
1351         }
1352     } else if (targetUrl() != url()) {
1353         text += i18n(" (Points to %1)", targetUrl().toDisplayString());
1354     } else if ((d->m_fileMode & QT_STAT_MASK) == QT_STAT_REG) {
1355         text += QStringLiteral(" (%1, %2)").arg(comment, KIO::convertSize(size()));
1356     } else {
1357         text += QStringLiteral(" (%1)").arg(comment);
1358     }
1359     return text;
1360 }
1361 
cmp(const KFileItem & item) const1362 bool KFileItem::cmp(const KFileItem &item) const
1363 {
1364     if (!d && !item.d) {
1365         return true;
1366     }
1367 
1368     if (!d || !item.d) {
1369         return false;
1370     }
1371 
1372     return d->cmp(*item.d);
1373 }
1374 
operator ==(const KFileItem & other) const1375 bool KFileItem::operator==(const KFileItem &other) const
1376 {
1377     if (!d && !other.d) {
1378         return true;
1379     }
1380 
1381     if (!d || !other.d) {
1382         return false;
1383     }
1384 
1385     return d->m_url == other.d->m_url;
1386 }
1387 
operator !=(const KFileItem & other) const1388 bool KFileItem::operator!=(const KFileItem &other) const
1389 {
1390     return !operator==(other);
1391 }
1392 
operator <(const KFileItem & other) const1393 bool KFileItem::operator<(const KFileItem &other) const
1394 {
1395     if (!other.d) {
1396         return false;
1397     }
1398     if (!d) {
1399         return other.d->m_url.isValid();
1400     }
1401     return d->m_url < other.d->m_url;
1402 }
1403 
operator <(const QUrl & other) const1404 bool KFileItem::operator<(const QUrl &other) const
1405 {
1406     if (!d) {
1407         return other.isValid();
1408     }
1409     return d->m_url < other;
1410 }
1411 
operator QVariant() const1412 KFileItem::operator QVariant() const
1413 {
1414     return QVariant::fromValue(*this);
1415 }
1416 
permissionsString() const1417 QString KFileItem::permissionsString() const
1418 {
1419     if (!d) {
1420         return QString();
1421     }
1422 
1423     d->ensureInitialized();
1424 
1425     if (d->m_access.isNull() && d->m_permissions != KFileItem::Unknown) {
1426         d->m_access = d->parsePermissions(d->m_permissions);
1427     }
1428 
1429     return d->m_access;
1430 }
1431 
1432 // check if we need to cache this
timeString(FileTimes which) const1433 QString KFileItem::timeString(FileTimes which) const
1434 {
1435     if (!d) {
1436         return QString();
1437     }
1438 
1439     return QLocale::system().toString(d->time(which), QLocale::LongFormat);
1440 }
1441 
1442 #if KIOCORE_BUILD_DEPRECATED_SINCE(4, 0)
timeString(unsigned int which) const1443 QString KFileItem::timeString(unsigned int which) const
1444 {
1445     if (!d) {
1446         return QString();
1447     }
1448 
1449     switch (which) {
1450     case KIO::UDSEntry::UDS_ACCESS_TIME:
1451         return timeString(AccessTime);
1452     case KIO::UDSEntry::UDS_CREATION_TIME:
1453         return timeString(CreationTime);
1454     case KIO::UDSEntry::UDS_MODIFICATION_TIME:
1455     default:
1456         return timeString(ModificationTime);
1457     }
1458 }
1459 #endif
1460 
1461 #if KIOCORE_BUILD_DEPRECATED_SINCE(4, 0)
assign(const KFileItem & item)1462 void KFileItem::assign(const KFileItem &item)
1463 {
1464     *this = item;
1465 }
1466 #endif
1467 
mostLocalUrl(bool * local) const1468 QUrl KFileItem::mostLocalUrl(bool *local) const
1469 {
1470     if (!d) {
1471         return QUrl();
1472     }
1473 
1474     const QString local_path = localPath();
1475     if (!local_path.isEmpty()) {
1476         if (local) {
1477             *local = true;
1478         }
1479         return QUrl::fromLocalFile(local_path);
1480     } else {
1481         if (local) {
1482             *local = d->m_bIsLocalUrl;
1483         }
1484         return d->m_url;
1485     }
1486 }
1487 
isMostLocalUrl() const1488 KFileItem::MostLocalUrlResult KFileItem::isMostLocalUrl() const
1489 {
1490     if (!d) {
1491         return {QUrl(), false};
1492     }
1493 
1494     const QString local_path = localPath();
1495     if (!local_path.isEmpty()) {
1496         return {QUrl::fromLocalFile(local_path), true};
1497     } else {
1498         return {d->m_url, d->m_bIsLocalUrl};
1499     }
1500 }
1501 
operator <<(QDataStream & s,const KFileItem & a)1502 QDataStream &operator<<(QDataStream &s, const KFileItem &a)
1503 {
1504     if (a.d) {
1505         // We don't need to save/restore anything that refresh() invalidates,
1506         // since that means we can re-determine those by ourselves.
1507         s << a.d->m_url;
1508         s << a.d->m_strName;
1509         s << a.d->m_strText;
1510     } else {
1511         s << QUrl();
1512         s << QString();
1513         s << QString();
1514     }
1515 
1516     return s;
1517 }
1518 
operator >>(QDataStream & s,KFileItem & a)1519 QDataStream &operator>>(QDataStream &s, KFileItem &a)
1520 {
1521     QUrl url;
1522     QString strName;
1523     QString strText;
1524 
1525     s >> url;
1526     s >> strName;
1527     s >> strText;
1528 
1529     if (!a.d) {
1530         qCWarning(KIO_CORE) << "null item";
1531         return s;
1532     }
1533 
1534     if (url.isEmpty()) {
1535         a.d = nullptr;
1536         return s;
1537     }
1538 
1539     a.d->m_url = url;
1540     a.d->m_strName = strName;
1541     a.d->m_strText = strText;
1542     a.d->m_bIsLocalUrl = a.d->m_url.isLocalFile();
1543     a.d->m_bMimeTypeKnown = false;
1544     a.refresh();
1545 
1546     return s;
1547 }
1548 
url() const1549 QUrl KFileItem::url() const
1550 {
1551     if (!d) {
1552         return QUrl();
1553     }
1554 
1555     return d->m_url;
1556 }
1557 
permissions() const1558 mode_t KFileItem::permissions() const
1559 {
1560     if (!d) {
1561         return 0;
1562     }
1563 
1564     d->ensureInitialized();
1565 
1566     return d->m_permissions;
1567 }
1568 
mode() const1569 mode_t KFileItem::mode() const
1570 {
1571     if (!d) {
1572         return 0;
1573     }
1574 
1575     d->ensureInitialized();
1576 
1577     return d->m_fileMode;
1578 }
1579 
isLink() const1580 bool KFileItem::isLink() const
1581 {
1582     if (!d) {
1583         return false;
1584     }
1585 
1586     d->ensureInitialized();
1587 
1588     return d->m_bLink;
1589 }
1590 
isLocalFile() const1591 bool KFileItem::isLocalFile() const
1592 {
1593     if (!d) {
1594         return false;
1595     }
1596 
1597     return d->m_bIsLocalUrl;
1598 }
1599 
text() const1600 QString KFileItem::text() const
1601 {
1602     if (!d) {
1603         return QString();
1604     }
1605 
1606     return d->m_strText;
1607 }
1608 
name(bool lowerCase) const1609 QString KFileItem::name(bool lowerCase) const
1610 {
1611     if (!d) {
1612         return QString();
1613     }
1614 
1615     if (!lowerCase) {
1616         return d->m_strName;
1617     } else if (d->m_strLowerCaseName.isNull()) {
1618         d->m_strLowerCaseName = d->m_strName.toLower();
1619     }
1620     return d->m_strLowerCaseName;
1621 }
1622 
targetUrl() const1623 QUrl KFileItem::targetUrl() const
1624 {
1625     if (!d) {
1626         return QUrl();
1627     }
1628 
1629     const QString targetUrlStr = d->m_entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL);
1630     if (!targetUrlStr.isEmpty()) {
1631         return QUrl(targetUrlStr);
1632     } else {
1633         return url();
1634     }
1635 }
1636 
1637 /*
1638  * MIME type handling.
1639  *
1640  * Initial state: m_mimeType = QMimeType().
1641  * When currentMimeType() is called first: fast MIME type determination,
1642  *   might either find an accurate MIME type (-> Final state), otherwise we
1643  *   set m_mimeType but not m_bMimeTypeKnown (-> Intermediate state)
1644  * Intermediate state: determineMimeType() does the real determination -> Final state.
1645  *
1646  * If delayedMimeTypes isn't set, then we always go to the Final state directly.
1647  */
1648 
currentMimeType() const1649 QMimeType KFileItem::currentMimeType() const
1650 {
1651     if (!d || d->m_url.isEmpty()) {
1652         return QMimeType();
1653     }
1654 
1655     if (!d->m_mimeType.isValid()) {
1656         // On-demand fast (but not always accurate) MIME type determination
1657         QMimeDatabase db;
1658         if (isDir()) {
1659             d->m_mimeType = db.mimeTypeForName(QStringLiteral("inode/directory"));
1660             return d->m_mimeType;
1661         }
1662         const QUrl url = mostLocalUrl();
1663         if (d->m_delayedMimeTypes) {
1664             const QList<QMimeType> mimeTypes = db.mimeTypesForFileName(url.path());
1665             if (mimeTypes.isEmpty()) {
1666                 d->m_mimeType = db.mimeTypeForName(QStringLiteral("application/octet-stream"));
1667                 d->m_bMimeTypeKnown = false;
1668             } else {
1669                 d->m_mimeType = mimeTypes.first();
1670                 // If there were conflicting globs. determineMimeType will be able to do better.
1671                 d->m_bMimeTypeKnown = (mimeTypes.count() == 1);
1672             }
1673         } else {
1674             // ## d->m_fileMode isn't used anymore (for remote urls)
1675             d->determineMimeTypeHelper(url);
1676             d->m_bMimeTypeKnown = true;
1677         }
1678     }
1679     return d->m_mimeType;
1680 }
1681 
entry() const1682 KIO::UDSEntry KFileItem::entry() const
1683 {
1684     if (!d) {
1685         return KIO::UDSEntry();
1686     }
1687 
1688     d->ensureInitialized();
1689 
1690     return d->m_entry;
1691 }
1692 
isNull() const1693 bool KFileItem::isNull() const
1694 {
1695     return d == nullptr;
1696 }
1697 
KFileItemList()1698 KFileItemList::KFileItemList()
1699 {
1700 }
1701 
KFileItemList(const QList<KFileItem> & items)1702 KFileItemList::KFileItemList(const QList<KFileItem> &items)
1703     : QList<KFileItem>(items)
1704 {
1705 }
1706 
KFileItemList(std::initializer_list<KFileItem> items)1707 KFileItemList::KFileItemList(std::initializer_list<KFileItem> items)
1708     : QList<KFileItem>(items)
1709 {
1710 }
1711 
findByName(const QString & fileName) const1712 KFileItem KFileItemList::findByName(const QString &fileName) const
1713 {
1714     auto it = std::find_if(cbegin(), cend(), [&fileName](const KFileItem &item) {
1715         return item.name() == fileName;
1716     });
1717 
1718     return it != cend() ? *it : KFileItem();
1719 }
1720 
findByUrl(const QUrl & url) const1721 KFileItem KFileItemList::findByUrl(const QUrl &url) const
1722 {
1723     auto it = std::find_if(cbegin(), cend(), [&url](const KFileItem &item) {
1724         return item.url() == url;
1725     });
1726 
1727     return it != cend() ? *it : KFileItem();
1728 }
1729 
urlList() const1730 QList<QUrl> KFileItemList::urlList() const
1731 {
1732     QList<QUrl> lst;
1733     lst.reserve(size());
1734 
1735     for (const auto &item : *this) {
1736         lst.append(item.url());
1737     }
1738     return lst;
1739 }
1740 
targetUrlList() const1741 QList<QUrl> KFileItemList::targetUrlList() const
1742 {
1743     QList<QUrl> lst;
1744     lst.reserve(size());
1745 
1746     for (const auto &item : *this) {
1747         lst.append(item.targetUrl());
1748     }
1749     return lst;
1750 }
1751 
isDesktopFile() const1752 bool KFileItem::isDesktopFile() const
1753 {
1754     return checkDesktopFile(*this, true);
1755 }
1756 
isRegularFile() const1757 bool KFileItem::isRegularFile() const
1758 {
1759     if (!d) {
1760         return false;
1761     }
1762 
1763     d->ensureInitialized();
1764 
1765     return (d->m_fileMode & QT_STAT_MASK) == QT_STAT_REG;
1766 }
1767 
operator <<(QDebug stream,const KFileItem & item)1768 QDebug operator<<(QDebug stream, const KFileItem &item)
1769 {
1770     QDebugStateSaver saver(stream);
1771     stream.nospace();
1772     if (item.isNull()) {
1773         stream << "[null KFileItem]";
1774     } else {
1775         stream << "[KFileItem for " << item.url() << "]";
1776     }
1777     return stream;
1778 }
1779