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