KIO

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

KDE's Doxygen guidelines are available online.