File indexing completed on 2024-04-14 03:52:56

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 1999-2011 David Faure <faure@kde.org>
0004     SPDX-FileCopyrightText: 2001 Carsten Pfeiffer <pfeiffer@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "kfileitem.h"
0010 
0011 #include "config-kiocore.h"
0012 
0013 #if HAVE_POSIX_ACL
0014 #include "../aclhelpers_p.h"
0015 #endif
0016 
0017 #include "../utils_p.h"
0018 #include "kiocoredebug.h"
0019 #include "kioglobal_p.h"
0020 
0021 #include <QDataStream>
0022 #include <QDate>
0023 #include <QDebug>
0024 #include <QDir>
0025 #include <QDirIterator>
0026 #include <QLocale>
0027 #include <QMimeDatabase>
0028 
0029 #include <KConfigGroup>
0030 #include <KDesktopFile>
0031 #include <KLocalizedString>
0032 #include <kmountpoint.h>
0033 #ifndef Q_OS_WIN
0034 #include <knfsshare.h>
0035 #include <ksambashare.h>
0036 #endif
0037 #include <KFileSystemType>
0038 #include <KProtocolManager>
0039 #include <KShell>
0040 
0041 #define KFILEITEM_DEBUG 0
0042 
0043 class KFileItemPrivate : public QSharedData
0044 {
0045 public:
0046     KFileItemPrivate(const KIO::UDSEntry &entry,
0047                      mode_t mode,
0048                      mode_t permissions,
0049                      const QUrl &itemOrDirUrl,
0050                      bool urlIsDirectory,
0051                      bool delayedMimeTypes,
0052                      KFileItem::MimeTypeDetermination mimeTypeDetermination)
0053         : m_entry(entry)
0054         , m_url(itemOrDirUrl)
0055         , m_strName()
0056         , m_strText()
0057         , m_iconName()
0058         , m_strLowerCaseName()
0059         , m_mimeType()
0060         , m_fileMode(mode)
0061         , m_permissions(permissions)
0062         , m_addACL(false)
0063         , m_bLink(false)
0064         , m_bIsLocalUrl(itemOrDirUrl.isLocalFile())
0065         , m_bMimeTypeKnown(false)
0066         , m_delayedMimeTypes(delayedMimeTypes)
0067         , m_useIconNameCache(false)
0068         , m_hidden(Auto)
0069         , m_slow(SlowUnknown)
0070         , m_bSkipMimeTypeFromContent(mimeTypeDetermination == KFileItem::SkipMimeTypeFromContent)
0071         , m_bInitCalled(false)
0072     {
0073         if (entry.count() != 0) {
0074             readUDSEntry(urlIsDirectory);
0075         } else {
0076             Q_ASSERT(!urlIsDirectory);
0077             m_strName = itemOrDirUrl.fileName();
0078             m_strText = KIO::decodeFileName(m_strName);
0079         }
0080     }
0081 
0082     /**
0083      * Call init() if not yet done.
0084      */
0085     void ensureInitialized() const;
0086 
0087     /**
0088      * Computes the text and mode from the UDSEntry.
0089      */
0090     void init() const;
0091 
0092     QString localPath() const;
0093     KIO::filesize_t size() const;
0094     KIO::filesize_t recursiveSize() const;
0095     QDateTime time(KFileItem::FileTimes which) const;
0096     void setTime(KFileItem::FileTimes which, uint time_t_val) const;
0097     void setTime(KFileItem::FileTimes which, const QDateTime &val) const;
0098     bool cmp(const KFileItemPrivate &item) const;
0099     void printCompareDebug(const KFileItemPrivate &item) const;
0100     bool isSlow() const;
0101 
0102     /**
0103      * Extracts the data from the UDSEntry member and updates the KFileItem
0104      * accordingly.
0105      */
0106     void readUDSEntry(bool _urlIsDirectory);
0107 
0108     /**
0109      * Parses the given permission set and provides it for access()
0110      */
0111     QString parsePermissions(mode_t perm) const;
0112 
0113     /**
0114      * Mime type helper
0115      */
0116     void determineMimeTypeHelper(const QUrl &url) const;
0117 
0118     /**
0119      * The UDSEntry that contains the data for this fileitem, if it came from a directory listing.
0120      */
0121     mutable KIO::UDSEntry m_entry;
0122     /**
0123      * The url of the file
0124      */
0125     QUrl m_url;
0126 
0127     /**
0128      * The text for this item, i.e. the file name without path,
0129      */
0130     QString m_strName;
0131 
0132     /**
0133      * The text for this item, i.e. the file name without path, decoded
0134      * ('%%' becomes '%', '%2F' becomes '/')
0135      */
0136     QString m_strText;
0137 
0138     /**
0139      * The icon name for this item.
0140      */
0141     mutable QString m_iconName;
0142 
0143     /**
0144      * The filename in lower case (to speed up sorting)
0145      */
0146     mutable QString m_strLowerCaseName;
0147 
0148     /**
0149      * The MIME type of the file
0150      */
0151     mutable QMimeType m_mimeType;
0152 
0153     /**
0154      * The file mode
0155      */
0156     mutable mode_t m_fileMode;
0157     /**
0158      * The permissions
0159      */
0160     mutable mode_t m_permissions;
0161 
0162     /**
0163      * Whether the UDSEntry ACL fields should be added to m_entry.
0164      */
0165     mutable bool m_addACL : 1;
0166 
0167     /**
0168      * Whether the file is a link
0169      */
0170     mutable bool m_bLink : 1;
0171     /**
0172      * True if local file
0173      */
0174     bool m_bIsLocalUrl : 1;
0175 
0176     mutable bool m_bMimeTypeKnown : 1;
0177     mutable bool m_delayedMimeTypes : 1;
0178 
0179     /** True if m_iconName should be used as cache. */
0180     mutable bool m_useIconNameCache : 1;
0181 
0182     // Auto: check leading dot.
0183     enum { Auto, Hidden, Shown } m_hidden : 3;
0184 
0185     // Slow? (nfs/smb/ssh)
0186     mutable enum { SlowUnknown, Fast, Slow } m_slow : 3;
0187 
0188     /**
0189      * True if MIME type determination by content should be skipped
0190      */
0191     bool m_bSkipMimeTypeFromContent : 1;
0192 
0193     /**
0194      * True if init() was called on demand
0195      */
0196     mutable bool m_bInitCalled : 1;
0197 
0198     // For special case like link to dirs over FTP
0199     QString m_guessedMimeType;
0200     mutable QString m_access;
0201 };
0202 
0203 void KFileItemPrivate::ensureInitialized() const
0204 {
0205     if (!m_bInitCalled) {
0206         init();
0207     }
0208 }
0209 
0210 void KFileItemPrivate::init() const
0211 {
0212     m_access.clear();
0213     //  metaInfo = KFileMetaInfo();
0214 
0215     // stat() local files if needed
0216     const bool shouldStat = (m_fileMode == KFileItem::Unknown || m_permissions == KFileItem::Unknown || m_entry.count() == 0) && m_url.isLocalFile();
0217     if (shouldStat) {
0218         /* directories may not have a slash at the end if we want to stat()
0219          * them; it requires that we change into it .. which may not be allowed
0220          * stat("/is/unaccessible")  -> rwx------
0221          * stat("/is/unaccessible/") -> EPERM            H.Z.
0222          * This is the reason for the StripTrailingSlash
0223          */
0224         QT_STATBUF buf;
0225         const QString path = m_url.adjusted(QUrl::StripTrailingSlash).toLocalFile();
0226         const QByteArray pathBA = QFile::encodeName(path);
0227         if (QT_LSTAT(pathBA.constData(), &buf) == 0) {
0228             m_entry.reserve(9);
0229             m_entry.replace(KIO::UDSEntry::UDS_DEVICE_ID, buf.st_dev);
0230             m_entry.replace(KIO::UDSEntry::UDS_INODE, buf.st_ino);
0231 
0232             mode_t mode = buf.st_mode;
0233             if (Utils::isLinkMask(buf.st_mode)) {
0234                 m_bLink = true;
0235                 if (QT_STAT(pathBA.constData(), &buf) == 0) {
0236                     mode = buf.st_mode;
0237                 } else { // link pointing to nowhere (see FileProtocol::createUDSEntry() in kioworkers/file/file.cpp)
0238                     mode = (QT_STAT_MASK - 1) | S_IRWXU | S_IRWXG | S_IRWXO;
0239                 }
0240             }
0241 
0242             const mode_t type = mode & QT_STAT_MASK;
0243 
0244             m_entry.replace(KIO::UDSEntry::UDS_SIZE, buf.st_size);
0245             m_entry.replace(KIO::UDSEntry::UDS_FILE_TYPE, type); // extract file type
0246             m_entry.replace(KIO::UDSEntry::UDS_ACCESS, mode & 07777); // extract permissions
0247             m_entry.replace(KIO::UDSEntry::UDS_MODIFICATION_TIME, buf.st_mtime); // TODO: we could use msecs too...
0248             m_entry.replace(KIO::UDSEntry::UDS_ACCESS_TIME, buf.st_atime);
0249 #ifndef Q_OS_WIN
0250             const auto uid = buf.st_uid;
0251             const auto gid = buf.st_gid;
0252             m_entry.replace(KIO::UDSEntry::UDS_LOCAL_USER_ID, uid);
0253             m_entry.replace(KIO::UDSEntry::UDS_LOCAL_GROUP_ID, gid);
0254 #endif
0255 
0256             // TODO: these can be removed, we can use UDS_FILE_TYPE and UDS_ACCESS everywhere
0257             if (m_fileMode == KFileItem::Unknown) {
0258                 m_fileMode = type; // extract file type
0259             }
0260             if (m_permissions == KFileItem::Unknown) {
0261                 m_permissions = mode & 07777; // extract permissions
0262             }
0263 
0264 #if HAVE_POSIX_ACL
0265             if (m_addACL) {
0266                 appendACLAtoms(pathBA, m_entry, type);
0267             }
0268 #endif
0269         } else {
0270             if (errno != ENOENT) {
0271                 // another error
0272                 qCDebug(KIO_CORE) << QStringLiteral("KFileItem: error %1: %2").arg(errno).arg(QString::fromLatin1(strerror(errno))) << "when refreshing"
0273                                   << m_url;
0274             }
0275         }
0276     }
0277 
0278     m_bInitCalled = true;
0279 }
0280 
0281 void KFileItemPrivate::readUDSEntry(bool _urlIsDirectory)
0282 {
0283     // extract fields from the KIO::UDS Entry
0284 
0285     m_fileMode = m_entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE, KFileItem::Unknown);
0286     m_permissions = m_entry.numberValue(KIO::UDSEntry::UDS_ACCESS, KFileItem::Unknown);
0287     m_strName = m_entry.stringValue(KIO::UDSEntry::UDS_NAME);
0288 
0289     const QString displayName = m_entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
0290     if (!displayName.isEmpty()) {
0291         m_strText = displayName;
0292     } else {
0293         m_strText = KIO::decodeFileName(m_strName);
0294     }
0295 
0296     const QString urlStr = m_entry.stringValue(KIO::UDSEntry::UDS_URL);
0297     const bool UDS_URL_seen = !urlStr.isEmpty();
0298     if (UDS_URL_seen) {
0299         m_url = QUrl(urlStr);
0300         if (m_url.isLocalFile()) {
0301             m_bIsLocalUrl = true;
0302         }
0303     }
0304     QMimeDatabase db;
0305     const QString mimeTypeStr = m_entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE);
0306     m_bMimeTypeKnown = !mimeTypeStr.isEmpty();
0307     if (m_bMimeTypeKnown) {
0308         m_mimeType = db.mimeTypeForName(mimeTypeStr);
0309     }
0310 
0311     m_guessedMimeType = m_entry.stringValue(KIO::UDSEntry::UDS_GUESSED_MIME_TYPE);
0312     m_bLink = !m_entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST).isEmpty(); // we don't store the link dest
0313 
0314     const int hiddenVal = m_entry.numberValue(KIO::UDSEntry::UDS_HIDDEN, -1);
0315     m_hidden = hiddenVal == 1 ? Hidden : (hiddenVal == 0 ? Shown : Auto);
0316 
0317     if (_urlIsDirectory && !UDS_URL_seen && !m_strName.isEmpty() && m_strName != QLatin1String(".")) {
0318         m_url.setPath(Utils::concatPaths(m_url.path(), m_strName));
0319     }
0320 
0321     m_iconName.clear();
0322 }
0323 
0324 // Inlined because it is used only in one place
0325 inline KIO::filesize_t KFileItemPrivate::size() const
0326 {
0327     ensureInitialized();
0328 
0329     // Extract it from the KIO::UDSEntry
0330     long long fieldVal = m_entry.numberValue(KIO::UDSEntry::UDS_SIZE, -1);
0331     if (fieldVal != -1) {
0332         return fieldVal;
0333     }
0334 
0335     // If not in the KIO::UDSEntry, or if UDSEntry empty, use stat() [if local URL]
0336     if (m_bIsLocalUrl) {
0337         return QFileInfo(m_url.toLocalFile()).size();
0338     }
0339     return 0;
0340 }
0341 
0342 KIO::filesize_t KFileItemPrivate::recursiveSize() const
0343 {
0344     // Extract it from the KIO::UDSEntry
0345     long long fieldVal = m_entry.numberValue(KIO::UDSEntry::UDS_RECURSIVE_SIZE, -1);
0346     if (fieldVal != -1) {
0347         return static_cast<KIO::filesize_t>(fieldVal);
0348     }
0349 
0350     return 0;
0351 }
0352 
0353 static uint udsFieldForTime(KFileItem::FileTimes mappedWhich)
0354 {
0355     switch (mappedWhich) {
0356     case KFileItem::ModificationTime:
0357         return KIO::UDSEntry::UDS_MODIFICATION_TIME;
0358     case KFileItem::AccessTime:
0359         return KIO::UDSEntry::UDS_ACCESS_TIME;
0360     case KFileItem::CreationTime:
0361         return KIO::UDSEntry::UDS_CREATION_TIME;
0362     }
0363     return 0;
0364 }
0365 
0366 void KFileItemPrivate::setTime(KFileItem::FileTimes mappedWhich, uint time_t_val) const
0367 {
0368     m_entry.replace(udsFieldForTime(mappedWhich), time_t_val);
0369 }
0370 
0371 void KFileItemPrivate::setTime(KFileItem::FileTimes mappedWhich, const QDateTime &val) const
0372 {
0373     const QDateTime dt = val.toLocalTime(); // #160979
0374     setTime(mappedWhich, dt.toSecsSinceEpoch());
0375 }
0376 
0377 QDateTime KFileItemPrivate::time(KFileItem::FileTimes mappedWhich) const
0378 {
0379     ensureInitialized();
0380 
0381     // Extract it from the KIO::UDSEntry
0382     const uint uds = udsFieldForTime(mappedWhich);
0383     if (uds > 0) {
0384         const long long fieldVal = m_entry.numberValue(uds, -1);
0385         if (fieldVal != -1) {
0386             return QDateTime::fromSecsSinceEpoch(fieldVal);
0387         }
0388     }
0389 
0390     return QDateTime();
0391 }
0392 
0393 void KFileItemPrivate::printCompareDebug(const KFileItemPrivate &item) const
0394 {
0395     Q_UNUSED(item);
0396 
0397 #if KFILEITEM_DEBUG
0398     const KIO::UDSEntry &otherEntry = item.m_entry;
0399 
0400     qDebug() << "Comparing" << m_url << "and" << item.m_url;
0401     qDebug() << " name" << (m_strName == item.m_strName);
0402     qDebug() << " local" << (m_bIsLocalUrl == item.m_bIsLocalUrl);
0403 
0404     qDebug() << " mode" << (m_fileMode == item.m_fileMode);
0405     qDebug() << " perm" << (m_permissions == item.m_permissions);
0406     qDebug() << " group" << (m_entry.stringValue(KIO::UDSEntry::UDS_GROUP) == otherEntry.stringValue(KIO::UDSEntry::UDS_GROUP));
0407     qDebug() << " user" << (m_entry.stringValue(KIO::UDSEntry::UDS_USER) == otherEntry.stringValue(KIO::UDSEntry::UDS_USER));
0408 
0409     qDebug() << " UDS_EXTENDED_ACL" << (m_entry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL) == otherEntry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL));
0410     qDebug() << " UDS_ACL_STRING" << (m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING) == otherEntry.stringValue(KIO::UDSEntry::UDS_ACL_STRING));
0411     qDebug() << " UDS_DEFAULT_ACL_STRING"
0412              << (m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING) == otherEntry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING));
0413 
0414     qDebug() << " m_bLink" << (m_bLink == item.m_bLink);
0415     qDebug() << " m_hidden" << (m_hidden == item.m_hidden);
0416 
0417     qDebug() << " size" << (size() == item.size());
0418 
0419     qDebug() << " ModificationTime" << m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME)
0420              << otherEntry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME);
0421 
0422     qDebug() << " UDS_ICON_NAME" << (m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME) == otherEntry.stringValue(KIO::UDSEntry::UDS_ICON_NAME));
0423 #endif
0424 }
0425 
0426 // Inlined because it is used only in one place
0427 inline bool KFileItemPrivate::cmp(const KFileItemPrivate &item) const
0428 {
0429     if (item.m_bInitCalled) {
0430         ensureInitialized();
0431     }
0432 
0433     if (m_bInitCalled) {
0434         item.ensureInitialized();
0435     }
0436 
0437 #if KFILEITEM_DEBUG
0438     printCompareDebug(item);
0439 #endif
0440 
0441     /* clang-format off */
0442     return (m_strName == item.m_strName
0443             && m_bIsLocalUrl == item.m_bIsLocalUrl
0444             && m_fileMode == item.m_fileMode
0445             && m_permissions == item.m_permissions
0446             && m_entry.stringValue(KIO::UDSEntry::UDS_GROUP) == item.m_entry.stringValue(KIO::UDSEntry::UDS_GROUP)
0447             && m_entry.stringValue(KIO::UDSEntry::UDS_USER) == item.m_entry.stringValue(KIO::UDSEntry::UDS_USER)
0448             && m_entry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL) == item.m_entry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL)
0449             && m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING) == item.m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING)
0450             && m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING) == item.m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING)
0451             && m_bLink == item.m_bLink
0452             && m_hidden == item.m_hidden
0453             && size() == item.size()
0454             && m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME) == item.m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME)
0455             && m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME) == item.m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME)
0456             && m_entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL) == item.m_entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL)
0457             && m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH) == item.m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH));
0458     /* clang-format on */
0459     // Don't compare the MIME types here. They might not be known, and we don't want to
0460     // do the slow operation of determining them here.
0461 }
0462 
0463 // Inlined because it is used only in one place
0464 inline QString KFileItemPrivate::parsePermissions(mode_t perm) const
0465 {
0466     ensureInitialized();
0467 
0468     static char buffer[12];
0469 
0470     char uxbit;
0471     char gxbit;
0472     char oxbit;
0473 
0474     if ((perm & (S_IXUSR | S_ISUID)) == (S_IXUSR | S_ISUID)) {
0475         uxbit = 's';
0476     } else if ((perm & (S_IXUSR | S_ISUID)) == S_ISUID) {
0477         uxbit = 'S';
0478     } else if ((perm & (S_IXUSR | S_ISUID)) == S_IXUSR) {
0479         uxbit = 'x';
0480     } else {
0481         uxbit = '-';
0482     }
0483 
0484     if ((perm & (S_IXGRP | S_ISGID)) == (S_IXGRP | S_ISGID)) {
0485         gxbit = 's';
0486     } else if ((perm & (S_IXGRP | S_ISGID)) == S_ISGID) {
0487         gxbit = 'S';
0488     } else if ((perm & (S_IXGRP | S_ISGID)) == S_IXGRP) {
0489         gxbit = 'x';
0490     } else {
0491         gxbit = '-';
0492     }
0493 
0494     if ((perm & (S_IXOTH | S_ISVTX)) == (S_IXOTH | S_ISVTX)) {
0495         oxbit = 't';
0496     } else if ((perm & (S_IXOTH | S_ISVTX)) == S_ISVTX) {
0497         oxbit = 'T';
0498     } else if ((perm & (S_IXOTH | S_ISVTX)) == S_IXOTH) {
0499         oxbit = 'x';
0500     } else {
0501         oxbit = '-';
0502     }
0503 
0504     // Include the type in the first char like ls does; people are more used to seeing it,
0505     // even though it's not really part of the permissions per se.
0506     if (m_bLink) {
0507         buffer[0] = 'l';
0508     } else if (m_fileMode != KFileItem::Unknown) {
0509         if (Utils::isDirMask(m_fileMode)) {
0510             buffer[0] = 'd';
0511         }
0512 #ifdef Q_OS_UNIX
0513         else if (S_ISSOCK(m_fileMode)) {
0514             buffer[0] = 's';
0515         } else if (S_ISCHR(m_fileMode)) {
0516             buffer[0] = 'c';
0517         } else if (S_ISBLK(m_fileMode)) {
0518             buffer[0] = 'b';
0519         } else if (S_ISFIFO(m_fileMode)) {
0520             buffer[0] = 'p';
0521         }
0522 #endif // Q_OS_UNIX
0523         else {
0524             buffer[0] = '-';
0525         }
0526     } else {
0527         buffer[0] = '-';
0528     }
0529 
0530     buffer[1] = (((perm & S_IRUSR) == S_IRUSR) ? 'r' : '-');
0531     buffer[2] = (((perm & S_IWUSR) == S_IWUSR) ? 'w' : '-');
0532     buffer[3] = uxbit;
0533     buffer[4] = (((perm & S_IRGRP) == S_IRGRP) ? 'r' : '-');
0534     buffer[5] = (((perm & S_IWGRP) == S_IWGRP) ? 'w' : '-');
0535     buffer[6] = gxbit;
0536     buffer[7] = (((perm & S_IROTH) == S_IROTH) ? 'r' : '-');
0537     buffer[8] = (((perm & S_IWOTH) == S_IWOTH) ? 'w' : '-');
0538     buffer[9] = oxbit;
0539     // if (hasExtendedACL())
0540     if (m_entry.contains(KIO::UDSEntry::UDS_EXTENDED_ACL)) {
0541         buffer[10] = '+';
0542         buffer[11] = 0;
0543     } else {
0544         buffer[10] = 0;
0545     }
0546 
0547     return QString::fromLatin1(buffer);
0548 }
0549 
0550 void KFileItemPrivate::determineMimeTypeHelper(const QUrl &url) const
0551 {
0552     QMimeDatabase db;
0553     if (m_bSkipMimeTypeFromContent || isSlow()) {
0554         const QString scheme = url.scheme();
0555         if (scheme.startsWith(QLatin1String("http")) || scheme == QLatin1String("mailto")) {
0556             m_mimeType = db.mimeTypeForName(QLatin1String("application/octet-stream"));
0557         } else {
0558             m_mimeType = db.mimeTypeForFile(url.path(), QMimeDatabase::MatchMode::MatchExtension);
0559         }
0560     } else {
0561         m_mimeType = db.mimeTypeForUrl(url);
0562     }
0563 }
0564 
0565 ///////
0566 
0567 KFileItem::KFileItem()
0568     : d(nullptr)
0569 {
0570 }
0571 
0572 KFileItem::KFileItem(const KIO::UDSEntry &entry, const QUrl &itemOrDirUrl, bool delayedMimeTypes, bool urlIsDirectory)
0573     : d(new KFileItemPrivate(entry,
0574                              KFileItem::Unknown,
0575                              KFileItem::Unknown,
0576                              itemOrDirUrl,
0577                              urlIsDirectory,
0578                              delayedMimeTypes,
0579                              KFileItem::NormalMimeTypeDetermination))
0580 {
0581 }
0582 
0583 KFileItem::KFileItem(const QUrl &url, const QString &mimeType, mode_t mode)
0584     : d(new KFileItemPrivate(KIO::UDSEntry(), mode, KFileItem::Unknown, url, false, false, KFileItem::NormalMimeTypeDetermination))
0585 {
0586     d->m_bMimeTypeKnown = !mimeType.simplified().isEmpty();
0587     if (d->m_bMimeTypeKnown) {
0588         QMimeDatabase db;
0589         d->m_mimeType = db.mimeTypeForName(mimeType);
0590     }
0591 }
0592 
0593 KFileItem::KFileItem(const QUrl &url, KFileItem::MimeTypeDetermination mimeTypeDetermination)
0594     : d(new KFileItemPrivate(KIO::UDSEntry(), KFileItem::Unknown, KFileItem::Unknown, url, false, false, mimeTypeDetermination))
0595 {
0596 }
0597 
0598 // Default implementations for:
0599 // - Copy constructor
0600 // - Move constructor
0601 // - Copy assignment
0602 // - Move assignment
0603 // - Destructor
0604 // The compiler will now generate the content of those.
0605 KFileItem::KFileItem(const KFileItem &) = default;
0606 KFileItem::~KFileItem() = default;
0607 KFileItem::KFileItem(KFileItem &&) = default;
0608 KFileItem &KFileItem::operator=(const KFileItem &) = default;
0609 KFileItem &KFileItem::operator=(KFileItem &&) = default;
0610 
0611 void KFileItem::refresh()
0612 {
0613     if (!d) {
0614         qCWarning(KIO_CORE) << "null item";
0615         return;
0616     }
0617 
0618     d->m_fileMode = KFileItem::Unknown;
0619     d->m_permissions = KFileItem::Unknown;
0620     d->m_hidden = KFileItemPrivate::Auto;
0621     refreshMimeType();
0622 
0623 #if HAVE_POSIX_ACL
0624     // If the item had ACL, re-add them in init()
0625     d->m_addACL = !d->m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING).isEmpty();
0626 #endif
0627 
0628     // Basically, we can't trust any information we got while listing.
0629     // Everything could have changed...
0630     // Clearing m_entry makes it possible to detect changes in the size of the file,
0631     // the time information, etc.
0632     d->m_entry.clear();
0633     d->init(); // re-populates d->m_entry
0634 }
0635 
0636 void KFileItem::refreshMimeType()
0637 {
0638     if (!d) {
0639         return;
0640     }
0641 
0642     d->m_mimeType = QMimeType();
0643     d->m_bMimeTypeKnown = false;
0644     d->m_iconName.clear();
0645 }
0646 
0647 void KFileItem::setDelayedMimeTypes(bool b)
0648 {
0649     if (!d) {
0650         return;
0651     }
0652     d->m_delayedMimeTypes = b;
0653 }
0654 
0655 void KFileItem::setUrl(const QUrl &url)
0656 {
0657     if (!d) {
0658         qCWarning(KIO_CORE) << "null item";
0659         return;
0660     }
0661 
0662     d->m_url = url;
0663     setName(url.fileName());
0664 }
0665 
0666 void KFileItem::setLocalPath(const QString &path)
0667 {
0668     if (!d) {
0669         qCWarning(KIO_CORE) << "null item";
0670         return;
0671     }
0672 
0673     d->m_entry.replace(KIO::UDSEntry::UDS_LOCAL_PATH, path);
0674 }
0675 
0676 void KFileItem::setName(const QString &name)
0677 {
0678     if (!d) {
0679         qCWarning(KIO_CORE) << "null item";
0680         return;
0681     }
0682 
0683     d->ensureInitialized();
0684 
0685     d->m_strName = name;
0686     if (!d->m_strName.isEmpty()) {
0687         d->m_strText = KIO::decodeFileName(d->m_strName);
0688     }
0689     if (d->m_entry.contains(KIO::UDSEntry::UDS_NAME)) {
0690         d->m_entry.replace(KIO::UDSEntry::UDS_NAME, d->m_strName); // #195385
0691     }
0692 }
0693 
0694 QString KFileItem::linkDest() const
0695 {
0696     if (!d) {
0697         return QString();
0698     }
0699 
0700     d->ensureInitialized();
0701 
0702     // Extract it from the KIO::UDSEntry
0703     const QString linkStr = d->m_entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
0704     if (!linkStr.isEmpty()) {
0705         return linkStr;
0706     }
0707 
0708     // If not in the KIO::UDSEntry, or if UDSEntry empty, use readlink() [if local URL]
0709     if (d->m_bIsLocalUrl) {
0710         return QFile::symLinkTarget(d->m_url.adjusted(QUrl::StripTrailingSlash).toLocalFile());
0711     }
0712     return QString();
0713 }
0714 
0715 QString KFileItemPrivate::localPath() const
0716 {
0717     if (m_bIsLocalUrl) {
0718         return m_url.toLocalFile();
0719     }
0720 
0721     ensureInitialized();
0722 
0723     // Extract the local path from the KIO::UDSEntry
0724     return m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
0725 }
0726 
0727 QString KFileItem::localPath() const
0728 {
0729     if (!d) {
0730         return QString();
0731     }
0732 
0733     return d->localPath();
0734 }
0735 
0736 KIO::filesize_t KFileItem::size() const
0737 {
0738     if (!d) {
0739         return 0;
0740     }
0741 
0742     return d->size();
0743 }
0744 
0745 KIO::filesize_t KFileItem::recursiveSize() const
0746 {
0747     if (!d) {
0748         return 0;
0749     }
0750 
0751     return d->recursiveSize();
0752 }
0753 
0754 bool KFileItem::hasExtendedACL() const
0755 {
0756     if (!d) {
0757         return false;
0758     }
0759 
0760     // Check if the field exists; its value doesn't matter
0761     return entry().contains(KIO::UDSEntry::UDS_EXTENDED_ACL);
0762 }
0763 
0764 KACL KFileItem::ACL() const
0765 {
0766     if (!d) {
0767         return KACL();
0768     }
0769 
0770     if (hasExtendedACL()) {
0771         // Extract it from the KIO::UDSEntry
0772         const QString fieldVal = d->m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING);
0773         if (!fieldVal.isEmpty()) {
0774             return KACL(fieldVal);
0775         }
0776     }
0777 
0778     // create one from the basic permissions
0779     return KACL(d->m_permissions);
0780 }
0781 
0782 KACL KFileItem::defaultACL() const
0783 {
0784     if (!d) {
0785         return KACL();
0786     }
0787 
0788     // Extract it from the KIO::UDSEntry
0789     const QString fieldVal = entry().stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING);
0790     if (!fieldVal.isEmpty()) {
0791         return KACL(fieldVal);
0792     } else {
0793         return KACL();
0794     }
0795 }
0796 
0797 QDateTime KFileItem::time(FileTimes which) const
0798 {
0799     if (!d) {
0800         return QDateTime();
0801     }
0802 
0803     return d->time(which);
0804 }
0805 
0806 QString KFileItem::user() const
0807 {
0808     if (!d) {
0809         return QString();
0810     }
0811     if (entry().contains(KIO::UDSEntry::UDS_USER)) {
0812         return entry().stringValue(KIO::UDSEntry::UDS_USER);
0813     } else {
0814 #ifdef Q_OS_UNIX
0815         auto uid = entry().numberValue(KIO::UDSEntry::UDS_LOCAL_USER_ID, -1);
0816         if (uid != -1) {
0817             return KUser(uid).loginName();
0818         }
0819 #endif
0820     }
0821     return QString();
0822 }
0823 
0824 int KFileItem::userId() const
0825 {
0826     if (!d) {
0827         return -1;
0828     }
0829 
0830     return entry().numberValue(KIO::UDSEntry::UDS_LOCAL_USER_ID, -1);
0831 }
0832 
0833 QString KFileItem::group() const
0834 {
0835     if (!d) {
0836         return QString();
0837     }
0838 
0839     if (entry().contains(KIO::UDSEntry::UDS_GROUP)) {
0840         return entry().stringValue(KIO::UDSEntry::UDS_GROUP);
0841     } else {
0842 #ifdef Q_OS_UNIX
0843         auto gid = entry().numberValue(KIO::UDSEntry::UDS_LOCAL_GROUP_ID, -1);
0844         if (gid != -1) {
0845             return KUserGroup(gid).name();
0846         }
0847 #endif
0848     }
0849     return QString();
0850 }
0851 
0852 int KFileItem::groupId() const
0853 {
0854     if (!d) {
0855         return -1;
0856     }
0857 
0858     return entry().numberValue(KIO::UDSEntry::UDS_LOCAL_GROUP_ID, -1);
0859 }
0860 
0861 bool KFileItemPrivate::isSlow() const
0862 {
0863     if (m_slow == SlowUnknown) {
0864         const QString path = localPath();
0865         if (!path.isEmpty()) {
0866             const KFileSystemType::Type fsType = KFileSystemType::fileSystemType(path);
0867             m_slow = (fsType == KFileSystemType::Nfs || fsType == KFileSystemType::Smb) ? Slow : Fast;
0868         } else {
0869             m_slow = Slow;
0870         }
0871     }
0872     return m_slow == Slow;
0873 }
0874 
0875 bool KFileItem::isSlow() const
0876 {
0877     if (!d) {
0878         return false;
0879     }
0880 
0881     return d->isSlow();
0882 }
0883 
0884 QString KFileItem::mimetype() const
0885 {
0886     if (!d) {
0887         return QString();
0888     }
0889 
0890     KFileItem *that = const_cast<KFileItem *>(this);
0891     return that->determineMimeType().name();
0892 }
0893 
0894 QMimeType KFileItem::determineMimeType() const
0895 {
0896     if (!d) {
0897         return QMimeType();
0898     }
0899 
0900     if (!d->m_mimeType.isValid() || !d->m_bMimeTypeKnown) {
0901         QMimeDatabase db;
0902         if (isDir()) {
0903             d->m_mimeType = db.mimeTypeForName(QStringLiteral("inode/directory"));
0904         } else {
0905             const auto [url, isLocalUrl] = isMostLocalUrl();
0906             d->determineMimeTypeHelper(url);
0907 
0908             // was:  d->m_mimeType = KMimeType::findByUrl( url, d->m_fileMode, isLocalUrl );
0909             // => we are no longer using d->m_fileMode for remote URLs.
0910             Q_ASSERT(d->m_mimeType.isValid());
0911             // qDebug() << d << "finding final MIME type for" << url << ":" << d->m_mimeType.name();
0912         }
0913         d->m_bMimeTypeKnown = true;
0914     }
0915 
0916     if (d->m_delayedMimeTypes) { // if we delayed getting the iconName up till now, this is the right point in time to do so
0917         d->m_delayedMimeTypes = false;
0918         d->m_useIconNameCache = false;
0919         (void)iconName();
0920     }
0921 
0922     return d->m_mimeType;
0923 }
0924 
0925 bool KFileItem::isMimeTypeKnown() const
0926 {
0927     if (!d) {
0928         return false;
0929     }
0930 
0931     // The MIME type isn't known if determineMimeType was never called (on-demand determination)
0932     // or if this fileitem has a guessed MIME type (e.g. ftp symlink) - in which case
0933     // it always remains "not fully determined"
0934     return d->m_bMimeTypeKnown && d->m_guessedMimeType.isEmpty();
0935 }
0936 
0937 static bool isDirectoryMounted(const QUrl &url)
0938 {
0939     // Stating .directory files can cause long freezes when e.g. /home
0940     // uses autofs for every user's home directory, i.e. opening /home
0941     // in a file dialog will mount every single home directory.
0942     // These non-mounted directories can be identified by having 0 size.
0943     // There are also other directories with 0 size, such as /proc, that may
0944     // be mounted, but those are unlikely to contain .directory (and checking
0945     // this would require checking with KMountPoint).
0946 
0947     // TODO: maybe this could be checked with KFileSystemType instead?
0948     QFileInfo info(url.toLocalFile());
0949     if (info.isDir() && info.size() == 0) {
0950         return false;
0951     }
0952     return true;
0953 }
0954 
0955 bool KFileItem::isFinalIconKnown() const
0956 {
0957     if (!d) {
0958         return false;
0959     }
0960     return d->m_bMimeTypeKnown && (!d->m_delayedMimeTypes);
0961 }
0962 
0963 // KDE5 TODO: merge with comment()? Need to see what lxr says about the usage of both.
0964 QString KFileItem::mimeComment() const
0965 {
0966     if (!d) {
0967         return QString();
0968     }
0969 
0970     const QString displayType = d->m_entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_TYPE);
0971     if (!displayType.isEmpty()) {
0972         return displayType;
0973     }
0974 
0975     const auto [url, isLocalUrl] = isMostLocalUrl();
0976 
0977     QMimeType mime = currentMimeType();
0978     // This cannot move to kio_file (with UDS_DISPLAY_TYPE) because it needs
0979     // the MIME type to be determined, which is done here, and possibly delayed...
0980     if (isLocalUrl && !d->isSlow() && mime.inherits(QStringLiteral("application/x-desktop"))) {
0981         KDesktopFile cfg(url.toLocalFile());
0982         QString comment = cfg.desktopGroup().readEntry("Comment");
0983         if (!comment.isEmpty()) {
0984             return comment;
0985         }
0986     }
0987 
0988     // Support for .directory file in directories
0989     if (isLocalUrl && isDir() && !d->isSlow() && isDirectoryMounted(url)) {
0990         QUrl u(url);
0991         u.setPath(Utils::concatPaths(u.path(), QStringLiteral(".directory")));
0992         const KDesktopFile cfg(u.toLocalFile());
0993         const QString comment = cfg.readComment();
0994         if (!comment.isEmpty()) {
0995             return comment;
0996         }
0997     }
0998 
0999     const QString comment = mime.comment();
1000     // qDebug() << "finding comment for " << url.url() << " : " << d->m_mimeType->name();
1001     if (!comment.isEmpty()) {
1002         return comment;
1003     } else {
1004         return mime.name();
1005     }
1006 }
1007 
1008 static QString iconFromDirectoryFile(const QString &path)
1009 {
1010     const QString filePath = path + QLatin1String("/.directory");
1011     if (!QFileInfo(filePath).isFile()) { // exists -and- is a file
1012         return QString();
1013     }
1014 
1015     KDesktopFile cfg(filePath);
1016     QString icon = cfg.readIcon();
1017 
1018     const KConfigGroup group = cfg.desktopGroup();
1019     const QString emptyIcon = group.readEntry("EmptyIcon");
1020     if (!emptyIcon.isEmpty()) {
1021         bool isDirEmpty = true;
1022         QDirIterator dirIt(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
1023         while (dirIt.hasNext()) {
1024             dirIt.next();
1025             if (dirIt.fileName() != QLatin1String(".directory")) {
1026                 isDirEmpty = false;
1027                 break;
1028             }
1029         }
1030         if (isDirEmpty) {
1031             icon = emptyIcon;
1032         }
1033     }
1034 
1035     if (icon.startsWith(QLatin1String("./"))) {
1036         // path is relative with respect to the location of the .directory file (#73463)
1037         return path + QStringView(icon).mid(1);
1038     }
1039     return icon;
1040 }
1041 
1042 static QString iconFromDesktopFile(const QString &path)
1043 {
1044     KDesktopFile cfg(path);
1045     const QString icon = cfg.readIcon();
1046     if (cfg.hasLinkType()) {
1047         const KConfigGroup group = cfg.desktopGroup();
1048         const QString emptyIcon = group.readEntry("EmptyIcon");
1049         if (!emptyIcon.isEmpty()) {
1050             const QString u = cfg.readUrl();
1051             const QUrl url(u);
1052             if (url.scheme() == QLatin1String("trash")) {
1053                 // We need to find if the trash is empty, preferably without using a KIO job.
1054                 // So instead kio_trash leaves an entry in its config file for us.
1055                 KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig);
1056                 if (trashConfig.group(QStringLiteral("Status")).readEntry("Empty", true)) {
1057                     return emptyIcon;
1058                 }
1059             }
1060         }
1061     }
1062     return icon;
1063 }
1064 
1065 QString KFileItem::iconName() const
1066 {
1067     if (!d) {
1068         return QString();
1069     }
1070 
1071     if (d->m_useIconNameCache && !d->m_iconName.isEmpty()) {
1072         return d->m_iconName;
1073     }
1074 
1075     d->m_iconName = d->m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME);
1076     if (!d->m_iconName.isEmpty()) {
1077         d->m_useIconNameCache = d->m_bMimeTypeKnown;
1078         return d->m_iconName;
1079     }
1080 
1081     const auto [url, isLocalUrl] = isMostLocalUrl();
1082 
1083     QMimeDatabase db;
1084     QMimeType mime;
1085     // Use guessed MIME type for the icon
1086     if (!d->m_guessedMimeType.isEmpty()) {
1087         mime = db.mimeTypeForName(d->m_guessedMimeType);
1088     } else {
1089         mime = currentMimeType();
1090     }
1091 
1092     const bool delaySlowOperations = d->m_delayedMimeTypes;
1093 
1094     if (isLocalUrl && !delaySlowOperations) {
1095         const QString &localFile = url.toLocalFile();
1096 
1097         if (mime.inherits(QStringLiteral("application/x-desktop"))) {
1098             d->m_iconName = iconFromDesktopFile(localFile);
1099             if (!d->m_iconName.isEmpty()) {
1100                 d->m_useIconNameCache = d->m_bMimeTypeKnown;
1101                 return d->m_iconName;
1102             }
1103         }
1104 
1105         if (isDir()) {
1106             if (isDirectoryMounted(url)) {
1107                 d->m_iconName = iconFromDirectoryFile(localFile);
1108                 if (!d->m_iconName.isEmpty()) {
1109                     d->m_useIconNameCache = d->m_bMimeTypeKnown;
1110                     return d->m_iconName;
1111                 }
1112             }
1113 
1114             d->m_iconName = KIOPrivate::iconForStandardPath(localFile);
1115             if (!d->m_iconName.isEmpty()) {
1116                 d->m_useIconNameCache = d->m_bMimeTypeKnown;
1117                 return d->m_iconName;
1118             }
1119         }
1120     }
1121 
1122     d->m_iconName = mime.iconName();
1123     d->m_useIconNameCache = d->m_bMimeTypeKnown;
1124     return d->m_iconName;
1125 }
1126 
1127 /**
1128  * Returns true if this is a desktop file.
1129  * MIME type determination is optional.
1130  */
1131 static bool checkDesktopFile(const KFileItem &item, bool _determineMimeType)
1132 {
1133     // Only local files
1134     if (!item.isMostLocalUrl().local) {
1135         return false;
1136     }
1137 
1138     // only regular files
1139     if (!item.isRegularFile()) {
1140         return false;
1141     }
1142 
1143     // only if readable
1144     if (!item.isReadable()) {
1145         return false;
1146     }
1147 
1148     // return true if desktop file
1149     QMimeType mime = _determineMimeType ? item.determineMimeType() : item.currentMimeType();
1150     return mime.inherits(QStringLiteral("application/x-desktop"));
1151 }
1152 
1153 QStringList KFileItem::overlays() const
1154 {
1155     if (!d) {
1156         return QStringList();
1157     }
1158 
1159     d->ensureInitialized();
1160 
1161     QStringList names = d->m_entry.stringValue(KIO::UDSEntry::UDS_ICON_OVERLAY_NAMES).split(QLatin1Char(','), Qt::SkipEmptyParts);
1162 
1163     if (d->m_bLink) {
1164         names.append(QStringLiteral("emblem-symbolic-link"));
1165     }
1166 
1167     if (!isReadable()) {
1168         names.append(QStringLiteral("emblem-locked"));
1169     }
1170 
1171     if (checkDesktopFile(*this, false)) {
1172         KDesktopFile cfg(localPath());
1173         const KConfigGroup group = cfg.desktopGroup();
1174 
1175         // Add a warning emblem if this is an executable desktop file
1176         // which is untrusted.
1177         if (group.hasKey("Exec") && !KDesktopFile::isAuthorizedDesktopFile(localPath())) {
1178             names.append(QStringLiteral("emblem-important"));
1179         }
1180     }
1181 
1182     if (isHidden()) {
1183         names.append(QStringLiteral("hidden"));
1184     }
1185 #ifndef Q_OS_WIN
1186     if (isDir()) {
1187         const auto [url, isLocalUrl] = isMostLocalUrl();
1188         if (isLocalUrl) {
1189             const QString path = url.toLocalFile();
1190             if (KSambaShare::instance()->isDirectoryShared(path) || KNFSShare::instance()->isDirectoryShared(path)) {
1191                 names.append(QStringLiteral("emblem-shared"));
1192             }
1193         }
1194     }
1195 #endif // Q_OS_WIN
1196 
1197     return names;
1198 }
1199 
1200 QString KFileItem::comment() const
1201 {
1202     if (!d) {
1203         return QString();
1204     }
1205 
1206     return d->m_entry.stringValue(KIO::UDSEntry::UDS_COMMENT);
1207 }
1208 
1209 bool KFileItem::isReadable() const
1210 {
1211     if (!d) {
1212         return false;
1213     }
1214 
1215     d->ensureInitialized();
1216 
1217     if (d->m_permissions != KFileItem::Unknown) {
1218         const mode_t readMask = S_IRUSR | S_IRGRP | S_IROTH;
1219         // No read permission at all
1220         if ((d->m_permissions & readMask) == 0) {
1221             return false;
1222         }
1223 
1224         // Read permissions for all: save a stat call
1225         if ((d->m_permissions & readMask) == readMask) {
1226             return true;
1227         }
1228 
1229 #ifndef Q_OS_WIN
1230         const auto uid = userId();
1231         if (uid != -1) {
1232             if (((uint) uid) == KUserId::currentUserId().nativeId()) {
1233                 return S_IRUSR & d->m_permissions;
1234             }
1235             const auto gid = groupId();
1236             if (gid != -1) {
1237                 const KUser kuser = KUser(uid);
1238                 if (kuser.groups().contains(KUserGroup(gid))) {
1239                     return S_IRGRP & d->m_permissions;
1240                 }
1241 
1242                 return S_IROTH & d->m_permissions;
1243             }
1244         }
1245 #else
1246         // simple special case
1247         return S_IRUSR & d->m_permissions;
1248 #endif
1249     }
1250 
1251     // Or if we can't read it - not network transparent
1252     if (d->m_bIsLocalUrl && !QFileInfo(d->m_url.toLocalFile()).isReadable()) {
1253         return false;
1254     }
1255 
1256     return true;
1257 }
1258 
1259 bool KFileItem::isWritable() const
1260 {
1261     if (!d) {
1262         return false;
1263     }
1264 
1265     d->ensureInitialized();
1266 
1267     if (d->m_permissions != KFileItem::Unknown) {
1268         // No write permission at all
1269         if ((d->m_permissions & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0) {
1270             return false;
1271         }
1272 
1273 #ifndef Q_OS_WIN
1274         const auto uid = userId();
1275         if (uid != -1) {
1276             if (((uint) uid) == KUserId::currentUserId().nativeId()) {
1277                 return S_IWUSR & d->m_permissions;
1278             }
1279             const auto gid = groupId();
1280             if (gid != -1) {
1281                 const KUser kuser = KUser(uid);
1282                 if (kuser.groups().contains(KUserGroup(gid))) {
1283                     return S_IWGRP & d->m_permissions;
1284                 }
1285 
1286                 return S_IWOTH & d->m_permissions;
1287             }
1288         }
1289 #else
1290         // simple special case
1291         return S_IWUSR & d->m_permissions;
1292 #endif
1293     }
1294 
1295     // Or if we can't write it - not network transparent
1296     if (d->m_bIsLocalUrl) {
1297         return QFileInfo(d->m_url.toLocalFile()).isWritable();
1298     } else {
1299         return KProtocolManager::supportsWriting(d->m_url);
1300     }
1301 }
1302 
1303 bool KFileItem::isHidden() const
1304 {
1305     if (!d) {
1306         return false;
1307     }
1308 
1309     // The KIO worker can specify explicitly that a file is hidden or shown
1310     if (d->m_hidden != KFileItemPrivate::Auto) {
1311         return d->m_hidden == KFileItemPrivate::Hidden;
1312     }
1313 
1314     // Prefer the filename that is part of the URL, in case the display name is different.
1315     QString fileName = d->m_url.fileName();
1316     if (fileName.isEmpty()) { // e.g. "trash:/"
1317         fileName = d->m_strName;
1318     }
1319     return fileName.length() > 1 && fileName[0] == QLatin1Char('.'); // Just "." is current directory, not hidden.
1320 }
1321 
1322 void KFileItem::setHidden()
1323 {
1324     if (d) {
1325         d->m_hidden = KFileItemPrivate::Hidden;
1326     }
1327 }
1328 
1329 bool KFileItem::isDir() const
1330 {
1331     if (!d) {
1332         return false;
1333     }
1334 
1335     if (d->m_bMimeTypeKnown && d->m_mimeType.isValid()) {
1336         return d->m_mimeType.inherits(QStringLiteral("inode/directory"));
1337     }
1338 
1339     if (d->m_bSkipMimeTypeFromContent) {
1340         return false;
1341     }
1342 
1343     d->ensureInitialized();
1344 
1345     if (d->m_fileMode == KFileItem::Unknown) {
1346         // Probably the file was deleted already, and KDirLister hasn't told the world yet.
1347         // qDebug() << d << url() << "can't say -> false";
1348         return false; // can't say for sure, so no
1349     }
1350     return Utils::isDirMask(d->m_fileMode);
1351 }
1352 
1353 bool KFileItem::isFile() const
1354 {
1355     if (!d) {
1356         return false;
1357     }
1358 
1359     return !isDir();
1360 }
1361 
1362 QString KFileItem::getStatusBarInfo() const
1363 {
1364     if (!d) {
1365         return QString();
1366     }
1367 
1368     auto toDisplayUrl = [](const QUrl &url) {
1369         QString dest;
1370         if (url.isLocalFile()) {
1371             dest = KShell::tildeCollapse(url.toLocalFile());
1372         } else {
1373             dest = url.toDisplayString();
1374         }
1375         return dest;
1376     };
1377 
1378     QString text = d->m_strText;
1379     const QString comment = mimeComment();
1380 
1381     if (d->m_bLink) {
1382         auto linkText = linkDest();
1383         if (!linkText.startsWith(QStringLiteral("anon_inode:"))) {
1384             linkText = toDisplayUrl(d->m_url.resolved(QUrl::fromUserInput(linkText)));
1385         }
1386         text += QLatin1Char(' ');
1387         if (comment.isEmpty()) {
1388             text += i18n("(Symbolic Link to %1)", linkText);
1389         } else {
1390             text += i18n("(%1, Link to %2)", comment, linkText);
1391         }
1392     } else if (targetUrl() != url()) {
1393         text += i18n(" (Points to %1)", toDisplayUrl(targetUrl()));
1394     } else if (Utils::isRegFileMask(d->m_fileMode)) {
1395         text += QStringLiteral(" (%1, %2)").arg(comment, KIO::convertSize(size()));
1396     } else {
1397         text += QStringLiteral(" (%1)").arg(comment);
1398     }
1399     return text;
1400 }
1401 
1402 bool KFileItem::cmp(const KFileItem &item) const
1403 {
1404     if (!d && !item.d) {
1405         return true;
1406     }
1407 
1408     if (!d || !item.d) {
1409         return false;
1410     }
1411 
1412     return d->cmp(*item.d);
1413 }
1414 
1415 bool KFileItem::operator==(const KFileItem &other) const
1416 {
1417     if (!d && !other.d) {
1418         return true;
1419     }
1420 
1421     if (!d || !other.d) {
1422         return false;
1423     }
1424 
1425     return d->m_url == other.d->m_url;
1426 }
1427 
1428 bool KFileItem::operator!=(const KFileItem &other) const
1429 {
1430     return !operator==(other);
1431 }
1432 
1433 bool KFileItem::operator<(const KFileItem &other) const
1434 {
1435     if (!other.d) {
1436         return false;
1437     }
1438     if (!d) {
1439         return other.d->m_url.isValid();
1440     }
1441     return d->m_url < other.d->m_url;
1442 }
1443 
1444 bool KFileItem::operator<(const QUrl &other) const
1445 {
1446     if (!d) {
1447         return other.isValid();
1448     }
1449     return d->m_url < other;
1450 }
1451 
1452 KFileItem::operator QVariant() const
1453 {
1454     return QVariant::fromValue(*this);
1455 }
1456 
1457 QString KFileItem::permissionsString() const
1458 {
1459     if (!d) {
1460         return QString();
1461     }
1462 
1463     d->ensureInitialized();
1464 
1465     if (d->m_access.isNull() && d->m_permissions != KFileItem::Unknown) {
1466         d->m_access = d->parsePermissions(d->m_permissions);
1467     }
1468 
1469     return d->m_access;
1470 }
1471 
1472 // check if we need to cache this
1473 QString KFileItem::timeString(FileTimes which) const
1474 {
1475     if (!d) {
1476         return QString();
1477     }
1478 
1479     return QLocale::system().toString(d->time(which), QLocale::LongFormat);
1480 }
1481 
1482 QUrl KFileItem::mostLocalUrl(bool *local) const
1483 {
1484     if (!d) {
1485         return {};
1486     }
1487 
1488     const auto [url, isLocal] = isMostLocalUrl();
1489     if (local) {
1490         *local = isLocal;
1491     }
1492     return url;
1493 }
1494 
1495 KFileItem::MostLocalUrlResult KFileItem::isMostLocalUrl() const
1496 {
1497     if (!d) {
1498         return {QUrl(), false};
1499     }
1500 
1501     const QString local_path = localPath();
1502     if (!local_path.isEmpty()) {
1503         return {QUrl::fromLocalFile(local_path), true};
1504     } else {
1505         return {d->m_url, d->m_bIsLocalUrl};
1506     }
1507 }
1508 
1509 QDataStream &operator<<(QDataStream &s, const KFileItem &a)
1510 {
1511     if (a.d) {
1512         // We don't need to save/restore anything that refresh() invalidates,
1513         // since that means we can re-determine those by ourselves.
1514         s << a.d->m_url;
1515         s << a.d->m_strName;
1516         s << a.d->m_strText;
1517     } else {
1518         s << QUrl();
1519         s << QString();
1520         s << QString();
1521     }
1522 
1523     return s;
1524 }
1525 
1526 QDataStream &operator>>(QDataStream &s, KFileItem &a)
1527 {
1528     QUrl url;
1529     QString strName;
1530     QString strText;
1531 
1532     s >> url;
1533     s >> strName;
1534     s >> strText;
1535 
1536     if (!a.d) {
1537         qCWarning(KIO_CORE) << "null item";
1538         return s;
1539     }
1540 
1541     if (url.isEmpty()) {
1542         a.d = nullptr;
1543         return s;
1544     }
1545 
1546     a.d->m_url = url;
1547     a.d->m_strName = strName;
1548     a.d->m_strText = strText;
1549     a.d->m_bIsLocalUrl = a.d->m_url.isLocalFile();
1550     a.d->m_bMimeTypeKnown = false;
1551     a.refresh();
1552 
1553     return s;
1554 }
1555 
1556 QUrl KFileItem::url() const
1557 {
1558     if (!d) {
1559         return QUrl();
1560     }
1561 
1562     return d->m_url;
1563 }
1564 
1565 mode_t KFileItem::permissions() const
1566 {
1567     if (!d) {
1568         return 0;
1569     }
1570 
1571     d->ensureInitialized();
1572 
1573     return d->m_permissions;
1574 }
1575 
1576 mode_t KFileItem::mode() const
1577 {
1578     if (!d) {
1579         return 0;
1580     }
1581 
1582     d->ensureInitialized();
1583 
1584     return d->m_fileMode;
1585 }
1586 
1587 bool KFileItem::isLink() const
1588 {
1589     if (!d) {
1590         return false;
1591     }
1592 
1593     d->ensureInitialized();
1594 
1595     return d->m_bLink;
1596 }
1597 
1598 bool KFileItem::isLocalFile() const
1599 {
1600     if (!d) {
1601         return false;
1602     }
1603 
1604     return d->m_bIsLocalUrl;
1605 }
1606 
1607 QString KFileItem::text() const
1608 {
1609     if (!d) {
1610         return QString();
1611     }
1612 
1613     return d->m_strText;
1614 }
1615 
1616 QString KFileItem::name(bool lowerCase) const
1617 {
1618     if (!d) {
1619         return QString();
1620     }
1621 
1622     if (!lowerCase) {
1623         return d->m_strName;
1624     } else if (d->m_strLowerCaseName.isNull()) {
1625         d->m_strLowerCaseName = d->m_strName.toLower();
1626     }
1627     return d->m_strLowerCaseName;
1628 }
1629 
1630 QUrl KFileItem::targetUrl() const
1631 {
1632     if (!d) {
1633         return QUrl();
1634     }
1635 
1636     const QString targetUrlStr = d->m_entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL);
1637     if (!targetUrlStr.isEmpty()) {
1638         return QUrl(targetUrlStr);
1639     } else {
1640         return url();
1641     }
1642 }
1643 
1644 /*
1645  * MIME type handling.
1646  *
1647  * Initial state: m_mimeType = QMimeType().
1648  * When currentMimeType() is called first: fast MIME type determination,
1649  *   might either find an accurate MIME type (-> Final state), otherwise we
1650  *   set m_mimeType but not m_bMimeTypeKnown (-> Intermediate state)
1651  * Intermediate state: determineMimeType() does the real determination -> Final state.
1652  *
1653  * If delayedMimeTypes isn't set, then we always go to the Final state directly.
1654  */
1655 
1656 QMimeType KFileItem::currentMimeType() const
1657 {
1658     if (!d || d->m_url.isEmpty()) {
1659         return QMimeType();
1660     }
1661 
1662     if (!d->m_mimeType.isValid()) {
1663         // On-demand fast (but not always accurate) MIME type determination
1664         QMimeDatabase db;
1665         if (isDir()) {
1666             d->m_mimeType = db.mimeTypeForName(QStringLiteral("inode/directory"));
1667             return d->m_mimeType;
1668         }
1669         const QUrl url = mostLocalUrl();
1670         if (d->m_delayedMimeTypes) {
1671             const QList<QMimeType> mimeTypes = db.mimeTypesForFileName(url.path());
1672             if (mimeTypes.isEmpty()) {
1673                 d->m_mimeType = db.mimeTypeForName(QStringLiteral("application/octet-stream"));
1674                 d->m_bMimeTypeKnown = false;
1675             } else {
1676                 d->m_mimeType = mimeTypes.first();
1677                 // If there were conflicting globs. determineMimeType will be able to do better.
1678                 d->m_bMimeTypeKnown = (mimeTypes.count() == 1);
1679             }
1680         } else {
1681             // ## d->m_fileMode isn't used anymore (for remote urls)
1682             d->determineMimeTypeHelper(url);
1683             d->m_bMimeTypeKnown = true;
1684         }
1685     }
1686     return d->m_mimeType;
1687 }
1688 
1689 KIO::UDSEntry KFileItem::entry() const
1690 {
1691     if (!d) {
1692         return KIO::UDSEntry();
1693     }
1694 
1695     d->ensureInitialized();
1696 
1697     return d->m_entry;
1698 }
1699 
1700 bool KFileItem::isNull() const
1701 {
1702     return d == nullptr;
1703 }
1704 
1705 bool KFileItem::exists() const
1706 {
1707     if (!d) {
1708         return false;
1709     }
1710     if (!d->m_bInitCalled) {
1711         qCWarning(KIO_CORE) << "KFileItem: exists called when not initialised" << d->m_url;
1712         return false;
1713     }
1714     return d->m_fileMode != KFileItem::Unknown;
1715 }
1716 
1717 bool KFileItem::isExecutable() const
1718 {
1719     if (!d) {
1720         return false;
1721     }
1722 
1723     d->ensureInitialized();
1724 
1725     if (d->m_permissions == KFileItem::Unknown) {
1726         return false;
1727     }
1728 
1729     const mode_t executableMask = S_IXGRP | S_IXUSR | S_IXOTH;
1730     if ((d->m_permissions & executableMask) == 0) {
1731         return false;
1732     }
1733 
1734 #ifndef Q_OS_WIN
1735     const auto uid = userId();
1736     if (uid != -1) {
1737         if (((uint)uid) == KUserId::currentUserId().nativeId()) {
1738             return S_IXUSR & d->m_permissions;
1739         }
1740         const auto gid = groupId();
1741         if (gid != -1) {
1742             const KUser kuser = KUser(uid);
1743             if (kuser.groups().contains(KUserGroup(gid))) {
1744                 return S_IXGRP & d->m_permissions;
1745             }
1746 
1747             return S_IXOTH & d->m_permissions;
1748         }
1749     }
1750     return false;
1751 #else
1752     // simple special case
1753     return S_IXUSR & d->m_permissions;
1754 #endif
1755 }
1756 
1757 KFileItemList::KFileItemList()
1758 {
1759 }
1760 
1761 KFileItemList::KFileItemList(const QList<KFileItem> &items)
1762     : QList<KFileItem>(items)
1763 {
1764 }
1765 
1766 KFileItemList::KFileItemList(std::initializer_list<KFileItem> items)
1767     : QList<KFileItem>(items)
1768 {
1769 }
1770 
1771 KFileItem KFileItemList::findByName(const QString &fileName) const
1772 {
1773     auto it = std::find_if(cbegin(), cend(), [&fileName](const KFileItem &item) {
1774         return item.name() == fileName;
1775     });
1776 
1777     return it != cend() ? *it : KFileItem();
1778 }
1779 
1780 KFileItem KFileItemList::findByUrl(const QUrl &url) const
1781 {
1782     auto it = std::find_if(cbegin(), cend(), [&url](const KFileItem &item) {
1783         return item.url() == url;
1784     });
1785 
1786     return it != cend() ? *it : KFileItem();
1787 }
1788 
1789 QList<QUrl> KFileItemList::urlList() const
1790 {
1791     QList<QUrl> lst;
1792     lst.reserve(size());
1793 
1794     for (const auto &item : *this) {
1795         lst.append(item.url());
1796     }
1797     return lst;
1798 }
1799 
1800 QList<QUrl> KFileItemList::targetUrlList() const
1801 {
1802     QList<QUrl> lst;
1803     lst.reserve(size());
1804 
1805     for (const auto &item : *this) {
1806         lst.append(item.targetUrl());
1807     }
1808     return lst;
1809 }
1810 
1811 bool KFileItem::isDesktopFile() const
1812 {
1813     return checkDesktopFile(*this, true);
1814 }
1815 
1816 bool KFileItem::isRegularFile() const
1817 {
1818     if (!d) {
1819         return false;
1820     }
1821 
1822     d->ensureInitialized();
1823 
1824     return Utils::isRegFileMask(d->m_fileMode);
1825 }
1826 
1827 QString KFileItem::suffix() const
1828 {
1829     if (!d || isDir()) {
1830         return QString();
1831     }
1832 
1833     const int lastDot = d->m_strText.lastIndexOf(QStringLiteral("."));
1834     if (lastDot > 0) {
1835         return d->m_strText.mid(lastDot + 1);
1836     } else {
1837         return QString();
1838     }
1839 }
1840 
1841 QDebug operator<<(QDebug stream, const KFileItem &item)
1842 {
1843     QDebugStateSaver saver(stream);
1844     stream.nospace();
1845     if (item.isNull()) {
1846         stream << "[null KFileItem]";
1847     } else {
1848         stream << "[KFileItem for " << item.url() << "]";
1849     }
1850     return stream;
1851 }
1852 
1853 #include "moc_kfileitem.cpp"