File indexing completed on 2024-04-28 15:27:20

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
0004     SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only
0007 */
0008 
0009 #include "kdesktopfileactions.h"
0010 
0011 #include "kio_widgets_debug.h"
0012 
0013 #include "kautomount.h"
0014 #include "krun.h"
0015 #include <KDialogJobUiDelegate>
0016 #include <KIO/ApplicationLauncherJob>
0017 #include <KMessageBox>
0018 #include <kdirnotify.h>
0019 #include <kmountpoint.h>
0020 
0021 #include <KConfigGroup>
0022 #include <KDesktopFile>
0023 #include <KLocalizedString>
0024 #include <KService>
0025 
0026 #ifndef KIO_ANDROID_STUB
0027 #include <QDBusInterface>
0028 #include <QDBusReply>
0029 #endif
0030 
0031 enum BuiltinServiceType { ST_MOUNT = 0x0E1B05B0, ST_UNMOUNT = 0x0E1B05B1 }; // random numbers
0032 
0033 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
0034 static bool runFSDevice(const QUrl &_url, const KDesktopFile &cfg, const QByteArray &asn);
0035 static bool runLink(const QUrl &_url, const KDesktopFile &cfg, const QByteArray &asn);
0036 #endif
0037 
0038 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
0039 bool KDesktopFileActions::run(const QUrl &u, bool _is_local)
0040 {
0041     return runWithStartup(u, _is_local, QByteArray());
0042 }
0043 #endif
0044 
0045 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
0046 bool KDesktopFileActions::runWithStartup(const QUrl &u, bool _is_local, const QByteArray &asn)
0047 {
0048     // It might be a security problem to run external untrusted desktop
0049     // entry files
0050     if (!_is_local) {
0051         return false;
0052     }
0053 
0054     if (u.fileName() == QLatin1String(".directory")) {
0055         // We cannot execute a .directory file. Open with a text editor instead.
0056         return KRun::runUrl(u, QStringLiteral("text/plain"), nullptr, KRun::RunFlags(), QString(), asn);
0057     }
0058 
0059     KDesktopFile cfg(u.toLocalFile());
0060     if (!cfg.desktopGroup().hasKey("Type")) {
0061         KMessageBox::error(nullptr, i18n("The desktop entry file %1 has no Type=... entry.", u.toLocalFile()));
0062         return false;
0063     }
0064 
0065     // qDebug() << "TYPE = " << type.data();
0066 
0067     if (cfg.hasDeviceType()) {
0068         return runFSDevice(u, cfg, asn);
0069     } else if (cfg.hasApplicationType()
0070                || (cfg.readType() == QLatin1String("Service") && !cfg.desktopGroup().readEntry("Exec").isEmpty())) { // for kio_settings
0071         KService service(u.toLocalFile());
0072         return KRun::runApplication(service, QList<QUrl>(), nullptr /*TODO - window*/, KRun::RunFlags{}, QString(), asn);
0073     } else if (cfg.hasLinkType()) {
0074         return runLink(u, cfg, asn);
0075     }
0076 
0077     QString tmp = i18n("The desktop entry of type\n%1\nis unknown.", cfg.readType());
0078     KMessageBox::error(nullptr, tmp);
0079 
0080     return false;
0081 }
0082 #endif
0083 
0084 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
0085 static bool runFSDevice(const QUrl &_url, const KDesktopFile &cfg, const QByteArray &asn)
0086 {
0087     bool retval = false;
0088 
0089     QT_WARNING_PUSH
0090     QT_WARNING_DISABLE_DEPRECATED
0091     QString dev = cfg.readDevice();
0092     QT_WARNING_POP
0093 
0094     if (dev.isEmpty()) {
0095         QString tmp = i18n("The desktop entry file\n%1\nis of type FSDevice but has no Dev=... entry.", _url.toLocalFile());
0096         KMessageBox::error(nullptr, tmp);
0097         return retval;
0098     }
0099 
0100     KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByDevice(dev);
0101     // Is the device already mounted ?
0102     if (mp) {
0103         const QUrl mpURL = QUrl::fromLocalFile(mp->mountPoint());
0104         // Open a new window
0105         retval = KRun::runUrl(mpURL, QStringLiteral("inode/directory"), nullptr /*TODO - window*/, KRun::RunFlags(KRun::RunExecutables), QString(), asn);
0106     } else {
0107         KConfigGroup cg = cfg.desktopGroup();
0108         bool ro = cg.readEntry("ReadOnly", false);
0109         QString fstype = cg.readEntry("FSType");
0110         if (fstype == QLatin1String("Default")) { // KDE-1 thing
0111             fstype.clear();
0112         }
0113         QString point = cg.readEntry("MountPoint");
0114 #if !defined(Q_OS_WIN) && !defined(Q_OS_ANDROID)
0115         (void)new KAutoMount(ro, fstype.toLatin1(), dev, point, _url.toLocalFile());
0116 #endif
0117         retval = false;
0118     }
0119 
0120     return retval;
0121 }
0122 #endif
0123 
0124 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
0125 static bool runLink(const QUrl &_url, const KDesktopFile &cfg, const QByteArray &asn)
0126 {
0127     QString u = cfg.readUrl();
0128     if (u.isEmpty()) {
0129         QString tmp = i18n("The desktop entry file\n%1\nis of type Link but has no URL=... entry.", _url.toString());
0130         KMessageBox::error(nullptr, tmp);
0131         return false;
0132     }
0133 
0134     QUrl url = QUrl::fromUserInput(u);
0135     KRun *run = new KRun(url, (QWidget *)nullptr, true, asn);
0136 
0137     // X-KDE-LastOpenedWith holds the service desktop entry name that
0138     // should be preferred for opening this URL if possible.
0139     // This is used by the Recent Documents menu for instance.
0140     QString lastOpenedWidth = cfg.desktopGroup().readEntry("X-KDE-LastOpenedWith");
0141     if (!lastOpenedWidth.isEmpty()) {
0142         run->setPreferredService(lastOpenedWidth);
0143     }
0144 
0145     return false;
0146 }
0147 #endif
0148 
0149 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 82)
0150 QList<KServiceAction> KDesktopFileActions::builtinServices(const QUrl &_url)
0151 {
0152     QList<KServiceAction> result;
0153 
0154     if (!_url.isLocalFile()) {
0155         return result;
0156     }
0157 
0158     bool offerMount = false;
0159     bool offerUnmount = false;
0160 
0161     KDesktopFile cfg(_url.toLocalFile());
0162     if (cfg.hasDeviceType()) { // url to desktop file
0163 
0164         QT_WARNING_PUSH
0165         QT_WARNING_DISABLE_DEPRECATED
0166         const QString dev = cfg.readDevice();
0167         QT_WARNING_POP
0168 
0169         if (dev.isEmpty()) {
0170             QString tmp = i18n("The desktop entry file\n%1\nis of type FSDevice but has no Dev=... entry.", _url.toLocalFile());
0171             KMessageBox::error(nullptr, tmp);
0172             return result;
0173         }
0174 
0175         KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByDevice(dev);
0176         if (mp) {
0177             offerUnmount = true;
0178         } else {
0179             offerMount = true;
0180         }
0181     }
0182 
0183     if (offerMount) {
0184         KServiceAction mount(QStringLiteral("mount"), i18n("Mount"), QString(), QString(), false, {});
0185         mount.setData(QVariant(ST_MOUNT));
0186         result.append(mount);
0187     }
0188 
0189     if (offerUnmount) {
0190         KServiceAction unmount(QStringLiteral("unmount"), i18n("Unmount"), QString(), QString(), false, {});
0191         unmount.setData(QVariant(ST_UNMOUNT));
0192         result.append(unmount);
0193     }
0194 
0195     return result;
0196 }
0197 #endif
0198 
0199 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 86)
0200 QList<KServiceAction> KDesktopFileActions::userDefinedServices(const QString &path, bool bLocalFiles)
0201 {
0202     KService service(path);
0203     return userDefinedServices(service, bLocalFiles);
0204 }
0205 #endif
0206 
0207 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 86)
0208 QList<KServiceAction> KDesktopFileActions::userDefinedServices(const QString &path, const KDesktopFile &cfg, bool bLocalFiles, const QList<QUrl> &file_list)
0209 {
0210     Q_UNUSED(path); // this was just for debugging; we use service.entryPath() now.
0211     KService service(&cfg);
0212     return userDefinedServices(service, bLocalFiles, file_list);
0213 }
0214 #endif
0215 
0216 QList<KServiceAction> KDesktopFileActions::userDefinedServices(const KService &service, bool bLocalFiles, const QList<QUrl> &file_list)
0217 {
0218     QList<KServiceAction> result;
0219 
0220     if (!service.isValid()) { // e.g. TryExec failed
0221         return result;
0222     }
0223 
0224     QStringList keys;
0225     const QString actionMenu = service.property(QStringLiteral("X-KDE-GetActionMenu"), QMetaType::QString).toString();
0226     if (!actionMenu.isEmpty()) {
0227         const QStringList dbuscall = actionMenu.split(QLatin1Char(' '));
0228         if (dbuscall.count() >= 4) {
0229             const QString &app = dbuscall.at(0);
0230             const QString &object = dbuscall.at(1);
0231             const QString &interface = dbuscall.at(2);
0232             const QString &function = dbuscall.at(3);
0233 
0234 #ifndef KIO_ANDROID_STUB
0235             QDBusInterface remote(app, object, interface);
0236             // Do NOT use QDBus::BlockWithGui here. It runs a nested event loop,
0237             // in which timers can fire, leading to crashes like #149736.
0238             QDBusReply<QStringList> reply = remote.call(function, QUrl::toStringList(file_list));
0239             keys = reply; // ensures that the reply was a QStringList
0240             if (keys.isEmpty()) {
0241                 return result;
0242             }
0243 #endif
0244         } else {
0245             qCWarning(KIO_WIDGETS) << "The desktop file" << service.entryPath() << "has an invalid X-KDE-GetActionMenu entry."
0246                                    << "Syntax is: app object interface function";
0247         }
0248     }
0249 
0250     // Now, either keys is empty (all actions) or it's set to the actions we want
0251 
0252     const QList<KServiceAction> list = service.actions();
0253     for (const KServiceAction &action : list) {
0254         if (keys.isEmpty() || keys.contains(action.name())) {
0255             const QString exec = action.exec();
0256             if (bLocalFiles || exec.contains(QLatin1String("%U")) || exec.contains(QLatin1String("%u"))) {
0257                 result.append(action);
0258             }
0259         }
0260     }
0261 
0262     return result;
0263 }
0264 
0265 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 84)
0266 void KDesktopFileActions::executeService(const QList<QUrl> &urls, const KServiceAction &action)
0267 {
0268     // qDebug() << "EXECUTING Service " << action.name();
0269 
0270     int actionData = action.data().toInt();
0271     if (actionData == ST_MOUNT || actionData == ST_UNMOUNT) {
0272 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 82)
0273         Q_ASSERT(urls.count() == 1);
0274         const QString path = urls.first().toLocalFile();
0275         // qDebug() << "MOUNT&UNMOUNT";
0276 
0277         KDesktopFile cfg(path);
0278         if (cfg.hasDeviceType()) { // path to desktop file
0279             QT_WARNING_PUSH
0280             QT_WARNING_DISABLE_DEPRECATED
0281             const QString dev = cfg.readDevice();
0282             QT_WARNING_POP
0283 
0284             if (dev.isEmpty()) {
0285                 QString tmp = i18n("The desktop entry file\n%1\nis of type FSDevice but has no Dev=... entry.", path);
0286                 KMessageBox::error(nullptr /*TODO window*/, tmp);
0287                 return;
0288             }
0289             KMountPoint::Ptr mp = KMountPoint::currentMountPoints().findByDevice(dev);
0290 
0291             if (actionData == ST_MOUNT) {
0292                 // Already mounted? Strange, but who knows ...
0293                 if (mp) {
0294                     // qDebug() << "ALREADY Mounted";
0295                     return;
0296                 }
0297 
0298                 const KConfigGroup group = cfg.desktopGroup();
0299                 bool ro = group.readEntry("ReadOnly", false);
0300                 QString fstype = group.readEntry("FSType");
0301                 if (fstype == QLatin1String("Default")) { // KDE-1 thing
0302                     fstype.clear();
0303                 }
0304                 QString point = group.readEntry("MountPoint");
0305 #if !defined(Q_OS_WIN) && !defined(Q_OS_ANDROID)
0306                 (void)new KAutoMount(ro, fstype.toLatin1(), dev, point, path, false);
0307 #endif
0308             } else if (actionData == ST_UNMOUNT) {
0309                 // Not mounted? Strange, but who knows ...
0310                 if (!mp) {
0311                     return;
0312                 }
0313 
0314 #if !defined(Q_OS_WIN) && !defined(Q_OS_ANDROID)
0315                 (void)new KAutoUnmount(mp->mountPoint(), path);
0316 #endif
0317             }
0318         }
0319 #endif // deprecated since 5.82
0320     } else {
0321         KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(action);
0322         job->setUrls(urls);
0323         QObject::connect(job, &KJob::result, qApp, [urls]() {
0324         // The action may update the desktop file. Example: eject unmounts (#5129).
0325 #ifndef KIO_ANDROID_STUB
0326             org::kde::KDirNotify::emitFilesChanged(urls);
0327 #endif
0328         });
0329         job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr /*TODO window*/));
0330         job->start();
0331     }
0332 }
0333 #endif