KIO

kdesktopfileactions.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 1999 Waldo Bastian <[email protected]>
4  SPDX-FileCopyrightText: 1999 David Faure <[email protected]>
5 
6  SPDX-License-Identifier: LGPL-2.0-only
7 */
8 
9 #include "kdesktopfileactions.h"
10 
11 #include "../core/config-kmountpoint.h" // for HAVE_VOLMGT (yes I cheat a bit)
12 #include "kio_widgets_debug.h"
13 
14 #include <KIO/ApplicationLauncherJob>
15 #include "krun.h"
16 #include "kautomount.h"
17 #include <KMessageBox>
18 #include <kdirnotify.h>
19 #include <KDialogJobUiDelegate>
20 #include <kmountpoint.h>
21 
22 #include <KDesktopFile>
23 #include <KConfigGroup>
24 #include <KLocalizedString>
25 #include <KService>
26 
27 #include <QDBusInterface>
28 #include <QDBusReply>
29 
30 enum BuiltinServiceType { ST_MOUNT = 0x0E1B05B0, ST_UNMOUNT = 0x0E1B05B1 }; // random numbers
31 
32 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
33 static bool runFSDevice(const QUrl &_url, const KDesktopFile &cfg, const QByteArray &asn);
34 static bool runLink(const QUrl &_url, const KDesktopFile &cfg, const QByteArray &asn);
35 #endif
36 
37 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
38 bool KDesktopFileActions::run(const QUrl &u, bool _is_local)
39 {
40  return runWithStartup(u, _is_local, QByteArray());
41 }
42 #endif
43 
44 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
45 bool KDesktopFileActions::runWithStartup(const QUrl &u, bool _is_local, const QByteArray &asn)
46 {
47  // It might be a security problem to run external untrusted desktop
48  // entry files
49  if (!_is_local) {
50  return false;
51  }
52 
53  if (u.fileName() == QLatin1String(".directory")) {
54  // We cannot execute a .directory file. Open with a text editor instead.
55  return KRun::runUrl(u, QStringLiteral("text/plain"), nullptr, KRun::RunFlags(), QString(), asn);
56  }
57 
58  KDesktopFile cfg(u.toLocalFile());
59  if (!cfg.desktopGroup().hasKey("Type")) {
60  QString tmp = i18n("The desktop entry file %1 "
61  "has no Type=... entry.", u.toLocalFile());
62  KMessageBox::error(nullptr, tmp);
63  return false;
64  }
65 
66  //qDebug() << "TYPE = " << type.data();
67 
68  if (cfg.hasDeviceType()) {
69  return runFSDevice(u, cfg, asn);
70  } else if (cfg.hasApplicationType()
71  || (cfg.readType() == QLatin1String("Service") && !cfg.desktopGroup().readEntry("Exec").isEmpty())) { // for kio_settings
72  KService service(u.toLocalFile());
73  return KRun::runApplication(service, QList<QUrl>(), nullptr /*TODO - window*/, KRun::RunFlags{}, QString(), asn);
74  } else if (cfg.hasLinkType()) {
75  return runLink(u, cfg, asn);
76  }
77 
78  QString tmp = i18n("The desktop entry of type\n%1\nis unknown.", cfg.readType());
79  KMessageBox::error(nullptr, tmp);
80 
81  return false;
82 }
83 #endif
84 
85 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
86 static bool runFSDevice(const QUrl &_url, const KDesktopFile &cfg, const QByteArray &asn)
87 {
88  bool retval = false;
89 
90  QString dev = cfg.readDevice();
91 
92  if (dev.isEmpty()) {
93  QString tmp = i18n("The desktop entry file\n%1\nis of type FSDevice but has no Dev=... entry.", _url.toLocalFile());
94  KMessageBox::error(nullptr, tmp);
95  return retval;
96  }
97 
99  // Is the device already mounted ?
100  if (mp) {
101  const QUrl mpURL = QUrl::fromLocalFile(mp->mountPoint());
102  // Open a new window
103  retval = KRun::runUrl(mpURL, QStringLiteral("inode/directory"), nullptr /*TODO - window*/, KRun::RunFlags(KRun::RunExecutables), QString(), asn);
104  } else {
105  KConfigGroup cg = cfg.desktopGroup();
106  bool ro = cg.readEntry("ReadOnly", false);
107  QString fstype = cg.readEntry("FSType");
108  if (fstype == QLatin1String("Default")) { // KDE-1 thing
109  fstype.clear();
110  }
111  QString point = cg.readEntry("MountPoint");
112 #ifndef Q_OS_WIN
113  (void) new KAutoMount(ro, fstype.toLatin1(), dev, point, _url.toLocalFile());
114 #endif
115  retval = false;
116  }
117 
118  return retval;
119 }
120 #endif
121 
122 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 71)
123 static bool runLink(const QUrl &_url, const KDesktopFile &cfg, const QByteArray &asn)
124 {
125  QString u = cfg.readUrl();
126  if (u.isEmpty()) {
127  QString tmp = i18n("The desktop entry file\n%1\nis of type Link but has no URL=... entry.", _url.toString());
128  KMessageBox::error(nullptr, tmp);
129  return false;
130  }
131 
132  QUrl url = QUrl::fromUserInput(u);
133  KRun *run = new KRun(url, (QWidget *)nullptr, true, asn);
134 
135  // X-KDE-LastOpenedWith holds the service desktop entry name that
136  // should be preferred for opening this URL if possible.
137  // This is used by the Recent Documents menu for instance.
138  QString lastOpenedWidth = cfg.desktopGroup().readEntry("X-KDE-LastOpenedWith");
139  if (!lastOpenedWidth.isEmpty()) {
140  run->setPreferredService(lastOpenedWidth);
141  }
142 
143  return false;
144 }
145 #endif
146 
148 {
149  QList<KServiceAction> result;
150 
151  if (!_url.isLocalFile()) {
152  return result;
153  }
154 
155  bool offerMount = false;
156  bool offerUnmount = false;
157 
158  KDesktopFile cfg(_url.toLocalFile());
159  if (cfg.hasDeviceType()) { // url to desktop file
160  const QString dev = cfg.readDevice();
161  if (dev.isEmpty()) {
162  QString tmp = i18n("The desktop entry file\n%1\nis of type FSDevice but has no Dev=... entry.", _url.toLocalFile());
163  KMessageBox::error(nullptr, tmp);
164  return result;
165  }
166 
168  if (mp) {
169  offerUnmount = true;
170  } else {
171  offerMount = true;
172  }
173  }
174 
175  if (offerMount) {
176  KServiceAction mount(QStringLiteral("mount"), i18n("Mount"), QString(), QString(), false, {});
177  mount.setData(QVariant(ST_MOUNT));
178  result.append(mount);
179  }
180 
181  if (offerUnmount) {
182  QString text;
183 #ifdef HAVE_VOLMGT
184  /*
185  * Solaris' volume management can only umount+eject
186  */
187  text = i18n("Eject");
188 #else
189  text = i18n("Unmount");
190 #endif
191  KServiceAction unmount(QStringLiteral("unmount"), text, QString(), QString(), false, {});
192  unmount.setData(QVariant(ST_UNMOUNT));
193  result.append(unmount);
194  }
195 
196  return result;
197 }
198 
200 {
201  KDesktopFile cfg(path);
202  return userDefinedServices(path, cfg, bLocalFiles);
203 }
204 
205 QList<KServiceAction> KDesktopFileActions::userDefinedServices(const QString &path, const KDesktopFile &cfg, bool bLocalFiles, const QList<QUrl> &file_list)
206 {
207  Q_UNUSED(path); // this was just for debugging; we use service.entryPath() now.
208  KService service(&cfg);
209  return userDefinedServices(service, bLocalFiles, file_list);
210 }
211 
212 QList<KServiceAction> KDesktopFileActions::userDefinedServices(const KService &service, bool bLocalFiles, const QList<QUrl> &file_list)
213 {
214  QList<KServiceAction> result;
215 
216  if (!service.isValid()) { // e.g. TryExec failed
217  return result;
218  }
219 
220  QStringList keys;
221  const QString actionMenu = service.property(QStringLiteral("X-KDE-GetActionMenu"), QVariant::String).toString();
222  if (!actionMenu.isEmpty()) {
223  const QStringList dbuscall = actionMenu.split(QLatin1Char(' '));
224  if (dbuscall.count() >= 4) {
225  const QString &app = dbuscall.at(0);
226  const QString &object = dbuscall.at(1);
227  const QString &interface = dbuscall.at(2);
228  const QString &function = dbuscall.at(3);
229 
230  QDBusInterface remote(app, object, interface);
231  // Do NOT use QDBus::BlockWithGui here. It runs a nested event loop,
232  // in which timers can fire, leading to crashes like #149736.
233  QDBusReply<QStringList> reply = remote.call(function, QUrl::toStringList(file_list));
234  keys = reply; // ensures that the reply was a QStringList
235  if (keys.isEmpty()) {
236  return result;
237  }
238  } else {
239  qCWarning(KIO_WIDGETS) << "The desktop file" << service.entryPath()
240  << "has an invalid X-KDE-GetActionMenu entry."
241  << "Syntax is: app object interface function";
242  }
243  }
244 
245  // Now, either keys is empty (all actions) or it's set to the actions we want
246 
247  const QList<KServiceAction> list = service.actions();
248  for (const KServiceAction &action : list) {
249  if (keys.isEmpty() || keys.contains(action.name())) {
250  const QString exec = action.exec();
251  if (bLocalFiles || exec.contains(QLatin1String("%U")) || exec.contains(QLatin1String("%u"))) {
252  result.append(action);
253  }
254  }
255  }
256 
257  return result;
258 }
259 
260 // KF6 TODO remove this method and use ApplicationLauncherJob instead
262 {
263  //qDebug() << "EXECUTING Service " << action.name();
264 
265  int actionData = action.data().toInt();
266  if (actionData == ST_MOUNT || actionData == ST_UNMOUNT) {
267  Q_ASSERT(urls.count() == 1);
268  const QString path = urls.first().toLocalFile();
269  //qDebug() << "MOUNT&UNMOUNT";
270 
271  KDesktopFile cfg(path);
272  if (cfg.hasDeviceType()) { // path to desktop file
273  const QString dev = cfg.readDevice();
274  if (dev.isEmpty()) {
275  QString tmp = i18n("The desktop entry file\n%1\nis of type FSDevice but has no Dev=... entry.", path);
276  KMessageBox::error(nullptr /*TODO window*/, tmp);
277  return;
278  }
280 
281  if (actionData == ST_MOUNT) {
282  // Already mounted? Strange, but who knows ...
283  if (mp) {
284  //qDebug() << "ALREADY Mounted";
285  return;
286  }
287 
288  const KConfigGroup group = cfg.desktopGroup();
289  bool ro = group.readEntry("ReadOnly", false);
290  QString fstype = group.readEntry("FSType");
291  if (fstype == QLatin1String("Default")) { // KDE-1 thing
292  fstype.clear();
293  }
294  QString point = group.readEntry("MountPoint");
295 #ifndef Q_OS_WIN
296  (void)new KAutoMount(ro, fstype.toLatin1(), dev, point, path, false);
297 #endif
298  } else if (actionData == ST_UNMOUNT) {
299  // Not mounted? Strange, but who knows ...
300  if (!mp) {
301  return;
302  }
303 
304 #ifndef Q_OS_WIN
305  (void)new KAutoUnmount(mp->mountPoint(), path);
306 #endif
307  }
308  }
309  } else {
311  job->setUrls(urls);
312  QObject::connect(job, &KJob::result, qApp, [urls]() {
313  // The action may update the desktop file. Example: eject unmounts (#5129).
314  org::kde::KDirNotify::emitFilesChanged(urls);
315  });
316  job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr /*TODO window*/));
317  job->start();
318  }
319 }
320 
void setUiDelegate(KJobUiDelegate *delegate)
KIOWIDGETS_EXPORT bool runWithStartup(const QUrl &_url, bool _is_local, const QByteArray &asn)
Invokes the default action for the desktop entry.
static List currentMountPoints(DetailsNeededFlags infoNeeded=BasicInfoNeeded)
This function gives a list of all currently used mountpoints.
KIOWIDGETS_EXPORT QList< KServiceAction > userDefinedServices(const QString &path, bool bLocalFiles)
Returns a list of services defined by the user as possible actions on the given .desktop file...
QStringList toStringList(const QList< QUrl > &urls, QUrl::FormattingOptions options)
const T & at(int i) const const
QVariant property(const QString &_name, QVariant::Type t) const
Ptr findByDevice(const QString &device) const
Returns the mount point associated with device, i.e.
KIOWIDGETS_EXPORT void executeService(const QList< QUrl > &urls, const KServiceAction &service)
Execute service on the list of urls.
bool contains(const QString &str, Qt::CaseSensitivity cs) const const
static bool runUrl(const QUrl &url, const QString &mimetype, QWidget *window, bool tempFile=false, bool runExecutables=true, const QString &suggestedFileName=QString(), const QByteArray &asn=QByteArray())
Open the given URL.
Definition: krun.cpp:163
QUrl fromUserInput(const QString &userInput)
QString toString(QUrl::FormattingOptions options) const const
void start() override
Starts the job.
To open files with their associated applications in KDE, use KRun.
Definition: krun.h:52
bool isValid() const
void clear()
int count(const T &value) const const
void append(const T &value)
QString readDevice() const
QString entryPath() const
int toInt(bool *ok) const const
bool hasDeviceType() const
bool isEmpty() const const
bool isEmpty() const const
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
T & first()
QString readUrl() const
QVariant data() const
bool isEmpty() const const
QString toLocalFile() const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString i18n(const char *text, const TYPE &arg...)
Whether to run URLs that are executable scripts or binaries.
Definition: krun.h:274
void setPreferredService(const QString &desktopEntryName)
Set the preferred service for opening this URL, after its mimetype will have been found by KRun...
Definition: krun.cpp:947
QDBusMessage call(const QString &method, Args &&...args)
QByteArray toLatin1() const const
static qint64 runApplication(const KService &service, const QList< QUrl > &urls, QWidget *window, RunFlags flags=RunFlags(), const QString &suggestedFileName=QString(), const QByteArray &asn=QByteArray())
Run an application (known from its .desktop file, i.e.
Definition: krun.cpp:273
void setUrls(const QList< QUrl > &urls)
Specifies the URLs to be passed to the application.
KIOWIDGETS_EXPORT bool run(const QUrl &_url, bool _is_local)
Invokes the default action for the desktop entry.
KIOWIDGETS_EXPORT QList< KServiceAction > builtinServices(const QUrl &url)
Returns a list of services for the given .desktop file that are handled by kio itself.
QList< KServiceAction > actions() const
void result(KJob *job)
ApplicationLauncherJob runs an application and watches it while running.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString toString() const const
T readEntry(const QString &key, const T &aDefault) const
QString fileName(QUrl::ComponentFormattingOptions options) const const
QUrl fromLocalFile(const QString &localFile)
bool isLocalFile() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Wed Sep 23 2020 23:01:39 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.