File indexing completed on 2024-04-28 11:41:01

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 ioslaves/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             m_entry.replace(KIO::UDSEntry::UDS_USER, KUser(buf.st_uid).loginName());
0251             m_entry.replace(KIO::UDSEntry::UDS_GROUP, KUserGroup(buf.st_gid).name());
0252 #endif
0253 
0254             // TODO: these can be removed, we can use UDS_FILE_TYPE and UDS_ACCESS everywhere
0255             if (m_fileMode == KFileItem::Unknown) {
0256                 m_fileMode = type; // extract file type
0257             }
0258             if (m_permissions == KFileItem::Unknown) {
0259                 m_permissions = mode & 07777; // extract permissions
0260             }
0261 
0262 #if HAVE_POSIX_ACL
0263             if (m_addACL) {
0264                 appendACLAtoms(pathBA, m_entry, type);
0265             }
0266 #endif
0267         }
0268     }
0269 
0270     m_bInitCalled = true;
0271 }
0272 
0273 void KFileItemPrivate::readUDSEntry(bool _urlIsDirectory)
0274 {
0275     // extract fields from the KIO::UDS Entry
0276 
0277     m_fileMode = m_entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE, KFileItem::Unknown);
0278     m_permissions = m_entry.numberValue(KIO::UDSEntry::UDS_ACCESS, KFileItem::Unknown);
0279     m_strName = m_entry.stringValue(KIO::UDSEntry::UDS_NAME);
0280 
0281     const QString displayName = m_entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME);
0282     if (!displayName.isEmpty()) {
0283         m_strText = displayName;
0284     } else {
0285         m_strText = KIO::decodeFileName(m_strName);
0286     }
0287 
0288     const QString urlStr = m_entry.stringValue(KIO::UDSEntry::UDS_URL);
0289     const bool UDS_URL_seen = !urlStr.isEmpty();
0290     if (UDS_URL_seen) {
0291         m_url = QUrl(urlStr);
0292         if (m_url.isLocalFile()) {
0293             m_bIsLocalUrl = true;
0294         }
0295     }
0296     QMimeDatabase db;
0297     const QString mimeTypeStr = m_entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE);
0298     m_bMimeTypeKnown = !mimeTypeStr.isEmpty();
0299     if (m_bMimeTypeKnown) {
0300         m_mimeType = db.mimeTypeForName(mimeTypeStr);
0301     }
0302 
0303     m_guessedMimeType = m_entry.stringValue(KIO::UDSEntry::UDS_GUESSED_MIME_TYPE);
0304     m_bLink = !m_entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST).isEmpty(); // we don't store the link dest
0305 
0306     const int hiddenVal = m_entry.numberValue(KIO::UDSEntry::UDS_HIDDEN, -1);
0307     m_hidden = hiddenVal == 1 ? Hidden : (hiddenVal == 0 ? Shown : Auto);
0308 
0309     if (_urlIsDirectory && !UDS_URL_seen && !m_strName.isEmpty() && m_strName != QLatin1String(".")) {
0310         m_url.setPath(Utils::concatPaths(m_url.path(), m_strName));
0311     }
0312 
0313     m_iconName.clear();
0314 }
0315 
0316 // Inlined because it is used only in one place
0317 inline KIO::filesize_t KFileItemPrivate::size() const
0318 {
0319     ensureInitialized();
0320 
0321     // Extract it from the KIO::UDSEntry
0322     long long fieldVal = m_entry.numberValue(KIO::UDSEntry::UDS_SIZE, -1);
0323     if (fieldVal != -1) {
0324         return fieldVal;
0325     }
0326 
0327     // If not in the KIO::UDSEntry, or if UDSEntry empty, use stat() [if local URL]
0328     if (m_bIsLocalUrl) {
0329         return QFileInfo(m_url.toLocalFile()).size();
0330     }
0331     return 0;
0332 }
0333 
0334 KIO::filesize_t KFileItemPrivate::recursiveSize() const
0335 {
0336     // Extract it from the KIO::UDSEntry
0337     long long fieldVal = m_entry.numberValue(KIO::UDSEntry::UDS_RECURSIVE_SIZE, -1);
0338     if (fieldVal != -1) {
0339         return static_cast<KIO::filesize_t>(fieldVal);
0340     }
0341 
0342     return 0;
0343 }
0344 
0345 static uint udsFieldForTime(KFileItem::FileTimes mappedWhich)
0346 {
0347     switch (mappedWhich) {
0348     case KFileItem::ModificationTime:
0349         return KIO::UDSEntry::UDS_MODIFICATION_TIME;
0350     case KFileItem::AccessTime:
0351         return KIO::UDSEntry::UDS_ACCESS_TIME;
0352     case KFileItem::CreationTime:
0353         return KIO::UDSEntry::UDS_CREATION_TIME;
0354     }
0355     return 0;
0356 }
0357 
0358 void KFileItemPrivate::setTime(KFileItem::FileTimes mappedWhich, uint time_t_val) const
0359 {
0360     m_entry.replace(udsFieldForTime(mappedWhich), time_t_val);
0361 }
0362 
0363 void KFileItemPrivate::setTime(KFileItem::FileTimes mappedWhich, const QDateTime &val) const
0364 {
0365     const QDateTime dt = val.toLocalTime(); // #160979
0366     setTime(mappedWhich, dt.toSecsSinceEpoch());
0367 }
0368 
0369 QDateTime KFileItemPrivate::time(KFileItem::FileTimes mappedWhich) const
0370 {
0371     ensureInitialized();
0372 
0373     // Extract it from the KIO::UDSEntry
0374     const uint uds = udsFieldForTime(mappedWhich);
0375     if (uds > 0) {
0376         const long long fieldVal = m_entry.numberValue(uds, -1);
0377         if (fieldVal != -1) {
0378             return QDateTime::fromSecsSinceEpoch(fieldVal);
0379         }
0380     }
0381 
0382     return QDateTime();
0383 }
0384 
0385 void KFileItemPrivate::printCompareDebug(const KFileItemPrivate &item) const
0386 {
0387     Q_UNUSED(item);
0388 
0389 #if KFILEITEM_DEBUG
0390     const KIO::UDSEntry &otherEntry = item.m_entry;
0391 
0392     qDebug() << "Comparing" << m_url << "and" << item.m_url;
0393     qDebug() << " name" << (m_strName == item.m_strName);
0394     qDebug() << " local" << (m_bIsLocalUrl == item.m_bIsLocalUrl);
0395 
0396     qDebug() << " mode" << (m_fileMode == item.m_fileMode);
0397     qDebug() << " perm" << (m_permissions == item.m_permissions);
0398     qDebug() << " group" << (m_entry.stringValue(KIO::UDSEntry::UDS_GROUP) == otherEntry.stringValue(KIO::UDSEntry::UDS_GROUP));
0399     qDebug() << " user" << (m_entry.stringValue(KIO::UDSEntry::UDS_USER) == otherEntry.stringValue(KIO::UDSEntry::UDS_USER));
0400 
0401     qDebug() << " UDS_EXTENDED_ACL" << (m_entry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL) == otherEntry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL));
0402     qDebug() << " UDS_ACL_STRING" << (m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING) == otherEntry.stringValue(KIO::UDSEntry::UDS_ACL_STRING));
0403     qDebug() << " UDS_DEFAULT_ACL_STRING"
0404              << (m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING) == otherEntry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING));
0405 
0406     qDebug() << " m_bLink" << (m_bLink == item.m_bLink);
0407     qDebug() << " m_hidden" << (m_hidden == item.m_hidden);
0408 
0409     qDebug() << " size" << (size() == item.size());
0410 
0411     qDebug() << " ModificationTime" << m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME)
0412              << otherEntry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME);
0413 
0414     qDebug() << " UDS_ICON_NAME" << (m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME) == otherEntry.stringValue(KIO::UDSEntry::UDS_ICON_NAME));
0415 #endif
0416 }
0417 
0418 // Inlined because it is used only in one place
0419 inline bool KFileItemPrivate::cmp(const KFileItemPrivate &item) const
0420 {
0421     if (item.m_bInitCalled) {
0422         ensureInitialized();
0423     }
0424 
0425     if (m_bInitCalled) {
0426         item.ensureInitialized();
0427     }
0428 
0429 #if KFILEITEM_DEBUG
0430     printCompareDebug(item);
0431 #endif
0432 
0433     /* clang-format off */
0434     return (m_strName == item.m_strName
0435             && m_bIsLocalUrl == item.m_bIsLocalUrl
0436             && m_fileMode == item.m_fileMode
0437             && m_permissions == item.m_permissions
0438             && m_entry.stringValue(KIO::UDSEntry::UDS_GROUP) == item.m_entry.stringValue(KIO::UDSEntry::UDS_GROUP)
0439             && m_entry.stringValue(KIO::UDSEntry::UDS_USER) == item.m_entry.stringValue(KIO::UDSEntry::UDS_USER)
0440             && m_entry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL) == item.m_entry.stringValue(KIO::UDSEntry::UDS_EXTENDED_ACL)
0441             && m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING) == item.m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING)
0442             && m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING) == item.m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING)
0443             && m_bLink == item.m_bLink
0444             && m_hidden == item.m_hidden
0445             && size() == item.size()
0446             && m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME) == item.m_entry.numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME)
0447             && m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME) == item.m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME)
0448             && m_entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL) == item.m_entry.stringValue(KIO::UDSEntry::UDS_TARGET_URL)
0449             && m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH) == item.m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH));
0450     /* clang-format on */
0451     // Don't compare the MIME types here. They might not be known, and we don't want to
0452     // do the slow operation of determining them here.
0453 }
0454 
0455 // Inlined because it is used only in one place
0456 inline QString KFileItemPrivate::parsePermissions(mode_t perm) const
0457 {
0458     ensureInitialized();
0459 
0460     static char buffer[12];
0461 
0462     char uxbit;
0463     char gxbit;
0464     char oxbit;
0465 
0466     if ((perm & (S_IXUSR | S_ISUID)) == (S_IXUSR | S_ISUID)) {
0467         uxbit = 's';
0468     } else if ((perm & (S_IXUSR | S_ISUID)) == S_ISUID) {
0469         uxbit = 'S';
0470     } else if ((perm & (S_IXUSR | S_ISUID)) == S_IXUSR) {
0471         uxbit = 'x';
0472     } else {
0473         uxbit = '-';
0474     }
0475 
0476     if ((perm & (S_IXGRP | S_ISGID)) == (S_IXGRP | S_ISGID)) {
0477         gxbit = 's';
0478     } else if ((perm & (S_IXGRP | S_ISGID)) == S_ISGID) {
0479         gxbit = 'S';
0480     } else if ((perm & (S_IXGRP | S_ISGID)) == S_IXGRP) {
0481         gxbit = 'x';
0482     } else {
0483         gxbit = '-';
0484     }
0485 
0486     if ((perm & (S_IXOTH | S_ISVTX)) == (S_IXOTH | S_ISVTX)) {
0487         oxbit = 't';
0488     } else if ((perm & (S_IXOTH | S_ISVTX)) == S_ISVTX) {
0489         oxbit = 'T';
0490     } else if ((perm & (S_IXOTH | S_ISVTX)) == S_IXOTH) {
0491         oxbit = 'x';
0492     } else {
0493         oxbit = '-';
0494     }
0495 
0496     // Include the type in the first char like ls does; people are more used to seeing it,
0497     // even though it's not really part of the permissions per se.
0498     if (m_bLink) {
0499         buffer[0] = 'l';
0500     } else if (m_fileMode != KFileItem::Unknown) {
0501         if (Utils::isDirMask(m_fileMode)) {
0502             buffer[0] = 'd';
0503         }
0504 #ifdef Q_OS_UNIX
0505         else if (S_ISSOCK(m_fileMode)) {
0506             buffer[0] = 's';
0507         } else if (S_ISCHR(m_fileMode)) {
0508             buffer[0] = 'c';
0509         } else if (S_ISBLK(m_fileMode)) {
0510             buffer[0] = 'b';
0511         } else if (S_ISFIFO(m_fileMode)) {
0512             buffer[0] = 'p';
0513         }
0514 #endif // Q_OS_UNIX
0515         else {
0516             buffer[0] = '-';
0517         }
0518     } else {
0519         buffer[0] = '-';
0520     }
0521 
0522     buffer[1] = (((perm & S_IRUSR) == S_IRUSR) ? 'r' : '-');
0523     buffer[2] = (((perm & S_IWUSR) == S_IWUSR) ? 'w' : '-');
0524     buffer[3] = uxbit;
0525     buffer[4] = (((perm & S_IRGRP) == S_IRGRP) ? 'r' : '-');
0526     buffer[5] = (((perm & S_IWGRP) == S_IWGRP) ? 'w' : '-');
0527     buffer[6] = gxbit;
0528     buffer[7] = (((perm & S_IROTH) == S_IROTH) ? 'r' : '-');
0529     buffer[8] = (((perm & S_IWOTH) == S_IWOTH) ? 'w' : '-');
0530     buffer[9] = oxbit;
0531     // if (hasExtendedACL())
0532     if (m_entry.contains(KIO::UDSEntry::UDS_EXTENDED_ACL)) {
0533         buffer[10] = '+';
0534         buffer[11] = 0;
0535     } else {
0536         buffer[10] = 0;
0537     }
0538 
0539     return QString::fromLatin1(buffer);
0540 }
0541 
0542 void KFileItemPrivate::determineMimeTypeHelper(const QUrl &url) const
0543 {
0544     QMimeDatabase db;
0545     if (m_bSkipMimeTypeFromContent) {
0546         const QString scheme = url.scheme();
0547         if (scheme.startsWith(QLatin1String("http")) || scheme == QLatin1String("mailto")) {
0548             m_mimeType = db.mimeTypeForName(QLatin1String("application/octet-stream"));
0549         } else {
0550             m_mimeType = db.mimeTypeForFile(url.path(), QMimeDatabase::MatchMode::MatchExtension);
0551         }
0552     } else {
0553         m_mimeType = db.mimeTypeForUrl(url);
0554     }
0555 }
0556 
0557 ///////
0558 
0559 KFileItem::KFileItem()
0560     : d(nullptr)
0561 {
0562 }
0563 
0564 KFileItem::KFileItem(const KIO::UDSEntry &entry, const QUrl &itemOrDirUrl, bool delayedMimeTypes, bool urlIsDirectory)
0565     : d(new KFileItemPrivate(entry,
0566                              KFileItem::Unknown,
0567                              KFileItem::Unknown,
0568                              itemOrDirUrl,
0569                              urlIsDirectory,
0570                              delayedMimeTypes,
0571                              KFileItem::NormalMimeTypeDetermination))
0572 {
0573 }
0574 
0575 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 0)
0576 KFileItem::KFileItem(mode_t mode, mode_t permissions, const QUrl &url, bool delayedMimeTypes)
0577     : d(new KFileItemPrivate(KIO::UDSEntry(), mode, permissions, url, false, delayedMimeTypes, KFileItem::NormalMimeTypeDetermination))
0578 {
0579 }
0580 #endif
0581 
0582 KFileItem::KFileItem(const QUrl &url, const QString &mimeType, mode_t mode)
0583     : d(new KFileItemPrivate(KIO::UDSEntry(), mode, KFileItem::Unknown, url, false, false, KFileItem::NormalMimeTypeDetermination))
0584 {
0585     d->m_bMimeTypeKnown = !mimeType.simplified().isEmpty();
0586     if (d->m_bMimeTypeKnown) {
0587         QMimeDatabase db;
0588         d->m_mimeType = db.mimeTypeForName(mimeType);
0589     }
0590 }
0591 
0592 KFileItem::KFileItem(const QUrl &url, KFileItem::MimeTypeDetermination mimeTypeDetermination)
0593     : d(new KFileItemPrivate(KIO::UDSEntry(), KFileItem::Unknown, KFileItem::Unknown, url, false, false, mimeTypeDetermination))
0594 {
0595 }
0596 
0597 // Default implementations for:
0598 // - Copy constructor
0599 // - Move constructor
0600 // - Copy assignment
0601 // - Move assignment
0602 // - Destructor
0603 // The compiler will now generate the content of those.
0604 KFileItem::KFileItem(const KFileItem &) = default;
0605 KFileItem::~KFileItem() = default;
0606 KFileItem::KFileItem(KFileItem &&) = default;
0607 KFileItem &KFileItem::operator=(const KFileItem &) = default;
0608 KFileItem &KFileItem::operator=(KFileItem &&) = default;
0609 
0610 void KFileItem::refresh()
0611 {
0612     if (!d) {
0613         qCWarning(KIO_CORE) << "null item";
0614         return;
0615     }
0616 
0617     d->m_fileMode = KFileItem::Unknown;
0618     d->m_permissions = KFileItem::Unknown;
0619     d->m_hidden = KFileItemPrivate::Auto;
0620     refreshMimeType();
0621 
0622 #if HAVE_POSIX_ACL
0623     // If the item had ACL, re-add them in init()
0624     d->m_addACL = !d->m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING).isEmpty();
0625 #endif
0626 
0627     // Basically, we can't trust any information we got while listing.
0628     // Everything could have changed...
0629     // Clearing m_entry makes it possible to detect changes in the size of the file,
0630     // the time information, etc.
0631     d->m_entry.clear();
0632     d->init(); // re-populates d->m_entry
0633 }
0634 
0635 void KFileItem::refreshMimeType()
0636 {
0637     if (!d) {
0638         return;
0639     }
0640 
0641     d->m_mimeType = QMimeType();
0642     d->m_bMimeTypeKnown = false;
0643     d->m_iconName.clear();
0644 }
0645 
0646 void KFileItem::setDelayedMimeTypes(bool b)
0647 {
0648     if (!d) {
0649         return;
0650     }
0651     d->m_delayedMimeTypes = b;
0652 }
0653 
0654 void KFileItem::setUrl(const QUrl &url)
0655 {
0656     if (!d) {
0657         qCWarning(KIO_CORE) << "null item";
0658         return;
0659     }
0660 
0661     d->m_url = url;
0662     setName(url.fileName());
0663 }
0664 
0665 void KFileItem::setLocalPath(const QString &path)
0666 {
0667     if (!d) {
0668         qCWarning(KIO_CORE) << "null item";
0669         return;
0670     }
0671 
0672     d->m_entry.replace(KIO::UDSEntry::UDS_LOCAL_PATH, path);
0673 }
0674 
0675 void KFileItem::setName(const QString &name)
0676 {
0677     if (!d) {
0678         qCWarning(KIO_CORE) << "null item";
0679         return;
0680     }
0681 
0682     d->ensureInitialized();
0683 
0684     d->m_strName = name;
0685     if (!d->m_strName.isEmpty()) {
0686         d->m_strText = KIO::decodeFileName(d->m_strName);
0687     }
0688     if (d->m_entry.contains(KIO::UDSEntry::UDS_NAME)) {
0689         d->m_entry.replace(KIO::UDSEntry::UDS_NAME, d->m_strName); // #195385
0690     }
0691 }
0692 
0693 QString KFileItem::linkDest() const
0694 {
0695     if (!d) {
0696         return QString();
0697     }
0698 
0699     d->ensureInitialized();
0700 
0701     // Extract it from the KIO::UDSEntry
0702     const QString linkStr = d->m_entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST);
0703     if (!linkStr.isEmpty()) {
0704         return linkStr;
0705     }
0706 
0707     // If not in the KIO::UDSEntry, or if UDSEntry empty, use readlink() [if local URL]
0708     if (d->m_bIsLocalUrl) {
0709         return QFile::symLinkTarget(d->m_url.adjusted(QUrl::StripTrailingSlash).toLocalFile());
0710     }
0711     return QString();
0712 }
0713 
0714 QString KFileItemPrivate::localPath() const
0715 {
0716     if (m_bIsLocalUrl) {
0717         return m_url.toLocalFile();
0718     }
0719 
0720     ensureInitialized();
0721 
0722     // Extract the local path from the KIO::UDSEntry
0723     return m_entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
0724 }
0725 
0726 QString KFileItem::localPath() const
0727 {
0728     if (!d) {
0729         return QString();
0730     }
0731 
0732     return d->localPath();
0733 }
0734 
0735 KIO::filesize_t KFileItem::size() const
0736 {
0737     if (!d) {
0738         return 0;
0739     }
0740 
0741     return d->size();
0742 }
0743 
0744 KIO::filesize_t KFileItem::recursiveSize() const
0745 {
0746     if (!d) {
0747         return 0;
0748     }
0749 
0750     return d->recursiveSize();
0751 }
0752 
0753 bool KFileItem::hasExtendedACL() const
0754 {
0755     if (!d) {
0756         return false;
0757     }
0758 
0759     // Check if the field exists; its value doesn't matter
0760     return entry().contains(KIO::UDSEntry::UDS_EXTENDED_ACL);
0761 }
0762 
0763 KACL KFileItem::ACL() const
0764 {
0765     if (!d) {
0766         return KACL();
0767     }
0768 
0769     if (hasExtendedACL()) {
0770         // Extract it from the KIO::UDSEntry
0771         const QString fieldVal = d->m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING);
0772         if (!fieldVal.isEmpty()) {
0773             return KACL(fieldVal);
0774         }
0775     }
0776 
0777     // create one from the basic permissions
0778     return KACL(d->m_permissions);
0779 }
0780 
0781 KACL KFileItem::defaultACL() const
0782 {
0783     if (!d) {
0784         return KACL();
0785     }
0786 
0787     // Extract it from the KIO::UDSEntry
0788     const QString fieldVal = entry().stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING);
0789     if (!fieldVal.isEmpty()) {
0790         return KACL(fieldVal);
0791     } else {
0792         return KACL();
0793     }
0794 }
0795 
0796 QDateTime KFileItem::time(FileTimes which) const
0797 {
0798     if (!d) {
0799         return QDateTime();
0800     }
0801 
0802     return d->time(which);
0803 }
0804 
0805 QString KFileItem::user() const
0806 {
0807     if (!d) {
0808         return QString();
0809     }
0810 
0811     return entry().stringValue(KIO::UDSEntry::UDS_USER);
0812 }
0813 
0814 QString KFileItem::group() const
0815 {
0816     if (!d) {
0817         return QString();
0818     }
0819 
0820     return entry().stringValue(KIO::UDSEntry::UDS_GROUP);
0821 }
0822 
0823 bool KFileItemPrivate::isSlow() const
0824 {
0825     if (m_slow == SlowUnknown) {
0826         const QString path = localPath();
0827         if (!path.isEmpty()) {
0828             const KFileSystemType::Type fsType = KFileSystemType::fileSystemType(path);
0829             m_slow = (fsType == KFileSystemType::Nfs || fsType == KFileSystemType::Smb) ? Slow : Fast;
0830         } else {
0831             m_slow = Slow;
0832         }
0833     }
0834     return m_slow == Slow;
0835 }
0836 
0837 bool KFileItem::isSlow() const
0838 {
0839     if (!d) {
0840         return false;
0841     }
0842 
0843     return d->isSlow();
0844 }
0845 
0846 QString KFileItem::mimetype() const
0847 {
0848     if (!d) {
0849         return QString();
0850     }
0851 
0852     KFileItem *that = const_cast<KFileItem *>(this);
0853     return that->determineMimeType().name();
0854 }
0855 
0856 QMimeType KFileItem::determineMimeType() const
0857 {
0858     if (!d) {
0859         return QMimeType();
0860     }
0861 
0862     if (!d->m_mimeType.isValid() || !d->m_bMimeTypeKnown) {
0863         QMimeDatabase db;
0864         if (isDir()) {
0865             d->m_mimeType = db.mimeTypeForName(QStringLiteral("inode/directory"));
0866         } else {
0867             const auto [url, isLocalUrl] = isMostLocalUrl();
0868             d->determineMimeTypeHelper(url);
0869 
0870             // was:  d->m_mimeType = KMimeType::findByUrl( url, d->m_fileMode, isLocalUrl );
0871             // => we are no longer using d->m_fileMode for remote URLs.
0872             Q_ASSERT(d->m_mimeType.isValid());
0873             // qDebug() << d << "finding final MIME type for" << url << ":" << d->m_mimeType.name();
0874         }
0875         d->m_bMimeTypeKnown = true;
0876     }
0877 
0878     if (d->m_delayedMimeTypes) { // if we delayed getting the iconName up till now, this is the right point in time to do so
0879         d->m_delayedMimeTypes = false;
0880         d->m_useIconNameCache = false;
0881         (void)iconName();
0882     }
0883 
0884     return d->m_mimeType;
0885 }
0886 
0887 bool KFileItem::isMimeTypeKnown() const
0888 {
0889     if (!d) {
0890         return false;
0891     }
0892 
0893     // The MIME type isn't known if determineMimeType was never called (on-demand determination)
0894     // or if this fileitem has a guessed MIME type (e.g. ftp symlink) - in which case
0895     // it always remains "not fully determined"
0896     return d->m_bMimeTypeKnown && d->m_guessedMimeType.isEmpty();
0897 }
0898 
0899 static bool isDirectoryMounted(const QUrl &url)
0900 {
0901     // Stating .directory files can cause long freezes when e.g. /home
0902     // uses autofs for every user's home directory, i.e. opening /home
0903     // in a file dialog will mount every single home directory.
0904     // These non-mounted directories can be identified by having 0 size.
0905     // There are also other directories with 0 size, such as /proc, that may
0906     // be mounted, but those are unlikely to contain .directory (and checking
0907     // this would require checking with KMountPoint).
0908 
0909     // TODO: maybe this could be checked with KFileSystemType instead?
0910     QFileInfo info(url.toLocalFile());
0911     if (info.isDir() && info.size() == 0) {
0912         return false;
0913     }
0914     return true;
0915 }
0916 
0917 bool KFileItem::isFinalIconKnown() const
0918 {
0919     if (!d) {
0920         return false;
0921     }
0922     return d->m_bMimeTypeKnown && (!d->m_delayedMimeTypes);
0923 }
0924 
0925 // KDE5 TODO: merge with comment()? Need to see what lxr says about the usage of both.
0926 QString KFileItem::mimeComment() const
0927 {
0928     if (!d) {
0929         return QString();
0930     }
0931 
0932     const QString displayType = d->m_entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_TYPE);
0933     if (!displayType.isEmpty()) {
0934         return displayType;
0935     }
0936 
0937     const auto [url, isLocalUrl] = isMostLocalUrl();
0938 
0939     QMimeType mime = currentMimeType();
0940     // This cannot move to kio_file (with UDS_DISPLAY_TYPE) because it needs
0941     // the MIME type to be determined, which is done here, and possibly delayed...
0942     if (isLocalUrl && !d->isSlow() && mime.inherits(QStringLiteral("application/x-desktop"))) {
0943         KDesktopFile cfg(url.toLocalFile());
0944         QString comment = cfg.desktopGroup().readEntry("Comment");
0945         if (!comment.isEmpty()) {
0946             return comment;
0947         }
0948     }
0949 
0950     // Support for .directory file in directories
0951     if (isLocalUrl && isDir() && !d->isSlow() && isDirectoryMounted(url)) {
0952         QUrl u(url);
0953         u.setPath(Utils::concatPaths(u.path(), QStringLiteral(".directory")));
0954         const KDesktopFile cfg(u.toLocalFile());
0955         const QString comment = cfg.readComment();
0956         if (!comment.isEmpty()) {
0957             return comment;
0958         }
0959     }
0960 
0961     const QString comment = mime.comment();
0962     // qDebug() << "finding comment for " << url.url() << " : " << d->m_mimeType->name();
0963     if (!comment.isEmpty()) {
0964         return comment;
0965     } else {
0966         return mime.name();
0967     }
0968 }
0969 
0970 static QString iconFromDirectoryFile(const QString &path)
0971 {
0972     const QString filePath = path + QLatin1String("/.directory");
0973     if (!QFileInfo(filePath).isFile()) { // exists -and- is a file
0974         return QString();
0975     }
0976 
0977     KDesktopFile cfg(filePath);
0978     QString icon = cfg.readIcon();
0979 
0980     const KConfigGroup group = cfg.desktopGroup();
0981     const QString emptyIcon = group.readEntry("EmptyIcon");
0982     if (!emptyIcon.isEmpty()) {
0983         bool isDirEmpty = true;
0984         QDirIterator dirIt(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
0985         while (dirIt.hasNext()) {
0986             dirIt.next();
0987             if (dirIt.fileName() != QLatin1String(".directory")) {
0988                 isDirEmpty = false;
0989                 break;
0990             }
0991         }
0992         if (isDirEmpty) {
0993             icon = emptyIcon;
0994         }
0995     }
0996 
0997     if (icon.startsWith(QLatin1String("./"))) {
0998         // path is relative with respect to the location of the .directory file (#73463)
0999         return path + QStringView(icon).mid(1);
1000     }
1001     return icon;
1002 }
1003 
1004 static QString iconFromDesktopFile(const QString &path)
1005 {
1006     KDesktopFile cfg(path);
1007     const QString icon = cfg.readIcon();
1008     if (cfg.hasLinkType()) {
1009         const KConfigGroup group = cfg.desktopGroup();
1010         const QString emptyIcon = group.readEntry("EmptyIcon");
1011         if (!emptyIcon.isEmpty()) {
1012             const QString u = cfg.readUrl();
1013             const QUrl url(u);
1014             if (url.scheme() == QLatin1String("trash")) {
1015                 // We need to find if the trash is empty, preferably without using a KIO job.
1016                 // So instead kio_trash leaves an entry in its config file for us.
1017                 KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig);
1018                 if (trashConfig.group("Status").readEntry("Empty", true)) {
1019                     return emptyIcon;
1020                 }
1021             }
1022         }
1023     }
1024     return icon;
1025 }
1026 
1027 QString KFileItem::iconName() const
1028 {
1029     if (!d) {
1030         return QString();
1031     }
1032 
1033     if (d->m_useIconNameCache && !d->m_iconName.isEmpty()) {
1034         return d->m_iconName;
1035     }
1036 
1037     d->m_iconName = d->m_entry.stringValue(KIO::UDSEntry::UDS_ICON_NAME);
1038     if (!d->m_iconName.isEmpty()) {
1039         d->m_useIconNameCache = d->m_bMimeTypeKnown;
1040         return d->m_iconName;
1041     }
1042 
1043     const auto [url, isLocalUrl] = isMostLocalUrl();
1044 
1045     QMimeDatabase db;
1046     QMimeType mime;
1047     // Use guessed MIME type for the icon
1048     if (!d->m_guessedMimeType.isEmpty()) {
1049         mime = db.mimeTypeForName(d->m_guessedMimeType);
1050     } else {
1051         mime = currentMimeType();
1052     }
1053 
1054     const bool delaySlowOperations = d->m_delayedMimeTypes;
1055 
1056     if (isLocalUrl && !delaySlowOperations) {
1057         const QString &localFile = url.toLocalFile();
1058 
1059         if (mime.inherits(QStringLiteral("application/x-desktop"))) {
1060             d->m_iconName = iconFromDesktopFile(localFile);
1061             if (!d->m_iconName.isEmpty()) {
1062                 d->m_useIconNameCache = d->m_bMimeTypeKnown;
1063                 return d->m_iconName;
1064             }
1065         }
1066 
1067         if (isDir()) {
1068             if (isDirectoryMounted(url)) {
1069                 d->m_iconName = iconFromDirectoryFile(localFile);
1070                 if (!d->m_iconName.isEmpty()) {
1071                     d->m_useIconNameCache = d->m_bMimeTypeKnown;
1072                     return d->m_iconName;
1073                 }
1074             }
1075 
1076             d->m_iconName = KIOPrivate::iconForStandardPath(localFile);
1077             if (!d->m_iconName.isEmpty()) {
1078                 d->m_useIconNameCache = d->m_bMimeTypeKnown;
1079                 return d->m_iconName;
1080             }
1081         }
1082     }
1083 
1084     d->m_iconName = mime.iconName();
1085     d->m_useIconNameCache = d->m_bMimeTypeKnown;
1086     return d->m_iconName;
1087 }
1088 
1089 /**
1090  * Returns true if this is a desktop file.
1091  * MIME type determination is optional.
1092  */
1093 static bool checkDesktopFile(const KFileItem &item, bool _determineMimeType)
1094 {
1095     // Only local files
1096     if (!item.isMostLocalUrl().local) {
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 
1115 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         const auto [url, isLocalUrl] = isMostLocalUrl();
1165         if (isLocalUrl) {
1166             const QString path = url.toLocalFile();
1167             if (KSambaShare::instance()->isDirectoryShared(path) || KNFSShare::instance()->isDirectoryShared(path)) {
1168                 names.append(QStringLiteral("emblem-shared"));
1169             }
1170         }
1171     }
1172 #endif // Q_OS_WIN
1173 
1174     return names;
1175 }
1176 
1177 QString KFileItem::comment() const
1178 {
1179     if (!d) {
1180         return QString();
1181     }
1182 
1183     return d->m_entry.stringValue(KIO::UDSEntry::UDS_COMMENT);
1184 }
1185 
1186 bool KFileItem::isReadable() const
1187 {
1188     if (!d) {
1189         return false;
1190     }
1191 
1192     d->ensureInitialized();
1193 
1194     /*
1195       struct passwd * user = getpwuid( geteuid() );
1196       bool isMyFile = (QString::fromLocal8Bit(user->pw_name) == d->m_user);
1197       // This gets ugly for the group....
1198       // Maybe we want a static QString for the user and a static QStringList
1199       // for the groups... then we need to handle the deletion properly...
1200       */
1201 
1202     if (d->m_permissions != KFileItem::Unknown) {
1203         const mode_t readMask = S_IRUSR | S_IRGRP | S_IROTH;
1204         // No read permission at all
1205         if ((d->m_permissions & readMask) == 0) {
1206             return false;
1207         }
1208 
1209         // Read permissions for all: save a stat call
1210         if ((d->m_permissions & readMask) == readMask) {
1211             return true;
1212         }
1213     }
1214 
1215     // Or if we can't read it - not network transparent
1216     if (d->m_bIsLocalUrl && !QFileInfo(d->m_url.toLocalFile()).isReadable()) {
1217         return false;
1218     }
1219 
1220     return true;
1221 }
1222 
1223 bool KFileItem::isWritable() const
1224 {
1225     if (!d) {
1226         return false;
1227     }
1228 
1229     d->ensureInitialized();
1230 
1231     /*
1232       struct passwd * user = getpwuid( geteuid() );
1233       bool isMyFile = (QString::fromLocal8Bit(user->pw_name) == d->m_user);
1234       // This gets ugly for the group....
1235       // Maybe we want a static QString for the user and a static QStringList
1236       // for the groups... then we need to handle the deletion properly...
1237       */
1238 
1239     if (d->m_permissions != KFileItem::Unknown) {
1240         // No write permission at all
1241         if (!(S_IWUSR & d->m_permissions) && !(S_IWGRP & d->m_permissions) && !(S_IWOTH & d->m_permissions)) {
1242             return false;
1243         }
1244     }
1245 
1246     // Or if we can't write it - not network transparent
1247     if (d->m_bIsLocalUrl) {
1248         return QFileInfo(d->m_url.toLocalFile()).isWritable();
1249     } else {
1250         return KProtocolManager::supportsWriting(d->m_url);
1251     }
1252 }
1253 
1254 bool KFileItem::isHidden() const
1255 {
1256     if (!d) {
1257         return false;
1258     }
1259 
1260     // The KIO worker can specify explicitly that a file is hidden or shown
1261     if (d->m_hidden != KFileItemPrivate::Auto) {
1262         return d->m_hidden == KFileItemPrivate::Hidden;
1263     }
1264 
1265     // Prefer the filename that is part of the URL, in case the display name is different.
1266     QString fileName = d->m_url.fileName();
1267     if (fileName.isEmpty()) { // e.g. "trash:/"
1268         fileName = d->m_strName;
1269     }
1270     return fileName.length() > 1 && fileName[0] == QLatin1Char('.'); // Just "." is current directory, not hidden.
1271 }
1272 
1273 void KFileItem::setHidden()
1274 {
1275     if (d) {
1276         d->m_hidden = KFileItemPrivate::Hidden;
1277     }
1278 }
1279 
1280 bool KFileItem::isDir() const
1281 {
1282     if (!d) {
1283         return false;
1284     }
1285 
1286     if (d->m_bMimeTypeKnown && d->m_mimeType.isValid()) {
1287         return d->m_mimeType.inherits(QStringLiteral("inode/directory"));
1288     }
1289 
1290     if (d->m_bSkipMimeTypeFromContent) {
1291         return false;
1292     }
1293 
1294     d->ensureInitialized();
1295 
1296     if (d->m_fileMode == KFileItem::Unknown) {
1297         // Probably the file was deleted already, and KDirLister hasn't told the world yet.
1298         // qDebug() << d << url() << "can't say -> false";
1299         return false; // can't say for sure, so no
1300     }
1301     return Utils::isDirMask(d->m_fileMode);
1302 }
1303 
1304 bool KFileItem::isFile() const
1305 {
1306     if (!d) {
1307         return false;
1308     }
1309 
1310     return !isDir();
1311 }
1312 
1313 #if KIOCORE_BUILD_DEPRECATED_SINCE(4, 0)
1314 bool KFileItem::acceptsDrops() const
1315 {
1316     // A directory ?
1317     if (isDir()) {
1318         return isWritable();
1319     }
1320 
1321     // But only local .desktop files and executables
1322     if (!d->m_bIsLocalUrl) {
1323         return false;
1324     }
1325 
1326     if (mimetype() == QLatin1String("application/x-desktop")) {
1327         return true;
1328     }
1329 
1330     // Executable, shell script ... ?
1331     if (QFileInfo(d->m_url.toLocalFile()).isExecutable()) {
1332         return true;
1333     }
1334 
1335     return false;
1336 }
1337 #endif
1338 
1339 QString KFileItem::getStatusBarInfo() const
1340 {
1341     if (!d) {
1342         return QString();
1343     }
1344 
1345     auto toDisplayUrl = [this](const QUrl &url) {
1346         QString dest;
1347         if (url.isLocalFile()) {
1348             dest = KShell::tildeCollapse(url.toLocalFile());
1349         } else {
1350             dest = targetUrl().toDisplayString();
1351         }
1352         return dest;
1353     };
1354 
1355     QString text = d->m_strText;
1356     const QString comment = mimeComment();
1357 
1358     if (d->m_bLink) {
1359         text += QLatin1Char(' ');
1360         QString linkText = toDisplayUrl(QUrl::fromUserInput(linkDest()));
1361         if (comment.isEmpty()) {
1362             text += i18n("(Symbolic Link to %1)", linkText);
1363         } else {
1364             text += i18n("(%1, Link to %2)", comment, linkText);
1365         }
1366     } else if (targetUrl() != url()) {
1367         text += i18n(" (Points to %1)", toDisplayUrl(targetUrl()));
1368     } else if (Utils::isRegFileMask(d->m_fileMode)) {
1369         text += QStringLiteral(" (%1, %2)").arg(comment, KIO::convertSize(size()));
1370     } else {
1371         text += QStringLiteral(" (%1)").arg(comment);
1372     }
1373     return text;
1374 }
1375 
1376 bool KFileItem::cmp(const KFileItem &item) const
1377 {
1378     if (!d && !item.d) {
1379         return true;
1380     }
1381 
1382     if (!d || !item.d) {
1383         return false;
1384     }
1385 
1386     return d->cmp(*item.d);
1387 }
1388 
1389 bool KFileItem::operator==(const KFileItem &other) const
1390 {
1391     if (!d && !other.d) {
1392         return true;
1393     }
1394 
1395     if (!d || !other.d) {
1396         return false;
1397     }
1398 
1399     return d->m_url == other.d->m_url;
1400 }
1401 
1402 bool KFileItem::operator!=(const KFileItem &other) const
1403 {
1404     return !operator==(other);
1405 }
1406 
1407 bool KFileItem::operator<(const KFileItem &other) const
1408 {
1409     if (!other.d) {
1410         return false;
1411     }
1412     if (!d) {
1413         return other.d->m_url.isValid();
1414     }
1415     return d->m_url < other.d->m_url;
1416 }
1417 
1418 bool KFileItem::operator<(const QUrl &other) const
1419 {
1420     if (!d) {
1421         return other.isValid();
1422     }
1423     return d->m_url < other;
1424 }
1425 
1426 KFileItem::operator QVariant() const
1427 {
1428     return QVariant::fromValue(*this);
1429 }
1430 
1431 QString KFileItem::permissionsString() const
1432 {
1433     if (!d) {
1434         return QString();
1435     }
1436 
1437     d->ensureInitialized();
1438 
1439     if (d->m_access.isNull() && d->m_permissions != KFileItem::Unknown) {
1440         d->m_access = d->parsePermissions(d->m_permissions);
1441     }
1442 
1443     return d->m_access;
1444 }
1445 
1446 // check if we need to cache this
1447 QString KFileItem::timeString(FileTimes which) const
1448 {
1449     if (!d) {
1450         return QString();
1451     }
1452 
1453     return QLocale::system().toString(d->time(which), QLocale::LongFormat);
1454 }
1455 
1456 #if KIOCORE_BUILD_DEPRECATED_SINCE(4, 0)
1457 QString KFileItem::timeString(unsigned int which) const
1458 {
1459     if (!d) {
1460         return QString();
1461     }
1462 
1463     switch (which) {
1464     case KIO::UDSEntry::UDS_ACCESS_TIME:
1465         return timeString(AccessTime);
1466     case KIO::UDSEntry::UDS_CREATION_TIME:
1467         return timeString(CreationTime);
1468     case KIO::UDSEntry::UDS_MODIFICATION_TIME:
1469     default:
1470         return timeString(ModificationTime);
1471     }
1472 }
1473 #endif
1474 
1475 #if KIOCORE_BUILD_DEPRECATED_SINCE(4, 0)
1476 void KFileItem::assign(const KFileItem &item)
1477 {
1478     *this = item;
1479 }
1480 #endif
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 KFileItemList::KFileItemList()
1706 {
1707 }
1708 
1709 KFileItemList::KFileItemList(const QList<KFileItem> &items)
1710     : QList<KFileItem>(items)
1711 {
1712 }
1713 
1714 KFileItemList::KFileItemList(std::initializer_list<KFileItem> items)
1715     : QList<KFileItem>(items)
1716 {
1717 }
1718 
1719 KFileItem KFileItemList::findByName(const QString &fileName) const
1720 {
1721     auto it = std::find_if(cbegin(), cend(), [&fileName](const KFileItem &item) {
1722         return item.name() == fileName;
1723     });
1724 
1725     return it != cend() ? *it : KFileItem();
1726 }
1727 
1728 KFileItem KFileItemList::findByUrl(const QUrl &url) const
1729 {
1730     auto it = std::find_if(cbegin(), cend(), [&url](const KFileItem &item) {
1731         return item.url() == url;
1732     });
1733 
1734     return it != cend() ? *it : KFileItem();
1735 }
1736 
1737 QList<QUrl> KFileItemList::urlList() const
1738 {
1739     QList<QUrl> lst;
1740     lst.reserve(size());
1741 
1742     for (const auto &item : *this) {
1743         lst.append(item.url());
1744     }
1745     return lst;
1746 }
1747 
1748 QList<QUrl> KFileItemList::targetUrlList() const
1749 {
1750     QList<QUrl> lst;
1751     lst.reserve(size());
1752 
1753     for (const auto &item : *this) {
1754         lst.append(item.targetUrl());
1755     }
1756     return lst;
1757 }
1758 
1759 bool KFileItem::isDesktopFile() const
1760 {
1761     return checkDesktopFile(*this, true);
1762 }
1763 
1764 bool KFileItem::isRegularFile() const
1765 {
1766     if (!d) {
1767         return false;
1768     }
1769 
1770     d->ensureInitialized();
1771 
1772     return Utils::isRegFileMask(d->m_fileMode);
1773 }
1774 
1775 QDebug operator<<(QDebug stream, const KFileItem &item)
1776 {
1777     QDebugStateSaver saver(stream);
1778     stream.nospace();
1779     if (item.isNull()) {
1780         stream << "[null KFileItem]";
1781     } else {
1782         stream << "[KFileItem for " << item.url() << "]";
1783     }
1784     return stream;
1785 }
1786 
1787 #include "moc_kfileitem.cpp"