File indexing completed on 2024-05-05 17:32:40

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     : SlaveBase(QByteArrayLiteral("obexftp"), pool, app)
0074     , m_transfer(nullptr)
0075 {
0076     m_kded = new org::kde::BlueDevil::ObexFtp(QStringLiteral("org.kde.kded5"), 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 bool KioFtp::testConnection()
0094 {
0095     if (!m_kded->isOnline().value()) {
0096 #if KIO_VERSION >= QT_VERSION_CHECK(5, 96, 0)
0097         error(KIO::ERR_WORKER_DEFINED, i18n("Obexd service is not running."));
0098 #else
0099         error(KIO::ERR_SLAVE_DEFINED, i18n("Obexd service is not running."));
0100 #endif
0101         return false;
0102     }
0103 
0104     connectToHost();
0105 
0106     if (!m_transfer) {
0107         error(KIO::ERR_CANNOT_CONNECT, m_host);
0108         return false;
0109     }
0110     return true;
0111 }
0112 
0113 bool KioFtp::createSession(const QString &target)
0114 {
0115     QDBusPendingReply<QString> reply = m_kded->session(m_host, target);
0116     reply.waitForFinished();
0117 
0118     const QString &sessionPath = reply.value();
0119 
0120     if (reply.isError() || sessionPath.isEmpty()) {
0121         qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Create session error" << reply.error().name() << reply.error().message();
0122         delete m_transfer;
0123         m_transfer = nullptr;
0124         m_sessionPath.clear();
0125         return false;
0126     }
0127 
0128     if (m_sessionPath != sessionPath) {
0129         m_statMap.clear();
0130         delete m_transfer;
0131         m_transfer = new BluezQt::ObexFileTransfer(QDBusObjectPath(sessionPath));
0132         m_sessionPath = sessionPath;
0133     }
0134 
0135     return true;
0136 }
0137 
0138 void KioFtp::listDir(const QUrl &url)
0139 {
0140     if (!testConnection()) {
0141         return;
0142     }
0143 
0144     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "listdir: " << url;
0145 
0146     infoMessage(i18n("Retrieving information from remote deviceā€¦"));
0147 
0148     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Asking for listFolder" << url.path();
0149 
0150     if (!changeFolder(url.path())) {
0151         return;
0152     }
0153 
0154     bool ok;
0155     const QList<KIO::UDSEntry> &list = listFolder(url, &ok);
0156     if (!ok) {
0157         return;
0158     }
0159 
0160     for (const KIO::UDSEntry &entry : list) {
0161         listEntry(entry);
0162     }
0163 
0164     finished();
0165 }
0166 
0167 void KioFtp::copy(const QUrl &src, const QUrl &dest, int permissions, KIO::JobFlags flags)
0168 {
0169     Q_UNUSED(permissions)
0170     Q_UNUSED(flags)
0171 
0172     if (!testConnection()) {
0173         return;
0174     }
0175 
0176     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "copy: " << src.url() << " to " << dest.url();
0177 
0178     copyHelper(src, dest);
0179 
0180     finished();
0181 }
0182 
0183 void KioFtp::rename(const QUrl &src, const QUrl &dest, KIO::JobFlags flags)
0184 {
0185     Q_UNUSED(src)
0186     Q_UNUSED(dest)
0187     Q_UNUSED(flags)
0188 
0189     error(KIO::ERR_UNSUPPORTED_ACTION, QString());
0190 }
0191 
0192 void KioFtp::get(const QUrl &url)
0193 {
0194     if (!testConnection()) {
0195         return;
0196     }
0197 
0198     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "get" << url;
0199 
0200     QTemporaryFile tempFile(QStringLiteral("%1/kioftp_XXXXXX.%2").arg(QDir::tempPath(), urlFileName(url)));
0201     tempFile.open();
0202 
0203     copyHelper(url, QUrl::fromLocalFile(tempFile.fileName()));
0204 
0205     QMimeDatabase mimeDatabase;
0206     const QMimeType &mime = mimeDatabase.mimeTypeForFile(tempFile.fileName());
0207     mimeType(mime.name());
0208     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Mime: " << mime.name();
0209 
0210     totalSize(tempFile.size());
0211     data(tempFile.readAll());
0212     finished();
0213 }
0214 
0215 bool KioFtp::cancelTransfer(const QString &transfer)
0216 {
0217     return m_kded->cancelTransfer(transfer);
0218 }
0219 
0220 void KioFtp::setHost(const QString &host, quint16 port, const QString &user, const QString &pass)
0221 {
0222     Q_UNUSED(port)
0223     Q_UNUSED(user)
0224     Q_UNUSED(pass)
0225 
0226     m_host = host;
0227     m_host = m_host.replace(QLatin1Char('-'), QLatin1Char(':')).toUpper();
0228 
0229     infoMessage(i18n("Connecting to the device"));
0230 
0231     connectToHost();
0232 }
0233 
0234 void KioFtp::del(const QUrl &url, bool isfile)
0235 {
0236     Q_UNUSED(isfile)
0237 
0238     if (!testConnection()) {
0239         return;
0240     }
0241 
0242     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Del: " << url.url();
0243 
0244     if (!changeFolder(urlDirectory(url))) {
0245         return;
0246     }
0247 
0248     if (!deleteFile(urlFileName(url))) {
0249         return;
0250     }
0251 
0252     finished();
0253 }
0254 
0255 void KioFtp::mkdir(const QUrl &url, int permissions)
0256 {
0257     Q_UNUSED(permissions)
0258 
0259     if (!testConnection()) {
0260         return;
0261     }
0262 
0263     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "MkDir: " << url.url();
0264 
0265     if (!changeFolder(urlDirectory(url))) {
0266         return;
0267     }
0268 
0269     if (!createFolder(urlFileName(url))) {
0270         return;
0271     }
0272 
0273     finished();
0274 }
0275 
0276 void KioFtp::stat(const QUrl &url)
0277 {
0278     if (!testConnection()) {
0279         return;
0280     }
0281 
0282     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat: " << url.url();
0283     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat Dir: " << urlDirectory(url);
0284     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat File: " << urlFileName(url);
0285     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Empty Dir: " << urlDirectory(url).isEmpty();
0286 
0287     statHelper(url);
0288 
0289     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Finished";
0290     finished();
0291 }
0292 
0293 void KioFtp::copyHelper(const QUrl &src, const QUrl &dest)
0294 {
0295     if (src.scheme() == QLatin1String("obexftp") && dest.scheme() == QLatin1String("obexftp")) {
0296         copyWithinObexftp(src, dest);
0297         return;
0298     }
0299 
0300     if (src.scheme() == QLatin1String("obexftp")) {
0301         copyFromObexftp(src, dest);
0302         return;
0303     }
0304 
0305     if (dest.scheme() == QLatin1String("obexftp")) {
0306         copyToObexftp(src, dest);
0307         return;
0308     }
0309 
0310     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "This shouldn't happen...";
0311 }
0312 
0313 void KioFtp::copyWithinObexftp(const QUrl &src, const QUrl &dest)
0314 {
0315     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Source: " << src << "Dest:" << dest;
0316 
0317     if (!changeFolder(urlDirectory(src))) {
0318         return;
0319     }
0320 
0321     BluezQt::PendingCall *call = m_transfer->copyFile(src.path(), dest.path());
0322     call->waitForFinished();
0323 
0324     if (call->error()) {
0325         // Copying files within obexftp is currently not implemented in obexd
0326         if (call->errorText() == QLatin1String("Not Implemented")) {
0327             error(KIO::ERR_UNSUPPORTED_ACTION, src.path());
0328         } else {
0329             error(KIO::ERR_CANNOT_WRITE, src.path());
0330         }
0331         return;
0332     }
0333 
0334     finished();
0335 }
0336 
0337 void KioFtp::copyFromObexftp(const QUrl &src, const QUrl &dest)
0338 {
0339     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Source: " << src << "Dest:" << dest;
0340 
0341     if (!changeFolder(urlDirectory(src))) {
0342         return;
0343     }
0344 
0345     if (!m_statMap.contains(src.toDisplayString())) {
0346         bool ok;
0347         listFolder(urlUpDir(src), &ok);
0348     }
0349 
0350     BluezQt::PendingCall *call = m_transfer->getFile(dest.path(), urlFileName(src));
0351     call->waitForFinished();
0352 
0353     int size = m_statMap.value(src.toDisplayString()).numberValue(KIO::UDSEntry::UDS_SIZE);
0354     totalSize(size);
0355 
0356     BluezQt::ObexTransferPtr transfer = call->value().value<BluezQt::ObexTransferPtr>();
0357     TransferFileJob *getFile = new TransferFileJob(transfer, this);
0358     getFile->exec();
0359 }
0360 
0361 void KioFtp::copyToObexftp(const QUrl &src, const QUrl &dest)
0362 {
0363     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Source:" << src << "Dest:" << dest;
0364 
0365     if (!changeFolder(urlDirectory(dest))) {
0366         return;
0367     }
0368 
0369     BluezQt::PendingCall *call = m_transfer->putFile(src.path(), urlFileName(dest));
0370     call->waitForFinished();
0371 
0372     int size = QFile(src.path()).size();
0373     totalSize(size);
0374 
0375     BluezQt::ObexTransferPtr transfer = call->value().value<BluezQt::ObexTransferPtr>();
0376     TransferFileJob *putFile = new TransferFileJob(transfer, this);
0377     putFile->exec();
0378 }
0379 
0380 void KioFtp::statHelper(const QUrl &url)
0381 {
0382     if (m_statMap.contains(url.toDisplayString())) {
0383         qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "statMap contains the url";
0384         statEntry(m_statMap.value(url.toDisplayString()));
0385         return;
0386     }
0387 
0388     if (urlIsRoot(url)) {
0389         qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Url is root";
0390         KIO::UDSEntry entry;
0391         entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("/"));
0392         entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0393         entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0700);
0394 
0395         qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Adding stat cache" << url.toDisplayString();
0396         m_statMap.insert(url.toDisplayString(), entry);
0397         statEntry(entry);
0398         return;
0399     }
0400 
0401     qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "statMap does not contains the url";
0402 
0403     if (!changeFolder(urlDirectory(url))) {
0404         return;
0405     }
0406 
0407     bool ok;
0408     listFolder(urlUpDir(url), &ok);
0409     if (!ok) {
0410         return;
0411     }
0412 
0413     if (!m_statMap.contains(url.toDisplayString())) {
0414         qCWarning(BLUEDEVIL_KIO_OBEXFTP_LOG) << "statMap still does not contains the url!";
0415     }
0416 
0417     statEntry(m_statMap.value(url.toDisplayString()));
0418 }
0419 
0420 QList<KIO::UDSEntry> KioFtp::listFolder(const QUrl &url, bool *ok)
0421 {
0422     QList<KIO::UDSEntry> list;
0423 
0424     BluezQt::PendingCall *call = m_transfer->listFolder();
0425     call->waitForFinished();
0426 
0427     if (call->error()) {
0428         qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "List folder error" << call->errorText();
0429         error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.path());
0430         *ok = false;
0431         return list;
0432     }
0433 
0434     const QList<BluezQt::ObexFileTransferEntry> &items = call->value().value<QList<BluezQt::ObexFileTransferEntry>>();
0435 
0436     for (const BluezQt::ObexFileTransferEntry &item : items) {
0437         if (!item.isValid()) {
0438             continue;
0439         }
0440 
0441         KIO::UDSEntry entry;
0442         entry.fastInsert(KIO::UDSEntry::UDS_NAME, item.name());
0443         entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, item.label());
0444         entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0700);
0445         entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, item.modificationTime().toSecsSinceEpoch());
0446         entry.fastInsert(KIO::UDSEntry::UDS_SIZE, item.size());
0447 
0448         if (item.type() == BluezQt::ObexFileTransferEntry::Folder) {
0449             entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0450         } else if (item.type() == BluezQt::ObexFileTransferEntry::File) {
0451             entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
0452         }
0453 
0454         if (urlIsRoot(url)) {
0455             updateRootEntryIcon(entry, item.memoryType());
0456         }
0457 
0458         list.append(entry);
0459 
0460         // Most probably the client of the kio will stat each file
0461         // so since we are on it, let's cache all of them.
0462         QUrl statUrl = url;
0463 
0464         if (statUrl.path().endsWith('/')) {
0465             statUrl.setPath(statUrl.path() + item.name());
0466         } else {
0467             statUrl.setPath(statUrl.path() + QLatin1Char('/') + item.name());
0468         }
0469 
0470         if (!m_statMap.contains(statUrl.toDisplayString())) {
0471             qCDebug(BLUEDEVIL_KIO_OBEXFTP_LOG) << "Stat:" << statUrl.toDisplayString() << entry.stringValue(KIO::UDSEntry::UDS_NAME)
0472                                                << entry.numberValue(KIO::UDSEntry::UDS_SIZE);
0473             m_statMap.insert(statUrl.toDisplayString(), entry);
0474         }
0475     }
0476 
0477     *ok = true;
0478     return list;
0479 }
0480 
0481 bool KioFtp::changeFolder(const QString &folder)
0482 {
0483     BluezQt::PendingCall *call = m_transfer->changeFolder(folder);
0484     call->waitForFinished();
0485 
0486     if (call->error()) {
0487         error(KIO::ERR_CANNOT_ENTER_DIRECTORY, folder);
0488         return false;
0489     }
0490     return true;
0491 }
0492 
0493 bool KioFtp::createFolder(const QString &folder)
0494 {
0495     BluezQt::PendingCall *call = m_transfer->createFolder(folder);
0496     call->waitForFinished();
0497 
0498     if (call->error()) {
0499         error(KIO::ERR_CANNOT_MKDIR, folder);
0500         return false;
0501     }
0502     return true;
0503 }
0504 
0505 bool KioFtp::deleteFile(const QString &file)
0506 {
0507     BluezQt::PendingCall *call = m_transfer->deleteFile(file);
0508     call->waitForFinished();
0509 
0510     if (call->error()) {
0511         error(KIO::ERR_CANNOT_DELETE, file);
0512         return false;
0513     }
0514     return true;
0515 }
0516 
0517 void KioFtp::updateRootEntryIcon(KIO::UDSEntry &entry, const QString &memoryType)
0518 {
0519     const QString &path = entry.stringValue(KIO::UDSEntry::UDS_NAME);
0520 
0521     // Nokia (mount-points are C: D: E: ...)
0522     if (path.size() == 2 && path.at(1) == QLatin1Char(':')) {
0523         if (memoryType.startsWith(QLatin1String("DEV"))) {
0524             entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("drive-removable-media"));
0525         } else if (memoryType == QLatin1String("MMC")) {
0526             entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("media-flash-sd-mmc"));
0527         }
0528     }
0529 
0530     // Android
0531     if (entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1String("PHONE_MEMORY")) {
0532         entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Phone memory"));
0533         entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("drive-removable-media"));
0534     } else if (entry.stringValue(KIO::UDSEntry::UDS_NAME) == QLatin1String("EXTERNAL_MEMORY")) {
0535         entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("External memory"));
0536         entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("media-flash-sd-mmc"));
0537     }
0538 }
0539 
0540 #include "kioobexftp.moc"