1 /*
2     This file is part of the KDE project
3     SPDX-FileCopyrightText: 2004 David Faure <faure@kde.org>
4 
5     SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "kio_trash.h"
9 #include "../../pathhelpers_p.h"
10 #include "kiotrashdebug.h"
11 
12 #include <KDirNotify>
13 #include <KLocalizedString>
14 #include <kio/job.h>
15 #include <kio/jobuidelegateextension.h>
16 
17 #include <QCoreApplication>
18 #include <QDataStream>
19 #include <QEventLoop>
20 #include <QFile>
21 #include <QMimeDatabase>
22 #include <QMimeType>
23 
24 #include <grp.h>
25 #include <pwd.h>
26 #include <stdlib.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <time.h>
30 
31 // Pseudo plugin class to embed meta data
32 class KIOPluginForMetaData : public QObject
33 {
34     Q_OBJECT
35     Q_PLUGIN_METADATA(IID "org.kde.kio.slave.trash" FILE "trash.json")
36 };
37 
38 extern "C" {
kdemain(int argc,char ** argv)39 int Q_DECL_EXPORT kdemain(int argc, char **argv)
40 {
41     // necessary to use other kio slaves
42     QCoreApplication app(argc, argv);
43 
44     KIO::setDefaultJobUiDelegateExtension(nullptr);
45     // start the slave
46     TrashProtocol slave(argv[1], argv[2], argv[3]);
47     slave.dispatchLoop();
48     return 0;
49 }
50 }
51 
isTopLevelEntry(const QUrl & url)52 static bool isTopLevelEntry(const QUrl &url)
53 {
54     const QString dir = url.adjusted(QUrl::RemoveFilename).path();
55     return dir.length() <= 1;
56 }
57 
TrashProtocol(const QByteArray & protocol,const QByteArray & pool,const QByteArray & app)58 TrashProtocol::TrashProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app)
59     : SlaveBase(protocol, pool, app)
60 {
61     struct passwd *user = getpwuid(getuid());
62     if (user) {
63         m_userName = QString::fromLatin1(user->pw_name);
64     }
65     struct group *grp = getgrgid(getgid());
66     if (grp) {
67         m_groupName = QString::fromLatin1(grp->gr_name);
68     }
69 }
70 
~TrashProtocol()71 TrashProtocol::~TrashProtocol()
72 {
73 }
74 
initImpl()75 bool TrashProtocol::initImpl()
76 {
77     if (!impl.init()) {
78         error(impl.lastErrorCode(), impl.lastErrorMessage());
79         return false;
80     }
81 
82     return true;
83 }
84 
enterLoop()85 void TrashProtocol::enterLoop()
86 {
87     QEventLoop eventLoop;
88     connect(this, &TrashProtocol::leaveModality, &eventLoop, &QEventLoop::quit);
89     eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
90 }
91 
restore(const QUrl & trashURL)92 void TrashProtocol::restore(const QUrl &trashURL)
93 {
94     int trashId;
95     QString fileId;
96     QString relativePath;
97     bool ok = TrashImpl::parseURL(trashURL, trashId, fileId, relativePath);
98     if (!ok) {
99         error(KIO::ERR_SLAVE_DEFINED, i18n("Malformed URL %1", trashURL.toString()));
100         return;
101     }
102     TrashedFileInfo info;
103     ok = impl.infoForFile(trashId, fileId, info);
104     if (!ok) {
105         error(impl.lastErrorCode(), impl.lastErrorMessage());
106         return;
107     }
108     QUrl dest = QUrl::fromLocalFile(info.origPath);
109     if (!relativePath.isEmpty()) {
110         dest.setPath(concatPaths(dest.path(), relativePath));
111     }
112 
113     // Check that the destination directory exists, to improve the error code in case it doesn't.
114     const QString destDir = dest.adjusted(QUrl::RemoveFilename).path();
115     QT_STATBUF buff;
116 
117     if (QT_LSTAT(QFile::encodeName(destDir).constData(), &buff) == -1) {
118         error(KIO::ERR_SLAVE_DEFINED,
119               i18n("The directory %1 does not exist anymore, so it is not possible to restore this item to its original location. "
120                    "You can either recreate that directory and use the restore operation again, or drag the item anywhere else to restore it.",
121                    destDir));
122         return;
123     }
124 
125     copyOrMoveFromTrash(trashURL, dest, false /*overwrite*/, Move);
126 }
127 
rename(const QUrl & oldURL,const QUrl & newURL,KIO::JobFlags flags)128 void TrashProtocol::rename(const QUrl &oldURL, const QUrl &newURL, KIO::JobFlags flags)
129 {
130     if (!initImpl()) {
131         return;
132     }
133 
134     qCDebug(KIO_TRASH) << "TrashProtocol::rename(): old=" << oldURL << " new=" << newURL << " overwrite=" << (flags & KIO::Overwrite);
135 
136     if (oldURL.scheme() == QLatin1String("trash") && newURL.scheme() == QLatin1String("trash")) {
137         if (!isTopLevelEntry(oldURL) || !isTopLevelEntry(newURL)) {
138             error(KIO::ERR_CANNOT_RENAME, oldURL.toString());
139             return;
140         }
141         int oldTrashId;
142         QString oldFileId;
143         QString oldRelativePath;
144         bool oldOk = TrashImpl::parseURL(oldURL, oldTrashId, oldFileId, oldRelativePath);
145         if (!oldOk) {
146             error(KIO::ERR_SLAVE_DEFINED, i18n("Malformed URL %1", oldURL.toString()));
147             return;
148         }
149         if (!oldRelativePath.isEmpty()) {
150             error(KIO::ERR_CANNOT_RENAME, oldURL.toString());
151             return;
152         }
153         // Dolphin/KIO can't specify a trashid in the new URL so here path == filename
154         // bool newOk = TrashImpl::parseURL(newURL, newTrashId, newFileId, newRelativePath);
155         const QString newFileId = newURL.path().mid(1);
156         if (newFileId.contains(QLatin1Char('/'))) {
157             error(KIO::ERR_CANNOT_RENAME, oldURL.toString());
158             return;
159         }
160         bool ok = impl.moveInTrash(oldTrashId, oldFileId, newFileId);
161         if (!ok) {
162             error(impl.lastErrorCode(), impl.lastErrorMessage());
163             return;
164         }
165         const QUrl finalUrl = TrashImpl::makeURL(oldTrashId, newFileId, QString());
166         org::kde::KDirNotify::emitFileRenamed(oldURL, finalUrl);
167         finished();
168         return;
169     }
170 
171     if (oldURL.scheme() == QLatin1String("trash") && newURL.isLocalFile()) {
172         copyOrMoveFromTrash(oldURL, newURL, (flags & KIO::Overwrite), Move);
173     } else if (oldURL.isLocalFile() && newURL.scheme() == QLatin1String("trash")) {
174         copyOrMoveToTrash(oldURL, newURL, Move);
175     } else {
176         error(KIO::ERR_UNSUPPORTED_ACTION, i18n("Invalid combination of protocols."));
177     }
178 }
179 
copy(const QUrl & src,const QUrl & dest,int,KIO::JobFlags flags)180 void TrashProtocol::copy(const QUrl &src, const QUrl &dest, int /*permissions*/, KIO::JobFlags flags)
181 {
182     if (!initImpl()) {
183         return;
184     }
185 
186     qCDebug(KIO_TRASH) << "TrashProtocol::copy(): " << src << " " << dest;
187 
188     if (src.scheme() == QLatin1String("trash") && dest.scheme() == QLatin1String("trash")) {
189         error(KIO::ERR_UNSUPPORTED_ACTION, i18n("This file is already in the trash bin."));
190         return;
191     }
192 
193     if (src.scheme() == QLatin1String("trash") && dest.isLocalFile()) {
194         copyOrMoveFromTrash(src, dest, (flags & KIO::Overwrite), Copy);
195     } else if (src.isLocalFile() && dest.scheme() == QLatin1String("trash")) {
196         copyOrMoveToTrash(src, dest, Copy);
197     } else {
198         error(KIO::ERR_UNSUPPORTED_ACTION, i18n("Invalid combination of protocols."));
199     }
200 }
201 
copyOrMoveFromTrash(const QUrl & src,const QUrl & dest,bool overwrite,CopyOrMove action)202 void TrashProtocol::copyOrMoveFromTrash(const QUrl &src, const QUrl &dest, bool overwrite, CopyOrMove action)
203 {
204     // Extracting (e.g. via dnd). Ignore original location stored in info file.
205     int trashId;
206     QString fileId;
207     QString relativePath;
208     bool ok = TrashImpl::parseURL(src, trashId, fileId, relativePath);
209     if (!ok) {
210         error(KIO::ERR_SLAVE_DEFINED, i18n("Malformed URL %1", src.toString()));
211         return;
212     }
213     const QString destPath = dest.path();
214     if (QFile::exists(destPath)) {
215         if (overwrite) {
216             ok = QFile::remove(destPath);
217             Q_ASSERT(ok); // ### TODO
218         } else {
219             error(KIO::ERR_FILE_ALREADY_EXIST, destPath);
220             return;
221         }
222     }
223 
224     if (action == Move) {
225         qCDebug(KIO_TRASH) << "calling moveFromTrash(" << destPath << " " << trashId << " " << fileId << ")";
226         ok = impl.moveFromTrash(destPath, trashId, fileId, relativePath);
227     } else { // Copy
228         qCDebug(KIO_TRASH) << "calling copyFromTrash(" << destPath << " " << trashId << " " << fileId << ")";
229         ok = impl.copyFromTrash(destPath, trashId, fileId, relativePath);
230     }
231     if (!ok) {
232         error(impl.lastErrorCode(), impl.lastErrorMessage());
233     } else {
234         if (action == Move && relativePath.isEmpty()) {
235             (void)impl.deleteInfo(trashId, fileId);
236         }
237         finished();
238     }
239 }
240 
copyOrMoveToTrash(const QUrl & src,const QUrl & dest,CopyOrMove action)241 void TrashProtocol::copyOrMoveToTrash(const QUrl &src, const QUrl &dest, CopyOrMove action)
242 {
243     qCDebug(KIO_TRASH) << "trashing a file" << src << dest;
244 
245     // Trashing a file
246     // We detect the case where this isn't normal trashing, but
247     // e.g. if kwrite tries to save (moving tempfile over destination)
248     if (isTopLevelEntry(dest) && src.fileName() == dest.fileName()) { // new toplevel entry
249         const QString srcPath = src.path();
250         // In theory we should use TrashImpl::parseURL to give the right filename to createInfo,
251         // in case the trash URL didn't contain the same filename as srcPath.
252         // But this can only happen with copyAs/moveAs, not available in the GUI
253         // for the trash (New/... or Rename from iconview/listview).
254         int trashId;
255         QString fileId;
256         if (!impl.createInfo(srcPath, trashId, fileId)) {
257             error(impl.lastErrorCode(), impl.lastErrorMessage());
258         } else {
259             bool ok;
260             if (action == Move) {
261                 qCDebug(KIO_TRASH) << "calling moveToTrash(" << srcPath << " " << trashId << " " << fileId << ")";
262                 ok = impl.moveToTrash(srcPath, trashId, fileId);
263             } else { // Copy
264                 qCDebug(KIO_TRASH) << "calling copyToTrash(" << srcPath << " " << trashId << " " << fileId << ")";
265                 ok = impl.copyToTrash(srcPath, trashId, fileId);
266             }
267             if (!ok) {
268                 (void)impl.deleteInfo(trashId, fileId);
269                 error(impl.lastErrorCode(), impl.lastErrorMessage());
270             } else {
271                 // Inform caller of the final URL. Used by konq_undo.
272                 const QUrl url = impl.makeURL(trashId, fileId, QString());
273                 setMetaData(QLatin1String("trashURL-") + srcPath, url.url());
274                 finished();
275             }
276         }
277     } else {
278         qCDebug(KIO_TRASH) << "returning KIO::ERR_ACCESS_DENIED, it's not allowed to add a file to an existing trash directory";
279         // It's not allowed to add a file to an existing trash directory.
280         error(KIO::ERR_ACCESS_DENIED, dest.toString());
281     }
282 }
283 
createTopLevelDirEntry(KIO::UDSEntry & entry)284 void TrashProtocol::createTopLevelDirEntry(KIO::UDSEntry &entry)
285 {
286     entry.reserve(entry.count() + 8);
287     entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("."));
288     entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Trash"));
289     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
290     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0700);
291     entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory"));
292     entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, impl.isEmpty() ? QStringLiteral("user-trash") : QStringLiteral("user-trash-full"));
293     entry.fastInsert(KIO::UDSEntry::UDS_USER, m_userName);
294     entry.fastInsert(KIO::UDSEntry::UDS_GROUP, m_groupName);
295 }
296 
getStatDetails()297 KIO::StatDetails TrashProtocol::getStatDetails()
298 {
299     // takes care of converting old metadata details to new StatDetails
300     // TODO KF6 : remove legacy "details" code path
301     KIO::StatDetails details;
302 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 69)
303     if (hasMetaData(QStringLiteral("statDetails"))) {
304 #endif
305         const QString statDetails = metaData(QStringLiteral("statDetails"));
306         details = statDetails.isEmpty() ? KIO::StatDefaultDetails : static_cast<KIO::StatDetails>(statDetails.toInt());
307 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 69)
308     } else {
309         const QString sDetails = metaData(QStringLiteral("details"));
310         details = sDetails.isEmpty() ? KIO::StatDefaultDetails : KIO::detailsToStatDetails(sDetails.toInt());
311     }
312 #endif
313     return details;
314 }
315 
stat(const QUrl & url)316 void TrashProtocol::stat(const QUrl &url)
317 {
318     if (!initImpl()) {
319         return;
320     }
321     const QString path = url.path();
322     if (path.isEmpty() || path == QLatin1String("/")) {
323         // The root is "virtual" - it's not a single physical directory
324         KIO::UDSEntry entry = impl.trashUDSEntry(getStatDetails());
325         createTopLevelDirEntry(entry);
326         statEntry(entry);
327         finished();
328     } else {
329         int trashId;
330         QString fileId;
331         QString relativePath;
332 
333         bool ok = TrashImpl::parseURL(url, trashId, fileId, relativePath);
334 
335         if (!ok) {
336             // ######## do we still need this?
337             qCDebug(KIO_TRASH) << url << " looks fishy, returning does-not-exist";
338             // A URL like trash:/file simply means that CopyJob is trying to see if
339             // the destination exists already (it made up the URL by itself).
340             error(KIO::ERR_DOES_NOT_EXIST, url.toString());
341             // error( KIO::ERR_SLAVE_DEFINED, i18n( "Malformed URL %1" ).arg( url.toString() ) );
342             return;
343         }
344 
345         qCDebug(KIO_TRASH) << "parsed" << url << "got" << trashId << fileId << relativePath;
346 
347         const QString filePath = impl.physicalPath(trashId, fileId, relativePath);
348         if (filePath.isEmpty()) {
349             error(impl.lastErrorCode(), impl.lastErrorMessage());
350             return;
351         }
352 
353         // For a toplevel file, use the fileId as display name (to hide the trashId)
354         // For a file in a subdir, use the fileName as is.
355         QString fileDisplayName = relativePath.isEmpty() ? fileId : url.fileName();
356 
357         QUrl fileURL;
358         if (url.path().length() > 1) {
359             fileURL = url;
360         }
361 
362         KIO::UDSEntry entry;
363         TrashedFileInfo info;
364         ok = impl.infoForFile(trashId, fileId, info);
365         if (ok) {
366             ok = createUDSEntry(filePath, fileDisplayName, fileURL.fileName(), entry, info);
367         }
368 
369         if (!ok) {
370             error(KIO::ERR_CANNOT_STAT, url.toString());
371             return;
372         }
373 
374         statEntry(entry);
375         finished();
376     }
377 }
378 
del(const QUrl & url,bool)379 void TrashProtocol::del(const QUrl &url, bool /*isfile*/)
380 {
381     if (!initImpl()) {
382         return;
383     }
384     int trashId;
385     QString fileId;
386     QString relativePath;
387 
388     bool ok = TrashImpl::parseURL(url, trashId, fileId, relativePath);
389     if (!ok) {
390         error(KIO::ERR_SLAVE_DEFINED, i18n("Malformed URL %1", url.toString()));
391         return;
392     }
393 
394     ok = relativePath.isEmpty();
395     if (!ok) {
396         error(KIO::ERR_ACCESS_DENIED, url.toString());
397         return;
398     }
399 
400     ok = impl.del(trashId, fileId);
401     if (!ok) {
402         error(impl.lastErrorCode(), impl.lastErrorMessage());
403         return;
404     }
405 
406     finished();
407 }
408 
listDir(const QUrl & url)409 void TrashProtocol::listDir(const QUrl &url)
410 {
411     if (!initImpl()) {
412         return;
413     }
414     qCDebug(KIO_TRASH) << "listdir: " << url;
415     const QString path = url.path();
416     if (path.isEmpty() || path == QLatin1String("/")) {
417         listRoot();
418         return;
419     }
420     int trashId;
421     QString fileId;
422     QString relativePath;
423     bool ok = TrashImpl::parseURL(url, trashId, fileId, relativePath);
424     if (!ok) {
425         error(KIO::ERR_SLAVE_DEFINED, i18n("Malformed URL %1", url.toString()));
426         return;
427     }
428     // was: const QString physicalPath = impl.physicalPath( trashId, fileId, relativePath );
429 
430     // Get info for deleted directory - the date of deletion and orig path will be used
431     // for all the items in it, and we need the physicalPath.
432     TrashedFileInfo info;
433     ok = impl.infoForFile(trashId, fileId, info);
434     if (!ok || info.physicalPath.isEmpty()) {
435         error(impl.lastErrorCode(), impl.lastErrorMessage());
436         return;
437     }
438     if (!relativePath.isEmpty()) {
439         info.physicalPath += QLatin1Char('/') + relativePath;
440     }
441 
442     // List subdir. Can't use kio_file here since we provide our own info...
443     qCDebug(KIO_TRASH) << "listing " << info.physicalPath;
444     const QStringList entryNames = impl.listDir(info.physicalPath);
445     totalSize(entryNames.count());
446     KIO::UDSEntry entry;
447     for (const QString &fileName : entryNames) {
448         if (fileName == QLatin1String("..")) {
449             continue;
450         }
451         const QString filePath = info.physicalPath + QLatin1Char('/') + fileName;
452         // shouldn't be necessary
453         // const QString url = TrashImpl::makeURL( trashId, fileId, relativePath + '/' + fileName );
454         entry.clear();
455         TrashedFileInfo infoForItem(info);
456         infoForItem.origPath += QLatin1Char('/') + fileName;
457         if (createUDSEntry(filePath, fileName, fileName, entry, infoForItem)) {
458             listEntry(entry);
459         }
460     }
461     entry.clear();
462     finished();
463 }
464 
createUDSEntry(const QString & physicalPath,const QString & displayFileName,const QString & internalFileName,KIO::UDSEntry & entry,const TrashedFileInfo & info)465 bool TrashProtocol::createUDSEntry(const QString &physicalPath,
466                                    const QString &displayFileName,
467                                    const QString &internalFileName,
468                                    KIO::UDSEntry &entry,
469                                    const TrashedFileInfo &info)
470 {
471     entry.reserve(13);
472     QByteArray physicalPath_c = QFile::encodeName(physicalPath);
473     QT_STATBUF buff;
474     if (QT_LSTAT(physicalPath_c.constData(), &buff) == -1) {
475         qCWarning(KIO_TRASH) << "couldn't stat " << physicalPath << ", relevant trashinfo file will be removed";
476         impl.deleteInfo(info.trashId, info.fileId);
477         return false;
478     }
479     if (S_ISLNK(buff.st_mode)) {
480         char buffer2[1000];
481         int n = ::readlink(physicalPath_c.constData(), buffer2, 999);
482         if (n != -1) {
483             buffer2[n] = 0;
484         }
485 
486         entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, QFile::decodeName(buffer2));
487         // Follow symlink
488         // That makes sense in kio_file, but not in the trash, especially for the size
489         // #136876
490 #if 0
491         if (KDE_stat(physicalPath_c, &buff) == -1) {
492             // It is a link pointing to nowhere
493             buff.st_mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO;
494             buff.st_mtime = 0;
495             buff.st_atime = 0;
496             buff.st_size = 0;
497         }
498 #endif
499     }
500 
501     mode_t type = buff.st_mode & S_IFMT; // extract file type
502     mode_t access = buff.st_mode & 07777; // extract permissions
503     access &= 07555; // make it readonly, since it's in the trashcan
504     Q_ASSERT(!internalFileName.isEmpty());
505     entry.fastInsert(KIO::UDSEntry::UDS_NAME, internalFileName); // internal filename, like "0-foo"
506     entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, displayFileName); // user-visible filename, like "foo"
507     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, type);
508     // if ( !url.isEmpty() )
509     //    entry.insert( KIO::UDSEntry::UDS_URL, url );
510 
511     QMimeDatabase db;
512     QMimeType mt = db.mimeTypeForFile(physicalPath);
513     if (mt.isValid()) {
514         entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, mt.name());
515     }
516     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, access);
517     entry.fastInsert(KIO::UDSEntry::UDS_SIZE, buff.st_size);
518     entry.fastInsert(KIO::UDSEntry::UDS_USER, m_userName); // assumption
519     entry.fastInsert(KIO::UDSEntry::UDS_GROUP, m_groupName); // assumption
520     entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, buff.st_mtime);
521     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, buff.st_atime); // ## or use it for deletion time?
522     entry.fastInsert(KIO::UDSEntry::UDS_EXTRA, info.origPath);
523     entry.fastInsert(KIO::UDSEntry::UDS_EXTRA + 1, info.deletionDate.toString(Qt::ISODate));
524     return true;
525 }
526 
listRoot()527 void TrashProtocol::listRoot()
528 {
529     if (!initImpl()) {
530         return;
531     }
532     const TrashedFileInfoList lst = impl.list();
533     totalSize(lst.count());
534     KIO::UDSEntry entry;
535     createTopLevelDirEntry(entry);
536     listEntry(entry);
537     for (const TrashedFileInfo &fileInfo : lst) {
538         const QUrl url = TrashImpl::makeURL(fileInfo.trashId, fileInfo.fileId, QString());
539         entry.clear();
540         const QString fileDisplayName = fileInfo.fileId;
541 
542         if (createUDSEntry(fileInfo.physicalPath, fileDisplayName, url.fileName(), entry, fileInfo)) {
543             listEntry(entry);
544         }
545     }
546     entry.clear();
547     finished();
548 }
549 
special(const QByteArray & data)550 void TrashProtocol::special(const QByteArray &data)
551 {
552     if (!initImpl()) {
553         return;
554     }
555     QDataStream stream(data);
556     int cmd;
557     stream >> cmd;
558 
559     switch (cmd) {
560     case 1:
561         if (impl.emptyTrash()) {
562             finished();
563         } else {
564             error(impl.lastErrorCode(), impl.lastErrorMessage());
565         }
566         break;
567     case 2:
568         impl.migrateOldTrash();
569         finished();
570         break;
571     case 3: {
572         QUrl url;
573         stream >> url;
574         restore(url);
575         break;
576     }
577     default:
578         qCWarning(KIO_TRASH) << "Unknown command in special(): " << cmd;
579         error(KIO::ERR_UNSUPPORTED_ACTION, QString::number(cmd));
580         break;
581     }
582 }
583 
put(const QUrl & url,int,KIO::JobFlags)584 void TrashProtocol::put(const QUrl &url, int /*permissions*/, KIO::JobFlags)
585 {
586     if (!initImpl()) {
587         return;
588     }
589     qCDebug(KIO_TRASH) << "put: " << url;
590     // create deleted file. We need to get the mtime and original location from metadata...
591     // Maybe we can find the info file for url.fileName(), in case ::rename() was called first, and failed...
592     error(KIO::ERR_ACCESS_DENIED, url.toString());
593 }
594 
get(const QUrl & url)595 void TrashProtocol::get(const QUrl &url)
596 {
597     if (!initImpl()) {
598         return;
599     }
600     qCDebug(KIO_TRASH) << "get() : " << url;
601     if (!url.isValid()) {
602         // qCDebug(KIO_TRASH) << kBacktrace();
603         error(KIO::ERR_SLAVE_DEFINED, i18n("Malformed URL %1", url.url()));
604         return;
605     }
606     if (url.path().length() <= 1) {
607         error(KIO::ERR_IS_DIRECTORY, url.toString());
608         return;
609     }
610     int trashId;
611     QString fileId;
612     QString relativePath;
613     bool ok = TrashImpl::parseURL(url, trashId, fileId, relativePath);
614     if (!ok) {
615         error(KIO::ERR_SLAVE_DEFINED, i18n("Malformed URL %1", url.toString()));
616         return;
617     }
618     const QString physicalPath = impl.physicalPath(trashId, fileId, relativePath);
619     if (physicalPath.isEmpty()) {
620         error(impl.lastErrorCode(), impl.lastErrorMessage());
621         return;
622     }
623 
624     // Usually we run jobs in TrashImpl (for e.g. future kdedmodule)
625     // But for this one we wouldn't use DCOP for every bit of data...
626     QUrl fileURL = QUrl::fromLocalFile(physicalPath);
627     KIO::TransferJob *job = KIO::get(fileURL, KIO::NoReload, KIO::HideProgressInfo);
628     connect(job, &KIO::TransferJob::data, this, &TrashProtocol::slotData);
629     connect(job, &KIO::TransferJob::mimeTypeFound, this, &TrashProtocol::slotMimetype);
630     connect(job, &KJob::result, this, &TrashProtocol::jobFinished);
631     enterLoop();
632 }
633 
slotData(KIO::Job *,const QByteArray & arr)634 void TrashProtocol::slotData(KIO::Job *, const QByteArray &arr)
635 {
636     data(arr);
637 }
638 
slotMimetype(KIO::Job *,const QString & mt)639 void TrashProtocol::slotMimetype(KIO::Job *, const QString &mt)
640 {
641     mimeType(mt);
642 }
643 
jobFinished(KJob * job)644 void TrashProtocol::jobFinished(KJob *job)
645 {
646     if (job->error()) {
647         error(job->error(), job->errorText());
648     } else {
649         finished();
650     }
651     Q_EMIT leaveModality();
652 }
653 
654 #if 0
655 void TrashProtocol::mkdir(const QUrl &url, int /*permissions*/)
656 {
657     if (!initImpl()) {
658         return;
659     }
660     // create info about deleted dir
661     // ############ Problem: we don't know the original path.
662     // Let's try to avoid this case (we should get to copy() instead, for local files)
663     qCDebug(KIO_TRASH) << "mkdir: " << url;
664     QString dir = url.adjusted(QUrl::RemoveFilename).path();
665 
666     if (dir.length() <= 1) { // new toplevel entry
667         // ## we should use TrashImpl::parseURL to give the right filename to createInfo
668         int trashId;
669         QString fileId;
670         if (!impl.createInfo(url.path(), trashId, fileId)) {
671             error(impl.lastErrorCode(), impl.lastErrorMessage());
672         } else {
673             if (!impl.mkdir(trashId, fileId, permissions)) {
674                 (void)impl.deleteInfo(trashId, fileId);
675                 error(impl.lastErrorCode(), impl.lastErrorMessage());
676             } else {
677                 finished();
678             }
679         }
680     } else {
681         // Well it's not allowed to add a directory to an existing deleted directory.
682         error(KIO::ERR_ACCESS_DENIED, url.toString());
683     }
684 }
685 #endif
686 
virtual_hook(int id,void * data)687 void TrashProtocol::virtual_hook(int id, void *data)
688 {
689     switch (id) {
690     case SlaveBase::GetFileSystemFreeSpace: {
691         QUrl *url = static_cast<QUrl *>(data);
692         fileSystemFreeSpace(*url);
693         break;
694     }
695     default:
696         SlaveBase::virtual_hook(id, data);
697     }
698 }
699 
fileSystemFreeSpace(const QUrl & url)700 void TrashProtocol::fileSystemFreeSpace(const QUrl &url)
701 {
702     qCDebug(KIO_TRASH) << "fileSystemFreeSpace:" << url;
703 
704     if (!initImpl()) {
705         return;
706     }
707 
708     TrashImpl::TrashSpaceInfo spaceInfo;
709     if (!impl.trashSpaceInfo(url.path(), spaceInfo)) {
710         error(KIO::ERR_CANNOT_STAT, url.toDisplayString());
711         return;
712     }
713 
714     setMetaData(QStringLiteral("total"), QString::number(spaceInfo.totalSize));
715     setMetaData(QStringLiteral("available"), QString::number(spaceInfo.availableSize));
716 
717     finished();
718 }
719 
720 #include "kio_trash.moc"
721