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

source code of kio/src/core/kfileitem.cpp