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"