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: urlIsDirectory);
77 } else {
78 Q_ASSERT(!urlIsDirectory);
79 m_strName = itemOrDirUrl.fileName();
80 m_strText = KIO::decodeFileName(str: 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(s: QSysInfo::machineHostName(), cs: 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(options: QUrl::StripTrailingSlash).path();
248 const QByteArray pathBA = QFile::encodeName(fileName: path);
249 if (LSTAT(path: pathBA.constData(), buff: &buff, details: KIO::StatDefaultDetails) == 0) {
250 m_entry.reserve(size: 9);
251 m_entry.replace(field: KIO::UDSEntry::UDS_DEVICE_ID, l: stat_dev(buf: buff));
252 m_entry.replace(field: KIO::UDSEntry::UDS_INODE, l: stat_ino(buf: buff));
253
254 mode_t mode = stat_mode(buf: buff);
255 if (Utils::isLinkMask(mode)) {
256 m_bLink = true;
257 if (STAT(path: pathBA.constData(), buff: &buff, details: KIO::StatDefaultDetails) == 0) {
258 mode = stat_mode(buf: 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(field: KIO::UDSEntry::UDS_SIZE, l: stat_size(buf: buff));
267 m_entry.replace(field: KIO::UDSEntry::UDS_FILE_TYPE, l: type); // extract file type
268 m_entry.replace(field: KIO::UDSEntry::UDS_ACCESS, l: mode & 07777); // extract permissions
269 m_entry.replace(field: KIO::UDSEntry::UDS_MODIFICATION_TIME, l: stat_mtime(buf: buff)); // TODO: we could use msecs too...
270 m_entry.replace(field: KIO::UDSEntry::UDS_ACCESS_TIME, l: stat_atime(buf: buff));
271#if HAVE_STATX
272 m_entry.replace(field: KIO::UDSEntry::UDS_CREATION_TIME, l: buff.stx_btime.tv_sec);
273#endif
274
275#ifndef Q_OS_WIN
276 const auto uid = stat_uid(buf: buff);
277 const auto gid = stat_gid(buf: buff);
278 m_entry.replace(field: KIO::UDSEntry::UDS_LOCAL_USER_ID, l: uid);
279 m_entry.replace(field: KIO::UDSEntry::UDS_LOCAL_GROUP_ID, l: 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(a: QString::fromLatin1(ba: 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(field: KIO::UDSEntry::UDS_FILE_TYPE, defaultValue: KFileItem::Unknown);
312 m_permissions = m_entry.numberValue(field: KIO::UDSEntry::UDS_ACCESS, defaultValue: KFileItem::Unknown);
313 m_strName = m_entry.stringValue(field: KIO::UDSEntry::UDS_NAME);
314
315 const QString displayName = m_entry.stringValue(field: KIO::UDSEntry::UDS_DISPLAY_NAME);
316 if (!displayName.isEmpty()) {
317 m_strText = displayName;
318 } else {
319 m_strText = KIO::decodeFileName(str: m_strName);
320 }
321
322 const QString urlStr = m_entry.stringValue(field: 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(field: KIO::UDSEntry::UDS_MIME_TYPE);
332 m_bMimeTypeKnown = !mimeTypeStr.isEmpty();
333 if (m_bMimeTypeKnown) {
334 m_mimeType = db.mimeTypeForName(nameOrAlias: mimeTypeStr);
335 }
336
337 m_guessedMimeType = m_entry.stringValue(field: KIO::UDSEntry::UDS_GUESSED_MIME_TYPE);
338 m_bLink = !m_entry.stringValue(field: KIO::UDSEntry::UDS_LINK_DEST).isEmpty(); // we don't store the link dest
339
340 const int hiddenVal = m_entry.numberValue(field: KIO::UDSEntry::UDS_HIDDEN, defaultValue: -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(path: Utils::concatPaths(path1: path, path2: 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(field: KIO::UDSEntry::UDS_SIZE, defaultValue: -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(field: KIO::UDSEntry::UDS_RECURSIVE_SIZE, defaultValue: -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:
394 return KIO::UDSEntry::UDS_MODIFICATION_TIME;
395 case KFileItem::AccessTime:
396 return KIO::UDSEntry::UDS_ACCESS_TIME;
397 case KFileItem::CreationTime:
398 return KIO::UDSEntry::UDS_CREATION_TIME;
399 }
400 return 0;
401}
402
403void KFileItemPrivate::setTime(KFileItem::FileTimes mappedWhich, uint time_t_val) const
404{
405 m_entry.replace(field: udsFieldForTime(mappedWhich), l: 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, time_t_val: 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(field: uds, defaultValue: -1);
422 if (fieldVal != -1) {
423 return QDateTime::fromSecsSinceEpoch(secs: 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"
449 << (m_entry.stringValue(KIO::UDSEntry::UDS_DEFAULT_ACL_STRING) == otherEntry.stringValue(KIO::UDSEntry::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)
457 << otherEntry.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(field: KIO::UDSEntry::UDS_GROUP) == item.m_entry.stringValue(field: KIO::UDSEntry::UDS_GROUP)
484 && m_entry.stringValue(field: KIO::UDSEntry::UDS_USER) == item.m_entry.stringValue(field: KIO::UDSEntry::UDS_USER)
485 && m_entry.stringValue(field: KIO::UDSEntry::UDS_EXTENDED_ACL) == item.m_entry.stringValue(field: KIO::UDSEntry::UDS_EXTENDED_ACL)
486 && m_entry.stringValue(field: KIO::UDSEntry::UDS_ACL_STRING) == item.m_entry.stringValue(field: KIO::UDSEntry::UDS_ACL_STRING)
487 && m_entry.stringValue(field: KIO::UDSEntry::UDS_DEFAULT_ACL_STRING) == item.m_entry.stringValue(field: KIO::UDSEntry::UDS_DEFAULT_ACL_STRING)
488 && m_bLink == item.m_bLink
489 && m_hidden == item.m_hidden
490 && size() == item.size()
491 && m_entry.numberValue(field: KIO::UDSEntry::UDS_MODIFICATION_TIME) == item.m_entry.numberValue(field: KIO::UDSEntry::UDS_MODIFICATION_TIME)
492 && m_entry.stringValue(field: KIO::UDSEntry::UDS_ICON_NAME) == item.m_entry.stringValue(field: KIO::UDSEntry::UDS_ICON_NAME)
493 && m_entry.stringValue(field: KIO::UDSEntry::UDS_TARGET_URL) == item.m_entry.stringValue(field: KIO::UDSEntry::UDS_TARGET_URL)
494 && m_entry.stringValue(field: KIO::UDSEntry::UDS_LOCAL_PATH) == item.m_entry.stringValue(field: 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(mode: 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(field: KIO::UDSEntry::UDS_EXTENDED_ACL)) {
578 buffer[10] = '+';
579 buffer[11] = 0;
580 } else {
581 buffer[10] = 0;
582 }
583
584 return QString::fromLatin1(ba: 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(s: QLatin1String("http")) || scheme == QLatin1String("mailto")) {
593 m_mimeType = db.mimeTypeForName(nameOrAlias: QLatin1String("application/octet-stream"));
594 } else if (url.path().isEmpty()) {
595 m_mimeType = db.mimeTypeForName(nameOrAlias: QLatin1String("inode/directory"));
596 } else {
597 m_mimeType = db.mimeTypeForFile(fileName: url.path(), mode: QMimeDatabase::MatchMode::MatchExtension);
598 }
599 } else {
600 m_mimeType = db.mimeTypeForUrl(url);
601 }
602}
603
604///////
605
606KFileItem::KFileItem()
607 : d(nullptr)
608{
609}
610
611KFileItem::KFileItem(const KIO::UDSEntry &entry, const QUrl &itemOrDirUrl, bool delayedMimeTypes, bool urlIsDirectory)
612 : d(new KFileItemPrivate(entry,
613 KFileItem::Unknown,
614 KFileItem::Unknown,
615 itemOrDirUrl,
616 urlIsDirectory,
617 delayedMimeTypes,
618 KFileItem::NormalMimeTypeDetermination))
619{
620}
621
622KFileItem::KFileItem(const QUrl &url, const QString &mimeType, mode_t mode)
623 : d(new KFileItemPrivate(KIO::UDSEntry(), mode, KFileItem::Unknown, url, false, false, KFileItem::NormalMimeTypeDetermination))
624{
625 d->m_bMimeTypeKnown = !mimeType.simplified().isEmpty();
626 if (d->m_bMimeTypeKnown) {
627 QMimeDatabase db;
628 d->m_mimeType = db.mimeTypeForName(nameOrAlias: mimeType);
629 }
630}
631
632KFileItem::KFileItem(const QUrl &url, KFileItem::MimeTypeDetermination mimeTypeDetermination)
633 : d(new KFileItemPrivate(KIO::UDSEntry(), KFileItem::Unknown, KFileItem::Unknown, url, false, false, mimeTypeDetermination))
634{
635}
636
637// Default implementations for:
638// - Copy constructor
639// - Move constructor
640// - Copy assignment
641// - Move assignment
642// - Destructor
643// The compiler will now generate the content of those.
644KFileItem::KFileItem(const KFileItem &) = default;
645KFileItem::~KFileItem() = default;
646KFileItem::KFileItem(KFileItem &&) = default;
647KFileItem &KFileItem::operator=(const KFileItem &) = default;
648KFileItem &KFileItem::operator=(KFileItem &&) = default;
649
650void KFileItem::refresh()
651{
652 if (!d) {
653 qCWarning(KIO_CORE) << "null item";
654 return;
655 }
656
657 d->m_fileMode = KFileItem::Unknown;
658 d->m_permissions = KFileItem::Unknown;
659 d->m_hidden = KFileItemPrivate::Auto;
660 d->m_hiddenCache = KFileItemPrivate::HiddenUncached;
661 refreshMimeType();
662
663#if HAVE_POSIX_ACL
664 // If the item had ACL, re-add them in init()
665 d->m_addACL = !d->m_entry.stringValue(KIO::UDSEntry::UDS_ACL_STRING).isEmpty();
666#endif
667
668 // Basically, we can't trust any information we got while listing.
669 // Everything could have changed...
670 // Clearing m_entry makes it possible to detect changes in the size of the file,
671 // the time information, etc.
672 d->m_entry.clear();
673 d->init(); // re-populates d->m_entry
674}
675
676void KFileItem::refreshMimeType()
677{
678 if (!d) {
679 return;
680 }
681
682 d->m_mimeType = QMimeType();
683 d->m_bMimeTypeKnown = false;
684 d->m_iconName.clear();
685}
686
687void KFileItem::setDelayedMimeTypes(bool b)
688{
689 if (!d) {
690 return;
691 }
692 d->m_delayedMimeTypes = b;
693}
694
695void KFileItem::setUrl(const QUrl &url)
696{
697 if (!d) {
698 qCWarning(KIO_CORE) << "null item";
699 return;
700 }
701
702 d->m_url = url;
703 setName(url.fileName());
704}
705
706void KFileItem::setLocalPath(const QString &path)
707{
708 if (!d) {
709 qCWarning(KIO_CORE) << "null item";
710 return;
711 }
712
713 d->m_entry.replace(field: KIO::UDSEntry::UDS_LOCAL_PATH, value: path);
714}
715
716void KFileItem::setName(const QString &name)
717{
718 if (!d) {
719 qCWarning(KIO_CORE) << "null item";
720 return;
721 }
722
723 d->ensureInitialized();
724
725 d->m_strName = name;
726 if (!d->m_strName.isEmpty()) {
727 d->m_strText = KIO::decodeFileName(str: d->m_strName);
728 }
729 if (d->m_entry.contains(field: KIO::UDSEntry::UDS_NAME)) {
730 d->m_entry.replace(field: KIO::UDSEntry::UDS_NAME, value: d->m_strName); // #195385
731 }
732 d->m_hiddenCache = KFileItemPrivate::HiddenUncached;
733}
734
735QString KFileItem::linkDest() const
736{
737 if (!d) {
738 return QString();
739 }
740
741 d->ensureInitialized();
742
743 if (!d->m_bLink) {
744 return QString{};
745 }
746
747 // Extract it from the KIO::UDSEntry
748 const QString linkStr = d->m_entry.stringValue(field: KIO::UDSEntry::UDS_LINK_DEST);
749 if (!linkStr.isEmpty()) {
750 return linkStr;
751 }
752
753 // If not in the KIO::UDSEntry, or if UDSEntry empty, use readlink() [if local URL]
754 if (d->m_bIsLocalUrl) {
755 // Use QFileInfo::readSymlink once we depend on Qt 6.6+
756#ifdef Q_OS_UNIX
757 // Use readlink on Unix because symLinkTarget turns relative targets into absolute (#456198)
758 // implementation following file_unix.cpp readlinkToBuffer()
759 size_t linkSize = size();
760 const QString path = d->m_url.adjusted(options: QUrl::StripTrailingSlash).toLocalFile();
761 if (linkSize > SIZE_MAX) {
762 qCWarning(KIO_CORE) << "file size bigger than SIZE_MAX, too big for readlink use!" << path;
763 return {};
764 }
765 size_t lowerBound = 256;
766 size_t higherBound = 1024;
767 size_t bufferSize = qBound(min: lowerBound, val: linkSize + 1, max: higherBound);
768 QByteArray linkTargetBuffer(bufferSize, Qt::Initialization::Uninitialized);
769 const QByteArray pathBA = QFile::encodeName(fileName: path);
770 while (true) {
771 ssize_t n = readlink(path: pathBA.constData(), buf: linkTargetBuffer.data(), len: linkTargetBuffer.size());
772 if (n < 0 && errno != ERANGE) {
773 qCWarning(KIO_CORE) << "readlink failed!" << pathBA;
774 return {};
775 } else if (n > 0 && static_cast<size_t>(n) != bufferSize) {
776 // the buffer was not filled in the last iteration
777 // we are finished reading, break the loop
778 linkTargetBuffer.truncate(pos: n);
779 break;
780 }
781 linkTargetBuffer.resize(size: linkTargetBuffer.size() * 2);
782 }
783 return QString::fromUtf8(ba: linkTargetBuffer);
784#else
785 return QFile::symLinkTarget(d->m_url.adjusted(QUrl::StripTrailingSlash).toLocalFile());
786#endif
787 }
788
789 return QString();
790}
791
792QString KFileItemPrivate::localPath() const
793{
794 if (m_bIsLocalUrl) {
795 return m_url.toLocalFile();
796 }
797
798 ensureInitialized();
799
800 // Extract the local path from the KIO::UDSEntry
801 return m_entry.stringValue(field: KIO::UDSEntry::UDS_LOCAL_PATH);
802}
803
804QString KFileItem::localPath() const
805{
806 if (!d) {
807 return QString();
808 }
809
810 return d->localPath();
811}
812
813KIO::filesize_t KFileItem::size() const
814{
815 if (!d) {
816 return 0;
817 }
818
819 return d->size();
820}
821
822KIO::filesize_t KFileItem::recursiveSize() const
823{
824 if (!d) {
825 return 0;
826 }
827
828 return d->recursiveSize();
829}
830
831bool KFileItem::hasExtendedACL() const
832{
833 if (!d) {
834 return false;
835 }
836
837 // Check if the field exists; its value doesn't matter
838 return entry().contains(field: KIO::UDSEntry::UDS_EXTENDED_ACL);
839}
840
841KACL KFileItem::ACL() const
842{
843 if (!d) {
844 return KACL();
845 }
846
847 if (hasExtendedACL()) {
848 // Extract it from the KIO::UDSEntry
849 const QString fieldVal = d->m_entry.stringValue(field: KIO::UDSEntry::UDS_ACL_STRING);
850 if (!fieldVal.isEmpty()) {
851 return KACL(fieldVal);
852 }
853 }
854
855 // create one from the basic permissions
856 return KACL(d->m_permissions);
857}
858
859KACL KFileItem::defaultACL() const
860{
861 if (!d) {
862 return KACL();
863 }
864
865 // Extract it from the KIO::UDSEntry
866 const QString fieldVal = entry().stringValue(field: KIO::UDSEntry::UDS_DEFAULT_ACL_STRING);
867 if (!fieldVal.isEmpty()) {
868 return KACL(fieldVal);
869 } else {
870 return KACL();
871 }
872}
873
874QDateTime KFileItem::time(FileTimes which) const
875{
876 if (!d) {
877 return QDateTime();
878 }
879
880 return d->time(mappedWhich: which);
881}
882
883QString KFileItem::user() const
884{
885 if (!d) {
886 return QString();
887 }
888 if (entry().contains(field: KIO::UDSEntry::UDS_USER)) {
889 return entry().stringValue(field: KIO::UDSEntry::UDS_USER);
890 } else {
891#ifdef Q_OS_UNIX
892 auto uid = entry().numberValue(field: KIO::UDSEntry::UDS_LOCAL_USER_ID, defaultValue: -1);
893 if (uid != -1) {
894 return KUser(uid).loginName();
895 }
896#endif
897 }
898 return QString();
899}
900
901int KFileItem::userId() const
902{
903 if (!d) {
904 return -1;
905 }
906
907 return entry().numberValue(field: KIO::UDSEntry::UDS_LOCAL_USER_ID, defaultValue: -1);
908}
909
910QString KFileItem::group() const
911{
912 if (!d) {
913 return QString();
914 }
915
916 if (entry().contains(field: KIO::UDSEntry::UDS_GROUP)) {
917 return entry().stringValue(field: KIO::UDSEntry::UDS_GROUP);
918 } else {
919#ifdef Q_OS_UNIX
920 auto gid = entry().numberValue(field: KIO::UDSEntry::UDS_LOCAL_GROUP_ID, defaultValue: -1);
921 if (gid != -1) {
922 // We cache the group name strings.
923 // will often be the same for many entries in a row. Caching them
924 // permits to use implicit sharing to save memory.
925 thread_local static QMap<quint64, QString> cachedStrings;
926 if (!cachedStrings.contains(key: gid)) {
927 const auto groupName = KUserGroup(gid).name();
928 cachedStrings.insert(key: gid, value: groupName);
929 }
930 return cachedStrings.value(key: gid);
931 }
932#endif
933 }
934 return QString();
935}
936
937int KFileItem::groupId() const
938{
939 if (!d) {
940 return -1;
941 }
942
943 return entry().numberValue(field: KIO::UDSEntry::UDS_LOCAL_GROUP_ID, defaultValue: -1);
944}
945
946bool KFileItemPrivate::isSlow() const
947{
948 if (m_slow == SlowUnknown) {
949 const QString path = localPath();
950 if (!path.isEmpty()) {
951 const KFileSystemType::Type fsType = KFileSystemType::fileSystemType(path);
952 m_slow = (fsType == KFileSystemType::Nfs || fsType == KFileSystemType::Smb) ? Slow : Fast;
953 } else {
954 m_slow = Slow;
955 }
956 }
957 return m_slow == Slow;
958}
959
960bool KFileItem::isSlow() const
961{
962 if (!d) {
963 return false;
964 }
965
966 return d->isSlow();
967}
968
969QString KFileItem::mimetype() const
970{
971 if (!d) {
972 return QString();
973 }
974
975 KFileItem *that = const_cast<KFileItem *>(this);
976 return that->determineMimeType().name();
977}
978
979QMimeType KFileItem::determineMimeType() const
980{
981 if (!d) {
982 return QMimeType();
983 }
984
985 if (!d->m_mimeType.isValid() || !d->m_bMimeTypeKnown) {
986 QMimeDatabase db;
987 if (isDir()) {
988 d->m_mimeType = db.mimeTypeForName(QStringLiteral("inode/directory"));
989 } else {
990 const auto [url, isLocalUrl] = isMostLocalUrl();
991 d->determineMimeTypeHelper(url);
992
993 // was: d->m_mimeType = KMimeType::findByUrl( url, d->m_fileMode, isLocalUrl );
994 // => we are no longer using d->m_fileMode for remote URLs.
995 Q_ASSERT(d->m_mimeType.isValid());
996 // qDebug() << d << "finding final MIME type for" << url << ":" << d->m_mimeType.name();
997 }
998 d->m_bMimeTypeKnown = true;
999 }
1000
1001 if (d->m_delayedMimeTypes) { // if we delayed getting the iconName up till now, this is the right point in time to do so
1002 d->m_delayedMimeTypes = false;
1003 d->m_useIconNameCache = false;
1004 (void)iconName();
1005 }
1006
1007 return d->m_mimeType;
1008}
1009
1010bool KFileItem::isMimeTypeKnown() const
1011{
1012 if (!d) {
1013 return false;
1014 }
1015
1016 // The MIME type isn't known if determineMimeType was never called (on-demand determination)
1017 // or if this fileitem has a guessed MIME type (e.g. ftp symlink) - in which case
1018 // it always remains "not fully determined"
1019 return d->m_bMimeTypeKnown && d->m_guessedMimeType.isEmpty();
1020}
1021
1022static bool isDirectoryMounted(const QUrl &url)
1023{
1024 // Stating .directory files can cause long freezes when e.g. /home
1025 // uses autofs for every user's home directory, i.e. opening /home
1026 // in a file dialog will mount every single home directory.
1027 // These non-mounted directories can be identified by having 0 size.
1028 // There are also other directories with 0 size, such as /proc, that may
1029 // be mounted, but those are unlikely to contain .directory (and checking
1030 // this would require checking with KMountPoint).
1031
1032 // TODO: maybe this could be checked with KFileSystemType instead?
1033 QFileInfo info(url.toLocalFile());
1034 if (info.isDir() && info.size() == 0) {
1035 return false;
1036 }
1037 return true;
1038}
1039
1040bool KFileItem::isFinalIconKnown() const
1041{
1042 if (!d) {
1043 return false;
1044 }
1045 return d->m_bMimeTypeKnown && (!d->m_delayedMimeTypes);
1046}
1047
1048// KDE5 TODO: merge with comment()? Need to see what lxr says about the usage of both.
1049QString KFileItem::mimeComment() const
1050{
1051 if (!d) {
1052 return QString();
1053 }
1054
1055 const QString displayType = d->m_entry.stringValue(field: KIO::UDSEntry::UDS_DISPLAY_TYPE);
1056 if (!displayType.isEmpty()) {
1057 return displayType;
1058 }
1059
1060 const auto [url, isLocalUrl] = isMostLocalUrl();
1061
1062 QMimeType mime = currentMimeType();
1063 // This cannot move to kio_file (with UDS_DISPLAY_TYPE) because it needs
1064 // the MIME type to be determined, which is done here, and possibly delayed...
1065 if (isLocalUrl && !d->isSlow() && mime.inherits(QStringLiteral("application/x-desktop"))) {
1066 KDesktopFile cfg(url.toLocalFile());
1067 QString comment = cfg.desktopGroup().readEntry(key: "Comment");
1068 if (!comment.isEmpty()) {
1069 return comment;
1070 }
1071 }
1072
1073 // Support for .directory file in directories
1074 if (isLocalUrl && isDir() && !d->isSlow() && isDirectoryMounted(url)) {
1075 QUrl u(url);
1076 u.setPath(path: Utils::concatPaths(path1: u.path(), QStringLiteral(".directory")));
1077 const KDesktopFile cfg(u.toLocalFile());
1078 const QString comment = cfg.readComment();
1079 if (!comment.isEmpty()) {
1080 return comment;
1081 }
1082 }
1083
1084 const QString comment = mime.comment();
1085 // qDebug() << "finding comment for " << url.url() << " : " << d->m_mimeType->name();
1086 if (!comment.isEmpty()) {
1087 return comment;
1088 } else {
1089 return mime.name();
1090 }
1091}
1092
1093static QString iconFromDirectoryFile(const QString &path)
1094{
1095 const QString filePath = path + QLatin1String("/.directory");
1096 if (!QFileInfo(filePath).isFile()) { // exists -and- is a file
1097 return QString();
1098 }
1099
1100 KDesktopFile cfg(filePath);
1101 QString icon = cfg.readIcon();
1102
1103 const KConfigGroup group = cfg.desktopGroup();
1104 const QString emptyIcon = group.readEntry(key: "EmptyIcon");
1105 if (!emptyIcon.isEmpty()) {
1106 bool isDirEmpty = true;
1107 QDirIterator dirIt(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
1108 while (dirIt.hasNext()) {
1109 dirIt.next();
1110 if (dirIt.fileName() != QLatin1String(".directory")) {
1111 isDirEmpty = false;
1112 break;
1113 }
1114 }
1115 if (isDirEmpty) {
1116 icon = emptyIcon;
1117 }
1118 }
1119
1120 if (icon.startsWith(s: QLatin1String("./"))) {
1121 // path is relative with respect to the location of the .directory file (#73463)
1122 return path + QStringView(icon).mid(pos: 1);
1123 }
1124 return icon;
1125}
1126
1127static QString iconFromDesktopFile(const QString &path)
1128{
1129 KDesktopFile cfg(path);
1130 const QString icon = cfg.readIcon();
1131 if (cfg.hasLinkType()) {
1132 const KConfigGroup group = cfg.desktopGroup();
1133 const QString emptyIcon = group.readEntry(key: "EmptyIcon");
1134 if (!emptyIcon.isEmpty()) {
1135 const QString u = cfg.readUrl();
1136 const QUrl url(u);
1137 if (url.scheme() == QLatin1String("trash")) {
1138 // We need to find if the trash is empty, preferably without using a KIO job.
1139 // So instead kio_trash leaves an entry in its config file for us.
1140 KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig);
1141 if (trashConfig.group(QStringLiteral("Status")).readEntry(key: "Empty", defaultValue: true)) {
1142 return emptyIcon;
1143 }
1144 }
1145 }
1146 }
1147 return icon;
1148}
1149
1150QString KFileItem::iconName() const
1151{
1152 if (!d) {
1153 return QString();
1154 }
1155
1156 if (d->m_useIconNameCache && !d->m_iconName.isEmpty()) {
1157 return d->m_iconName;
1158 }
1159
1160 d->m_iconName = d->m_entry.stringValue(field: KIO::UDSEntry::UDS_ICON_NAME);
1161 if (!d->m_iconName.isEmpty()) {
1162 d->m_useIconNameCache = d->m_bMimeTypeKnown;
1163 return d->m_iconName;
1164 }
1165
1166 const auto [url, isLocalUrl] = isMostLocalUrl();
1167
1168 QMimeDatabase db;
1169 QMimeType mime;
1170 // Use guessed MIME type for the icon
1171 if (!d->m_guessedMimeType.isEmpty()) {
1172 mime = db.mimeTypeForName(nameOrAlias: d->m_guessedMimeType);
1173 } else {
1174 mime = currentMimeType();
1175 }
1176
1177 const bool delaySlowOperations = d->m_delayedMimeTypes;
1178
1179 if (isLocalUrl && !delaySlowOperations) {
1180 const QString &localFile = url.toLocalFile();
1181
1182 if (mime.inherits(QStringLiteral("application/x-desktop"))) {
1183 d->m_iconName = iconFromDesktopFile(path: localFile);
1184 if (!d->m_iconName.isEmpty()) {
1185 d->m_useIconNameCache = d->m_bMimeTypeKnown;
1186 return d->m_iconName;
1187 }
1188 }
1189
1190 if (isDir()) {
1191 if (isDirectoryMounted(url)) {
1192 d->m_iconName = iconFromDirectoryFile(path: localFile);
1193 if (!d->m_iconName.isEmpty()) {
1194 d->m_useIconNameCache = d->m_bMimeTypeKnown;
1195 return d->m_iconName;
1196 }
1197 }
1198
1199 d->m_iconName = KIOPrivate::iconForStandardPath(localDirectory: localFile);
1200 if (!d->m_iconName.isEmpty()) {
1201 d->m_useIconNameCache = d->m_bMimeTypeKnown;
1202 return d->m_iconName;
1203 }
1204 }
1205 }
1206
1207 d->m_iconName = mime.iconName();
1208 d->m_useIconNameCache = d->m_bMimeTypeKnown;
1209 return d->m_iconName;
1210}
1211
1212/*
1213 * Returns true if this is a desktop file.
1214 * MIME type determination is optional.
1215 */
1216static bool checkDesktopFile(const KFileItem &item, bool _determineMimeType)
1217{
1218 // Only local files
1219 if (!item.isMostLocalUrl().local) {
1220 return false;
1221 }
1222
1223 // only regular files
1224 if (!item.isRegularFile()) {
1225 return false;
1226 }
1227
1228 // only if readable
1229 if (!item.isReadable()) {
1230 return false;
1231 }
1232
1233 // return true if desktop file
1234 QMimeType mime = _determineMimeType ? item.determineMimeType() : item.currentMimeType();
1235 return mime.inherits(QStringLiteral("application/x-desktop"));
1236}
1237
1238QStringList KFileItem::overlays() const
1239{
1240 if (!d) {
1241 return QStringList();
1242 }
1243
1244 d->ensureInitialized();
1245
1246 QStringList names = d->m_entry.stringValue(field: KIO::UDSEntry::UDS_ICON_OVERLAY_NAMES).split(sep: QLatin1Char(','), behavior: Qt::SkipEmptyParts);
1247
1248 if (d->m_bLink) {
1249 names.append(QStringLiteral("emblem-symbolic-link"));
1250 }
1251
1252 if (!isReadable()) {
1253 names.append(QStringLiteral("emblem-locked"));
1254 }
1255
1256 if (checkDesktopFile(item: *this, determineMimeType: false)) {
1257 KDesktopFile cfg(localPath());
1258 const KConfigGroup group = cfg.desktopGroup();
1259
1260 // Add a warning emblem if this is an executable desktop file
1261 // which is untrusted.
1262 if (group.hasKey(key: "Exec") && !KDesktopFile::isAuthorizedDesktopFile(path: localPath())) {
1263 names.append(QStringLiteral("emblem-important"));
1264 }
1265 }
1266
1267 if (isHidden()) {
1268 names.append(QStringLiteral("hidden"));
1269 }
1270#ifndef Q_OS_WIN
1271 if (isDir()) {
1272 const auto [url, isLocalUrl] = isMostLocalUrl();
1273 if (isLocalUrl) {
1274 const QString path = url.toLocalFile();
1275 if (KSambaShare::instance()->isDirectoryShared(path) || KNFSShare::instance()->isDirectoryShared(path)) {
1276 names.append(QStringLiteral("emblem-shared"));
1277 }
1278 }
1279 }
1280#endif // Q_OS_WIN
1281
1282 return names;
1283}
1284
1285QString KFileItem::comment() const
1286{
1287 if (!d) {
1288 return QString();
1289 }
1290
1291 return d->m_entry.stringValue(field: KIO::UDSEntry::UDS_COMMENT);
1292}
1293
1294bool KFileItem::isReadable() const
1295{
1296 if (!d) {
1297 return false;
1298 }
1299
1300 d->ensureInitialized();
1301
1302 if (d->m_permissions != KFileItem::Unknown) {
1303 const mode_t readMask = S_IRUSR | S_IRGRP | S_IROTH;
1304 // No read permission at all
1305 if ((d->m_permissions & readMask) == 0) {
1306 return false;
1307 }
1308
1309 // Read permissions for all: save a stat call
1310 if ((d->m_permissions & readMask) == readMask) {
1311 return true;
1312 }
1313
1314#ifndef Q_OS_WIN
1315 const auto uidOfItem = userId();
1316 if (uidOfItem != -1) {
1317 const auto currentUser = KUserId::currentUserId();
1318 if (((uint)uidOfItem) == currentUser.nativeId()) {
1319 return S_IRUSR & d->m_permissions;
1320 }
1321 const auto gidOfItem = groupId();
1322 if (gidOfItem != -1) {
1323 if (KUser(currentUser).groups().contains(t: KUserGroup(gidOfItem))) {
1324 return S_IRGRP & d->m_permissions;
1325 }
1326
1327 return S_IROTH & d->m_permissions;
1328 }
1329 }
1330#else
1331 // simple special case
1332 return S_IRUSR & d->m_permissions;
1333#endif
1334 }
1335
1336 // Or if we can't read it - not network transparent
1337 if (d->m_bIsLocalUrl && !QFileInfo(d->m_url.toLocalFile()).isReadable()) {
1338 return false;
1339 }
1340
1341 return true;
1342}
1343
1344bool KFileItem::isWritable() const
1345{
1346 if (!d) {
1347 return false;
1348 }
1349
1350 d->ensureInitialized();
1351
1352 if (d->m_permissions != KFileItem::Unknown) {
1353 // No write permission at all
1354 if ((d->m_permissions & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0) {
1355 return false;
1356 }
1357
1358#ifndef Q_OS_WIN
1359 const auto uidOfItem = userId();
1360 if (uidOfItem != -1) {
1361 const auto currentUser = KUserId::currentUserId();
1362 if (((uint)uidOfItem) == currentUser.nativeId()) {
1363 return S_IWUSR & d->m_permissions;
1364 }
1365 const auto gidOfItem = groupId();
1366 if (gidOfItem != -1) {
1367 if (KUser(currentUser).groups().contains(t: KUserGroup(gidOfItem))) {
1368 return S_IWGRP & d->m_permissions;
1369 }
1370
1371 if (S_IWOTH & d->m_permissions) {
1372 return true;
1373 }
1374 }
1375 }
1376#else
1377 // simple special case
1378 return S_IWUSR & d->m_permissions;
1379#endif
1380 }
1381
1382 // Or if we can't write it - not network transparent
1383 if (d->m_bIsLocalUrl) {
1384 return QFileInfo(d->m_url.toLocalFile()).isWritable();
1385 } else {
1386 return KProtocolManager::supportsWriting(url: d->m_url);
1387 }
1388}
1389
1390bool KFileItem::isHidden() const
1391{
1392 if (!d) {
1393 return false;
1394 }
1395
1396 // The KIO worker can specify explicitly that a file is hidden or shown
1397 if (d->m_hidden != KFileItemPrivate::Auto) {
1398 return d->m_hidden == KFileItemPrivate::Hidden;
1399 }
1400 if (d->m_hiddenCache != KFileItemPrivate::HiddenUncached) {
1401 return d->m_hiddenCache == KFileItemPrivate::HiddenCached;
1402 }
1403
1404 // Prefer the filename that is part of the URL, in case the display name is different.
1405 QString fileName = d->m_url.fileName();
1406 if (fileName.isEmpty()) { // e.g. "trash:/"
1407 fileName = d->m_strName;
1408 }
1409
1410 // Just "." is current directory, not hidden.
1411 d->m_hiddenCache = fileName.length() > 1 && fileName[0] == QLatin1Char('.') ? KFileItemPrivate::HiddenCached : KFileItemPrivate::ShownCached;
1412 return d->m_hiddenCache == KFileItemPrivate::HiddenCached;
1413}
1414
1415void KFileItem::setHidden()
1416{
1417 if (d) {
1418 d->m_hidden = KFileItemPrivate::Hidden;
1419 }
1420}
1421
1422bool KFileItem::isDir() const
1423{
1424 if (!d) {
1425 return false;
1426 }
1427
1428 if (d->m_fileMode != KFileItem::Unknown) {
1429 // File mode is known so we can use that.
1430 return Utils::isDirMask(mode: d->m_fileMode);
1431 }
1432
1433 if (d->m_bMimeTypeKnown && d->m_mimeType.isValid()) {
1434 // File mode is not known but we do know the mime type, so use that to
1435 // avoid doing a stat.
1436 return d->m_mimeType.inherits(QStringLiteral("inode/directory"));
1437 }
1438
1439 if (d->m_bSkipMimeTypeFromContent) {
1440 return false;
1441 }
1442
1443 d->ensureInitialized();
1444
1445 if (d->m_fileMode == KFileItem::Unknown) {
1446 // Probably the file was deleted already, and KDirLister hasn't told the world yet.
1447 // qDebug() << d << url() << "can't say -> false";
1448 return false; // can't say for sure, so no
1449 }
1450 return Utils::isDirMask(mode: d->m_fileMode);
1451}
1452
1453bool KFileItem::isFile() const
1454{
1455 if (!d) {
1456 return false;
1457 }
1458
1459 return !isDir();
1460}
1461
1462QString KFileItem::getStatusBarInfo() const
1463{
1464 if (!d) {
1465 return QString();
1466 }
1467
1468 auto toDisplayUrl = [](const QUrl &url) {
1469 QString dest;
1470 if (url.isLocalFile()) {
1471 dest = KShell::tildeCollapse(path: url.toLocalFile());
1472 } else {
1473 dest = url.toDisplayString();
1474 }
1475 return dest;
1476 };
1477
1478 QString text = d->m_strText;
1479 const QString comment = mimeComment();
1480
1481 if (d->m_bLink) {
1482 auto linkText = linkDest();
1483 if (!linkText.startsWith(QStringLiteral("anon_inode:"))) {
1484 auto url = QUrl(linkText).adjusted(options: QUrl::StripTrailingSlash);
1485 if (d->m_url.isLocalFile()) {
1486 if (url.scheme().isEmpty()) {
1487 url.setScheme(QStringLiteral("file"));
1488 }
1489 } else {
1490 url = d->m_url.resolved(relative: url);
1491 }
1492 linkText = toDisplayUrl(url);
1493 }
1494 text += QLatin1Char(' ');
1495 if (comment.isEmpty()) {
1496 text += i18n("(Symbolic Link to %1)", linkText);
1497 } else {
1498 text += i18n("(%1, Link to %2)", comment, linkText);
1499 }
1500 } else if (targetUrl() != url()) {
1501 text += i18n(" (Points to %1)", toDisplayUrl(targetUrl()));
1502 } else if (Utils::isRegFileMask(mode: d->m_fileMode)) {
1503 text += QStringLiteral(" (%1, %2)").arg(args: comment, args: KIO::convertSize(size: size()));
1504 } else {
1505 text += QStringLiteral(" (%1)").arg(a: comment);
1506 }
1507 return text;
1508}
1509
1510bool KFileItem::cmp(const KFileItem &item) const
1511{
1512 if (!d && !item.d) {
1513 return true;
1514 }
1515
1516 if (!d || !item.d) {
1517 return false;
1518 }
1519
1520 return d->cmp(item: *item.d);
1521}
1522
1523bool KFileItem::operator==(const KFileItem &other) const
1524{
1525 if (!d && !other.d) {
1526 return true;
1527 }
1528
1529 if (!d || !other.d) {
1530 return false;
1531 }
1532
1533 return d->m_url == other.d->m_url;
1534}
1535
1536bool KFileItem::operator!=(const KFileItem &other) const
1537{
1538 return !operator==(other);
1539}
1540
1541bool KFileItem::operator<(const KFileItem &other) const
1542{
1543 if (!other.d) {
1544 return false;
1545 }
1546 if (!d) {
1547 return other.d->m_url.isValid();
1548 }
1549 return d->m_url < other.d->m_url;
1550}
1551
1552bool KFileItem::operator<(const QUrl &other) const
1553{
1554 if (!d) {
1555 return other.isValid();
1556 }
1557 return d->m_url < other;
1558}
1559
1560KFileItem::operator QVariant() const
1561{
1562 return QVariant::fromValue(value: *this);
1563}
1564
1565QString KFileItem::permissionsString() const
1566{
1567 if (!d) {
1568 return QString();
1569 }
1570
1571 d->ensureInitialized();
1572
1573 if (d->m_access.isNull() && d->m_permissions != KFileItem::Unknown) {
1574 d->m_access = d->parsePermissions(perm: d->m_permissions);
1575 }
1576
1577 return d->m_access;
1578}
1579
1580// check if we need to cache this
1581QString KFileItem::timeString(FileTimes which) const
1582{
1583 if (!d) {
1584 return QString();
1585 }
1586
1587 return QLocale::system().toString(dateTime: d->time(mappedWhich: which), format: QLocale::LongFormat);
1588}
1589
1590QUrl KFileItem::mostLocalUrl(bool *local) const
1591{
1592 if (!d) {
1593 return {};
1594 }
1595
1596 const auto [url, isLocal] = isMostLocalUrl();
1597 if (local) {
1598 *local = isLocal;
1599 }
1600 return url;
1601}
1602
1603KFileItem::MostLocalUrlResult KFileItem::isMostLocalUrl() const
1604{
1605 if (!d) {
1606 return {.url: QUrl(), .local: false};
1607 }
1608
1609 const QString local_path = localPath();
1610 if (!local_path.isEmpty()) {
1611 return {.url: QUrl::fromLocalFile(localfile: local_path), .local: true};
1612 } else {
1613 return {.url: d->m_url, .local: d->m_bIsLocalUrl};
1614 }
1615}
1616
1617QDataStream &operator<<(QDataStream &s, const KFileItem &a)
1618{
1619 if (a.d) {
1620 // We don't need to save/restore anything that refresh() invalidates,
1621 // since that means we can re-determine those by ourselves.
1622 s << a.d->m_url;
1623 s << a.d->m_strName;
1624 s << a.d->m_strText;
1625 } else {
1626 s << QUrl();
1627 s << QString();
1628 s << QString();
1629 }
1630
1631 return s;
1632}
1633
1634QDataStream &operator>>(QDataStream &s, KFileItem &a)
1635{
1636 QUrl url;
1637 QString strName;
1638 QString strText;
1639
1640 s >> url;
1641 s >> strName;
1642 s >> strText;
1643
1644 if (!a.d) {
1645 qCWarning(KIO_CORE) << "null item";
1646 return s;
1647 }
1648
1649 if (url.isEmpty()) {
1650 a.d = nullptr;
1651 return s;
1652 }
1653
1654 a.d->m_url = url;
1655 a.d->m_strName = strName;
1656 a.d->m_strText = strText;
1657 a.d->m_bIsLocalUrl = a.d->m_url.isLocalFile();
1658 a.d->m_bMimeTypeKnown = false;
1659 a.refresh();
1660
1661 return s;
1662}
1663
1664QUrl KFileItem::url() const
1665{
1666 if (!d) {
1667 return QUrl();
1668 }
1669
1670 return d->m_url;
1671}
1672
1673mode_t KFileItem::permissions() const
1674{
1675 if (!d) {
1676 return 0;
1677 }
1678
1679 d->ensureInitialized();
1680
1681 return d->m_permissions;
1682}
1683
1684mode_t KFileItem::mode() const
1685{
1686 if (!d) {
1687 return 0;
1688 }
1689
1690 d->ensureInitialized();
1691
1692 return d->m_fileMode;
1693}
1694
1695bool KFileItem::isLink() const
1696{
1697 if (!d) {
1698 return false;
1699 }
1700
1701 d->ensureInitialized();
1702
1703 return d->m_bLink;
1704}
1705
1706bool KFileItem::isLocalFile() const
1707{
1708 if (!d) {
1709 return false;
1710 }
1711
1712 return d->m_bIsLocalUrl;
1713}
1714
1715QString KFileItem::text() const
1716{
1717 if (!d) {
1718 return QString();
1719 }
1720
1721 return d->m_strText;
1722}
1723
1724QString KFileItem::name(bool lowerCase) const
1725{
1726 if (!d) {
1727 return QString();
1728 }
1729
1730 if (!lowerCase) {
1731 return d->m_strName;
1732 } else if (d->m_strLowerCaseName.isNull()) {
1733 d->m_strLowerCaseName = d->m_strName.toLower();
1734 }
1735 return d->m_strLowerCaseName;
1736}
1737
1738QUrl KFileItem::targetUrl() const
1739{
1740 if (!d) {
1741 return QUrl();
1742 }
1743
1744 const QString targetUrlStr = d->m_entry.stringValue(field: KIO::UDSEntry::UDS_TARGET_URL);
1745 if (!targetUrlStr.isEmpty()) {
1746 return QUrl(targetUrlStr);
1747 } else {
1748 return url();
1749 }
1750}
1751
1752/*
1753 * MIME type handling.
1754 *
1755 * Initial state: m_mimeType = QMimeType().
1756 * When currentMimeType() is called first: fast MIME type determination,
1757 * might either find an accurate MIME type (-> Final state), otherwise we
1758 * set m_mimeType but not m_bMimeTypeKnown (-> Intermediate state)
1759 * Intermediate state: determineMimeType() does the real determination -> Final state.
1760 *
1761 * If delayedMimeTypes isn't set, then we always go to the Final state directly.
1762 */
1763
1764QMimeType KFileItem::currentMimeType() const
1765{
1766 if (!d || d->m_url.isEmpty()) {
1767 return QMimeType();
1768 }
1769
1770 if (!d->m_mimeType.isValid()) {
1771 // On-demand fast (but not always accurate) MIME type determination
1772 QMimeDatabase db;
1773 if (isDir()) {
1774 d->m_mimeType = db.mimeTypeForName(QStringLiteral("inode/directory"));
1775 return d->m_mimeType;
1776 }
1777 const QUrl url = mostLocalUrl();
1778 if (d->m_delayedMimeTypes) {
1779 const QList<QMimeType> mimeTypes = db.mimeTypesForFileName(fileName: url.path());
1780 if (mimeTypes.isEmpty()) {
1781 d->m_mimeType = db.mimeTypeForName(QStringLiteral("application/octet-stream"));
1782 d->m_bMimeTypeKnown = false;
1783 } else {
1784 d->m_mimeType = mimeTypes.first();
1785 // If there were conflicting globs. determineMimeType will be able to do better.
1786 d->m_bMimeTypeKnown = (mimeTypes.count() == 1);
1787 }
1788 } else {
1789 // ## d->m_fileMode isn't used anymore (for remote urls)
1790 d->determineMimeTypeHelper(url);
1791 d->m_bMimeTypeKnown = true;
1792 }
1793 }
1794 return d->m_mimeType;
1795}
1796
1797KIO::UDSEntry KFileItem::entry() const
1798{
1799 if (!d) {
1800 return KIO::UDSEntry();
1801 }
1802
1803 d->ensureInitialized();
1804
1805 return d->m_entry;
1806}
1807
1808bool KFileItem::isNull() const
1809{
1810 return d == nullptr;
1811}
1812
1813bool KFileItem::exists() const
1814{
1815 if (!d) {
1816 return false;
1817 }
1818 if (!d->m_bInitCalled) {
1819 qCWarning(KIO_CORE) << "KFileItem: exists called when not initialised" << d->m_url;
1820 return false;
1821 }
1822 return d->m_fileMode != KFileItem::Unknown;
1823}
1824
1825bool KFileItem::isExecutable() const
1826{
1827 if (!d) {
1828 return false;
1829 }
1830
1831 d->ensureInitialized();
1832
1833 if (d->m_permissions == KFileItem::Unknown) {
1834 return false;
1835 }
1836
1837 const mode_t executableMask = S_IXGRP | S_IXUSR | S_IXOTH;
1838 if ((d->m_permissions & executableMask) == 0) {
1839 return false;
1840 }
1841
1842#ifndef Q_OS_WIN
1843 const auto uid = userId();
1844 if (uid != -1) {
1845 if (((uint)uid) == KUserId::currentUserId().nativeId()) {
1846 return S_IXUSR & d->m_permissions;
1847 }
1848 const auto gid = groupId();
1849 if (gid != -1) {
1850 const KUser kuser = KUser(uid);
1851 if (kuser.groups().contains(t: KUserGroup(gid))) {
1852 return S_IXGRP & d->m_permissions;
1853 }
1854
1855 return S_IXOTH & d->m_permissions;
1856 }
1857 }
1858 return false;
1859#else
1860 // simple special case
1861 return S_IXUSR & d->m_permissions;
1862#endif
1863}
1864
1865KFileItemList::KFileItemList()
1866{
1867}
1868
1869KFileItemList::KFileItemList(const QList<KFileItem> &items)
1870 : QList<KFileItem>(items)
1871{
1872}
1873
1874KFileItemList::KFileItemList(std::initializer_list<KFileItem> items)
1875 : QList<KFileItem>(items)
1876{
1877}
1878
1879KFileItem KFileItemList::findByName(const QString &fileName) const
1880{
1881 auto it = std::find_if(first: cbegin(), last: cend(), pred: [&fileName](const KFileItem &item) {
1882 return item.name() == fileName;
1883 });
1884
1885 return it != cend() ? *it : KFileItem();
1886}
1887
1888KFileItem KFileItemList::findByUrl(const QUrl &url) const
1889{
1890 auto it = std::find_if(first: cbegin(), last: cend(), pred: [&url](const KFileItem &item) {
1891 return item.url() == url;
1892 });
1893
1894 return it != cend() ? *it : KFileItem();
1895}
1896
1897QList<QUrl> KFileItemList::urlList() const
1898{
1899 QList<QUrl> lst;
1900 lst.reserve(asize: size());
1901
1902 for (const auto &item : *this) {
1903 lst.append(t: item.url());
1904 }
1905 return lst;
1906}
1907
1908QList<QUrl> KFileItemList::targetUrlList() const
1909{
1910 QList<QUrl> lst;
1911 lst.reserve(asize: size());
1912
1913 for (const auto &item : *this) {
1914 lst.append(t: item.targetUrl());
1915 }
1916 return lst;
1917}
1918
1919bool KFileItem::isDesktopFile() const
1920{
1921 return checkDesktopFile(item: *this, determineMimeType: true);
1922}
1923
1924bool KFileItem::isRegularFile() const
1925{
1926 if (!d) {
1927 return false;
1928 }
1929
1930 d->ensureInitialized();
1931
1932 return Utils::isRegFileMask(mode: d->m_fileMode);
1933}
1934
1935QString KFileItem::suffix() const
1936{
1937 if (!d || isDir()) {
1938 return QString();
1939 }
1940
1941 const int lastDot = d->m_strText.lastIndexOf(QStringLiteral("."));
1942 if (lastDot > 0) {
1943 return d->m_strText.mid(position: lastDot + 1);
1944 } else {
1945 return QString();
1946 }
1947}
1948
1949QDebug operator<<(QDebug stream, const KFileItem &item)
1950{
1951 QDebugStateSaver saver(stream);
1952 stream.nospace();
1953 if (item.isNull()) {
1954 stream << "[null KFileItem]";
1955 } else {
1956 stream << "[KFileItem for " << item.url() << "]";
1957 }
1958 return stream;
1959}
1960
1961#include "moc_kfileitem.cpp"
1962

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