File indexing completed on 2024-05-05 05:28:55

0001 /*
0002  *  SPDX-FileCopyrightText: 2010-2012 Alejandro Fiestas Olivares <afiestas@kde.org>
0003  *  SPDX-FileCopyrightText: 2010 UFO Coders <info@ufocoders.com>
0004  *  SPDX-FileCopyrightText: 2014-2015 David Rosca <nowrep@gmail.com>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "kioobexftp.h"
0010 #include "bluedevil_kio_obexftp.h"
0011 #include "transferfilejob.h"
0012 #include "version.h"
0013 #include <kio_version.h>
0014 
0015 #include <unistd.h>
0016 
0017 #include <QCoreApplication>
0018 #include <QMimeData>
0019 #include <QMimeDatabase>
0020 #include <QTemporaryFile>
0021 
0022 #include <KLocalizedString>
0023 
0024 #include <BluezQt/ObexTransfer>
0025 #include <BluezQt/PendingCall>
0026 
0027 // Pseudo plugin class to embed meta data
0028 class KIOPluginForMetaData : public QObject
0029 {
0030     Q_OBJECT
0031     Q_PLUGIN_METADATA(IID "org.kde.kio.slave.obexftp" FILE "obexftp.json")
0032 };
0033 
0034 extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv)
0035 {
0036     QCoreApplication app(argc, argv);
0037 
0038     if (argc != 4) {
0039         fprintf(stderr, "Usage: kio_obexftp protocol domain-socket1 domain-socket2\n");
0040         exit(-1);
0041     }
0042 
0043     KioFtp slave(argv[2], argv[3]);
0044     slave.dispatchLoop();
0045     return 0;
0046 }
0047 
0048 static QString urlDirectory(const QUrl &url)
0049 {
0050     const QUrl &u = url.adjusted(QUrl::StripTrailingSlash);
0051     return u.adjusted(QUrl::RemoveFilename).path();
0052 }
0053 
0054 static QString urlFileName(const QUrl &url)
0055 {
0056     const QUrl &u = url.adjusted(QUrl::StripTrailingSlash);
0057     return u.fileName();
0058 }
0059 
0060 static QUrl urlUpDir(const QUrl &url)
0061 {
0062     const QUrl &u = url.adjusted(QUrl::StripTrailingSlash);
0063     return u.adjusted(QUrl::RemoveFilename);
0064 }
0065 
0066 static bool urlIsRoot(const QUrl &url)
0067 {
0068     const QString &directory = urlDirectory(url);
0069     return (directory.isEmpty() || directory == QLatin1String("/")) && urlFileName(url).isEmpty();
0070 }
0071 
0072 KioFtp::KioFtp(const QByteArray &pool, const QByteArray &app)
0073     : WorkerBase(QByteArrayLiteral("obexftp"), pool, app)
0074     , m_transfer(nullptr)
0075 {
0076     m_kded = new org::kde::BlueDevil::ObexFtp(QStringLiteral("org.kde.kded6"), QStringLiteral("/modules/bluedevil"), QDBusConnection::sessionBus(), this);
0077 }
0078 
0079 void KioFtp::connectToHost()
0080 {
0081     const QString &target = m_kded->preferredTarget(m_host);
0082 
0083     if (target != QLatin1String("ftp")) {
0084         if (createSession(target)) {
0085             return;
0086         }
0087         // Fallback to ftp
0088     }
0089 
0090     createSession(QStringLiteral("ftp"));
0091 }
0092 
0093 KIO::WorkerResult KioFtp::testConnection()
0094 {
0095     if (!m_kded->isOnline().value()) {
0096         return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("Obexd service is not running."));
0097     }
0098 
0099     connectToHost();
0100 
0101     if (!m_transfer) {
0102         return KIO::WorkerResult::fail(KIO::ERR_CANNOT_CONNECT, m_host);
0103     }
0104     return KIO::WorkerResult::pass();
0105 }
0106 
0107 bool KioFtp::createSession(const QString &target)
0108 {
0109     QDBusPendingReply<QString> reply = m_kded->session(m_host, target);
0110     reply.waitForFinished();
0111 
0112     const QString &sessionPath = reply.value();
0113 
0114     if (reply.isError() || sessionPath.isEmpty()) {
0115         qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Create session error" << reply.error().name() << reply.error().message();
0116         delete m_transfer;
0117         m_transfer = nullptr;
0118         m_sessionPath.clear();
0119         return false;
0120     }
0121 
0122     if (m_sessionPath != sessionPath) {
0123         m_statMap.clear();
0124         delete m_transfer;
0125         m_transfer = new BluezQt::ObexFileTransfer(QDBusObjectPath(sessionPath));
0126         m_sessionPath = sessionPath;
0127     }
0128 
0129     return true;
0130 }
0131 
0132 KIO::WorkerResult KioFtp::listDir(const QUrl &url)
0133 {
0134     if (auto result = testConnection(); !result.success()) {
0135         return result;
0136     }
0137 
0138     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "listdir: " << url;
0139 
0140     infoMessage(i18n("Retrieving information from remote deviceā€¦"));
0141 
0142     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Asking for listFolder" << url.path();
0143 
0144     if (auto result = changeFolder(url.path()); !result.success()) {
0145         return result;
0146     }
0147 
0148     const auto [result, entries] = listFolder(url);
0149     if (!result.success()) {
0150         return result;
0151     }
0152 
0153     for (const KIO::UDSEntry &entry : entries) {
0154         listEntry(entry);
0155     }
0156 
0157     return KIO::WorkerResult::pass();
0158 }
0159 
0160 KIO::WorkerResult KioFtp::copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags)
0161 {
0162     Q_UNUSED(permissions)
0163     Q_UNUSED(flags)
0164 
0165     if (auto result = testConnection(); !result.success()) {
0166         return result;
0167     }
0168 
0169     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "copy: " << src.url() << " to " << dest.url();
0170 
0171     return copyHelper(src, dest);
0172 }
0173 
0174 KIO::WorkerResult KioFtp::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags)
0175 {
0176     Q_UNUSED(src)
0177     Q_UNUSED(dest)
0178     Q_UNUSED(flags)
0179 
0180     return KIO::WorkerResult::fail(KIO::ERR_UNSUPPORTED_ACTION, QString());
0181 }
0182 
0183 KIO::WorkerResult KioFtp::get(const QUrl &url)
0184 {
0185     if (auto result = testConnection(); !result.success()) {
0186         return result;
0187     }
0188 
0189     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "get" << url;
0190 
0191     QTemporaryFile tempFile(QStringLiteral("%1/kioftp_XXXXXX.%2").arg(QDir::tempPath(), urlFileName(url)));
0192     tempFile.open();
0193 
0194     if (auto result = copyHelper(url, QUrl::fromLocalFile(tempFile.fileName())); !result.success()) {
0195         return result;
0196     }
0197 
0198     QMimeDatabase mimeDatabase;
0199     const QMimeType &mime = mimeDatabase.mimeTypeForFile(tempFile.fileName());
0200     mimeType(mime.name());
0201     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Mime: " << mime.name();
0202 
0203     totalSize(tempFile.size());
0204     data(tempFile.readAll());
0205     return KIO::WorkerResult::pass();
0206 }
0207 
0208 bool KioFtp::cancelTransfer(const QString &transfer)
0209 {
0210     return m_kded->cancelTransfer(transfer);
0211 }
0212 
0213 void KioFtp::setHost(const QString &host, quint16 port, const QString &user, const QString &pass)
0214 {
0215     Q_UNUSED(port)
0216     Q_UNUSED(user)
0217     Q_UNUSED(pass)
0218 
0219     m_host = host;
0220     m_host = m_host.replace(QLatin1Char('-'), QLatin1Char(':')).toUpper();
0221 
0222     infoMessage(i18n("Connecting to the device"));
0223 
0224     connectToHost();
0225 }
0226 
0227 KIO::WorkerResult KioFtp::del(const QUrl &url, bool isfile)
0228 {
0229     Q_UNUSED(isfile)
0230 
0231     if (auto result = testConnection(); !result.success()) {
0232         return result;
0233     }
0234 
0235     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Del: " << url.url();
0236 
0237     if (auto result = changeFolder(urlDirectory(url)); !result.success()) {
0238         return result;
0239     }
0240 
0241     if (auto result = deleteFile(urlFileName(url)); !result.success()) {
0242         return result;
0243     }
0244 
0245     return KIO::WorkerResult::pass();
0246 }
0247 
0248 KIO::WorkerResult KioFtp::mkdir(const QUrl &url, int permissions)
0249 {
0250     Q_UNUSED(permissions)
0251 
0252     if (auto result = testConnection(); !result.success()) {
0253         return result;
0254     }
0255 
0256     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "MkDir: " << url.url();
0257 
0258     if (auto result = changeFolder(urlDirectory(url)); !result.success()) {
0259         return result;
0260     }
0261 
0262     if (auto result = createFolder(urlFileName(url)); !result.success()) {
0263         return result;
0264     }
0265 
0266     return KIO::WorkerResult::pass();
0267 }
0268 
0269 KIO::WorkerResult KioFtp::stat(const QUrl &url)
0270 {
0271     if (auto result = testConnection(); !result.success()) {
0272         return result;
0273     }
0274 
0275     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat: " << url.url();
0276     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat Dir: " << urlDirectory(url);
0277     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat File: " << urlFileName(url);
0278     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Empty Dir: " << urlDirectory(url).isEmpty();
0279 
0280     return statHelper(url);
0281 }
0282 
0283 KIO::WorkerResult KioFtp::copyHelper(const QUrl &src, const QUrl &dest)
0284 {
0285     if (src.scheme() == QLatin1String("obexftp") && dest.scheme() == QLatin1String("obexftp")) {
0286         return copyWithinObexftp(src, dest);
0287     }
0288 
0289     if (src.scheme() == QLatin1String("obexftp")) {
0290         return copyFromObexftp(src, dest);
0291     }
0292 
0293     if (dest.scheme() == QLatin1String("obexftp")) {
0294         return copyToObexftp(src, dest);
0295     }
0296 
0297     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "This shouldn't happen...";
0298     return KIO::WorkerResult::fail(KIO::ERR_UNKNOWN, i18n("This should not happen"));
0299 }
0300 
0301 KIO::WorkerResult KioFtp::copyWithinObexftp(const QUrl &src, const QUrl &dest)
0302 {
0303     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Source: " << src << "Dest:" << dest;
0304 
0305     if (auto result = changeFolder(urlDirectory(src)); !result.success()) {
0306         return result;
0307     }
0308 
0309     BluezQt::PendingCall *call = m_transfer->copyFile(src.path(), dest.path());
0310     call->waitForFinished();
0311 
0312     if (call->error()) {
0313         // Copying files within obexftp is currently not implemented in obexd
0314         if (call->errorText() == QLatin1String("Not Implemented")) {
0315             return KIO::WorkerResult::fail(KIO::ERR_UNSUPPORTED_ACTION, src.path());
0316         } else {
0317             return KIO::WorkerResult::fail(KIO::ERR_CANNOT_WRITE, src.path());
0318         }
0319     }
0320 
0321     return KIO::WorkerResult::pass();
0322 }
0323 
0324 KIO::WorkerResult KioFtp::copyFromObexftp(const QUrl &src, const QUrl &dest)
0325 {
0326     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Source: " << src << "Dest:" << dest;
0327 
0328     if (auto result = changeFolder(urlDirectory(src)); !result.success()) {
0329         return result;
0330     }
0331 
0332     if (!m_statMap.contains(src.toDisplayString())) {
0333         auto [result, _] = listFolder(urlUpDir(src));
0334         if (!result.success()) {
0335             return result;
0336         }
0337     }
0338 
0339     BluezQt::PendingCall *call = m_transfer->getFile(dest.path(), urlFileName(src));
0340     call->waitForFinished();
0341 
0342     int size = m_statMap.value(src.toDisplayString()).numberValue(KIO::UDSEntry::UDS_SIZE);
0343     totalSize(size);
0344 
0345     BluezQt::ObexTransferPtr transfer = call->value().value<BluezQt::ObexTransferPtr>();
0346     TransferFileJob *getFile = new TransferFileJob(transfer, this);
0347     getFile->exec();
0348 
0349     return KIO::WorkerResult::pass();
0350 }
0351 
0352 KIO::WorkerResult KioFtp::copyToObexftp(const QUrl &src, const QUrl &dest)
0353 {
0354     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Source:" << src << "Dest:" << dest;
0355 
0356     if (auto result = changeFolder(urlDirectory(dest)); !result.success()) {
0357         return result;
0358     }
0359 
0360     BluezQt::PendingCall *call = m_transfer->putFile(src.path(), urlFileName(dest));
0361     call->waitForFinished();
0362 
0363     int size = QFile(src.path()).size();
0364     totalSize(size);
0365 
0366     BluezQt::ObexTransferPtr transfer = call->value().value<BluezQt::ObexTransferPtr>();
0367     TransferFileJob *putFile = new TransferFileJob(transfer, this);
0368     putFile->exec();
0369 
0370     return KIO::WorkerResult::pass();
0371 }
0372 
0373 KIO::WorkerResult KioFtp::statHelper(const QUrl &url)
0374 {
0375     if (m_statMap.contains(url.toDisplayString())) {
0376         qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "statMap contains the url";
0377         statEntry(m_statMap.value(url.toDisplayString()));
0378         return KIO::WorkerResult::pass();
0379     }
0380 
0381     if (urlIsRoot(url)) {
0382         qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Url is root";
0383         KIO::UDSEntry entry;
0384         entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("/"));
0385         entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0386         entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0700);
0387 
0388         qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Adding stat cache" << url.toDisplayString();
0389         m_statMap.insert(url.toDisplayString(), entry);
0390         statEntry(entry);
0391         return KIO::WorkerResult::pass();
0392     }
0393 
0394     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "statMap does not contains the url";
0395 
0396     if (auto result = changeFolder(urlDirectory(url)); !result.success()) {
0397         return result;
0398     }
0399 
0400     auto [result, _] = listFolder(urlUpDir(url));
0401     if (!result.success()) {
0402         return result;
0403     }
0404 
0405     if (!m_statMap.contains(url.toDisplayString())) {
0406         qCWarning(BLUEDEVIL_KIO_OBEXFTP_LOG) << "statMap still does not contains the url!";
0407     }
0408 
0409     statEntry(m_statMap.value(url.toDisplayString()));
0410 
0411     return KIO::WorkerResult::pass();
0412 }
0413 
0414 KioFtp::ListResult KioFtp::listFolder(const QUrl &url)
0415 {
0416     QList<KIO::UDSEntry> list;
0417 
0418     BluezQt::PendingCall *call = m_transfer->listFolder();
0419     call->waitForFinished();
0420 
0421     if (call->error()) {
0422         qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "List folder error" << call->errorText();
0423         return {KIO::WorkerResult::fail(KIO::ERR_CANNOT_OPEN_FOR_READING, url.path()), {}};
0424     }
0425 
0426     const QList<BluezQt::ObexFileTransferEntry> &items = call->value().value<QList<BluezQt::ObexFileTransferEntry>>();
0427 
0428     for (const BluezQt::ObexFileTransferEntry &item : items) {
0429         if (!item.isValid()) {
0430             continue;
0431         }
0432 
0433         KIO::UDSEntry entry;
0434         entry.fastInsert(KIO::UDSEntry::UDS_NAME, item.name());
0435         entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, item.label());
0436         entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0700);
0437         entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, item.modificationTime().toSecsSinceEpoch());
0438         entry.fastInsert(KIO::UDSEntry::UDS_SIZE, item.size());
0439 
0440         if (item.type() == BluezQt::ObexFileTransferEntry::Folder) {
0441             entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0442         } else if (item.type() == BluezQt::ObexFileTransferEntry::File) {
0443             entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
0444         }
0445 
0446         if (urlIsRoot(url)) {
0447             updateRootEntryIcon(entry, item.memoryType());
0448         }
0449 
0450         list.append(entry);
0451 
0452         // Most probably the client of the kio will stat each file
0453         // so since we are on it, let's cache all of them.
0454         QUrl statUrl = url;
0455 
0456         if (statUrl.path().endsWith('/')) {
0457             statUrl.setPath(statUrl.path() + item.name());
0458         } else {
0459             statUrl.setPath(statUrl.path() + QLatin1Char('/') + item.name());
0460         }
0461 
0462         if (!m_statMap.contains(statUrl.toDisplayString())) {
0463             qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat:" << statUrl.toDisplayString() << entry.stringValue(KIO::UDSEntry::UDS_NAME)
0464                                                << entry.numberValue(KIO::UDSEntry::UDS_SIZE);
0465             m_statMap.insert(statUrl.toDisplayString(), entry);
0466         }
0467     }
0468 
0469     return {KIO::WorkerResult::pass(), list};
0470 }
0471 
0472 KIO::WorkerResult KioFtp::changeFolder(const QString &folder)
0473 {
0474     BluezQt::PendingCall *call = m_transfer->changeFolder(folder);
0475     call->waitForFinished();
0476 
0477     if (call->error()) {
0478         return KIO::WorkerResult::fail(KIO::ERR_CANNOT_ENTER_DIRECTORY, folder);
0479     }
0480     return KIO::WorkerResult::pass();
0481 }
0482 
0483 KIO::WorkerResult KioFtp::createFolder(const QString &folder)
0484 {
0485     BluezQt::PendingCall *call = m_transfer->createFolder(folder);
0486     call->waitForFinished();
0487 
0488     if (call->error()) {
0489         return KIO::WorkerResult::fail(KIO::ERR_CANNOT_MKDIR, folder);
0490     }
0491     return KIO::WorkerResult::pass();
0492 }
0493 
0494 KIO::WorkerResult KioFtp::deleteFile(const QString &file)
0495 {
0496     BluezQt::PendingCall *call = m_transfer->deleteFile(file);
0497     call->waitForFinished();
0498 
0499     if (call->error()) {
0500         return KIO::WorkerResult::fail(KIO::ERR_CANNOT_DELETE, file);
0501     }
0502     return KIO::WorkerResult::pass();
0503 }
0504 
0505 void KioFtp::updateRootEntryIcon(KIO::UDSEntry &entry, const QString &memoryType)
0506 {
0507     const QString &path = entry.stringValue(KIO::UDSEntry::UDS_NAME);
0508 
0509     // Nokia (mount-points are C: D: E: ...)
0510     if (path.size() == 2 && path.at(1) == QLatin1Char(':')) {
0511         if (memoryType.startsWith(QLatin1String("DEV"))) {
0512             entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("drive-removable-media"));
0513         } else if (memoryType == QLatin1String("MMC")) {
0514             entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("media-flash-sd-mmc"));
0515         }
0516     }
0517 
0518     // Android
0519     if (entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1String("PHONE_MEMORY")) {
0520         entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Phone memory"));
0521         entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("drive-removable-media"));
0522     } else if (entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1String("EXTERNAL_MEMORY")) {
0523         entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("External memory"));
0524         entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("media-flash-sd-mmc"));
0525     }
0526 }
0527 
0528 #include "kioobexftp.moc"
0529 
0530 #include "moc_kioobexftp.cpp"