• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.11.5 API Reference
  • KDE Home
  • Contact Us
 

KIO

  • kio
  • kio
krun.cpp
Go to the documentation of this file.
1 /* This file is part of the KDE libraries
2  Copyright (C) 2000 Torben Weis <weis@kde.org>
3  Copyright (C) 2006 David Faure <faure@kde.org>
4  Copyright (C) 2009 Michael Pyne <michael.pyne@kdemail.net>
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Library General Public
8  License as published by the Free Software Foundation; either
9  version 2 of the License, or (at your option) any later version.
10 
11  This library is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  Library General Public License for more details.
15 
16  You should have received a copy of the GNU Library General Public License
17  along with this library; see the file COPYING.LIB. If not, write to
18  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  Boston, MA 02110-1301, USA.
20 */
21 
22 #include "krun.h"
23 #include "krun_p.h"
24 
25 #include <config.h>
26 
27 #include <assert.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <typeinfo>
32 #include <sys/stat.h>
33 
34 #include <QtGui/QWidget>
35 #include <QtGui/QLabel>
36 #include <QtGui/QVBoxLayout>
37 #include <QtGui/QHBoxLayout>
38 #include <QtGui/QPlainTextEdit>
39 #include <QtGui/QApplication>
40 #include <QtGui/QDesktopWidget>
41 
42 #include <kmimetypetrader.h>
43 #include <kmimetype.h>
44 #include "kio/jobclasses.h" // for KIO::JobFlags
45 #include "kio/job.h"
46 #include "kio/jobuidelegate.h"
47 #include "kio/global.h"
48 #include "kio/scheduler.h"
49 #include "kio/netaccess.h"
50 #include "kfile/kopenwithdialog.h"
51 #include "kfile/krecentdocument.h"
52 #include "kdesktopfileactions.h"
53 
54 #include <kauthorized.h>
55 #include <kmessageboxwrapper.h>
56 #include <kurl.h>
57 #include <kglobal.h>
58 #include <ktoolinvocation.h>
59 #include <kdebug.h>
60 #include <klocale.h>
61 #include <kprotocolmanager.h>
62 #include <kstandarddirs.h>
63 #include <kprocess.h>
64 #include <QtCore/QFile>
65 #include <QtCore/QFileInfo>
66 #include <QtCore/QTextIStream>
67 #include <QtCore/QDate>
68 #include <QtCore/QRegExp>
69 #include <QDir>
70 #include <kdesktopfile.h>
71 #include <kmacroexpander.h>
72 #include <kshell.h>
73 #include <QTextDocument>
74 #include <kde_file.h>
75 #include <kconfiggroup.h>
76 #include <kdialog.h>
77 #include <kstandardguiitem.h>
78 #include <kguiitem.h>
79 #include <ksavefile.h>
80 
81 #ifdef Q_WS_X11
82 #include <kwindowsystem.h>
83 #elif defined(Q_WS_WIN)
84 #include <QDesktopServices>
85 #endif
86 
87 KRun::KRunPrivate::KRunPrivate(KRun *parent)
88  : q(parent),
89  m_showingDialog(false)
90 {
91 }
92 
93 void KRun::KRunPrivate::startTimer()
94 {
95  m_timer.start(0);
96 }
97 
98 // ---------------------------------------------------------------------------
99 
100 bool KRun::isExecutableFile(const KUrl& url, const QString &mimetype)
101 {
102  if (!url.isLocalFile()) {
103  return false;
104  }
105  QFileInfo file(url.toLocalFile());
106  if (file.isExecutable()) { // Got a prospective file to run
107  KMimeType::Ptr mimeType = KMimeType::mimeType(mimetype, KMimeType::ResolveAliases);
108  if (mimeType && (mimeType->is(QLatin1String("application/x-executable")) ||
109 #ifdef Q_WS_WIN
110  mimeType->is(QLatin1String("application/x-ms-dos-executable")) ||
111 #endif
112  mimeType->is(QLatin1String("application/x-executable-script")))
113  )
114  {
115  return true;
116  }
117  }
118  return false;
119 }
120 
121 // This is called by foundMimeType, since it knows the mimetype of the URL
122 bool KRun::runUrl(const KUrl& u, const QString& _mimetype, QWidget* window, bool tempFile, bool runExecutables, const QString& suggestedFileName, const QByteArray& asn)
123 {
124  bool noRun = false;
125  bool noAuth = false;
126  if (_mimetype == QLatin1String("inode/directory-locked")) {
127  KMessageBoxWrapper::error(window,
128  i18n("<qt>Unable to enter <b>%1</b>.\nYou do not have access rights to this location.</qt>", Qt::escape(u.prettyUrl())));
129  return false;
130  }
131  else if (_mimetype == QLatin1String("application/x-desktop")) {
132  if (u.isLocalFile() && runExecutables) {
133  return KDesktopFileActions::run(u, true);
134  }
135  }
136  else if (isExecutableFile(u, _mimetype)) {
137  if (u.isLocalFile() && runExecutables) {
138  if (KAuthorized::authorize("shell_access")) {
139  return (KRun::runCommand(KShell::quoteArg(u.toLocalFile()), QString(), QString(), window, asn, u.directory())); // just execute the url as a command
140  // ## TODO implement deleting the file if tempFile==true
141  }
142  else {
143  noAuth = true;
144  }
145  }
146  else if (_mimetype == QLatin1String("application/x-executable")) {
147  noRun = true;
148  }
149  }
150  else if (isExecutable(_mimetype)) {
151  if (!runExecutables) {
152  noRun = true;
153  }
154 
155  if (!KAuthorized::authorize("shell_access")) {
156  noAuth = true;
157  }
158  }
159 
160  if (noRun) {
161  KMessageBox::sorry(window,
162  i18n("<qt>The file <b>%1</b> is an executable program. "
163  "For safety it will not be started.</qt>", Qt::escape(u.prettyUrl())));
164  return false;
165  }
166  if (noAuth) {
167  KMessageBoxWrapper::error(window,
168  i18n("<qt>You do not have permission to run <b>%1</b>.</qt>", Qt::escape(u.prettyUrl())));
169  return false;
170  }
171 
172  KUrl::List lst;
173  lst.append(u);
174 
175  KService::Ptr offer = KMimeTypeTrader::self()->preferredService(_mimetype);
176 
177  if (!offer) {
178 #ifdef Q_WS_WIN
179  // As KDE on windows doesnt know about the windows default applications offers will be empty in nearly all cases.
180  // So we use QDesktopServices::openUrl to let windows decide how to open the file
181  return QDesktopServices::openUrl(u);
182 #else
183  // Open-with dialog
184  // TODO : pass the mimetype as a parameter, to show it (comment field) in the dialog !
185  // Hmm, in fact KOpenWithDialog::setServiceType already guesses the mimetype from the first URL of the list...
186  return displayOpenWithDialog(lst, window, tempFile, suggestedFileName, asn);
187 #endif
188  }
189 
190  return KRun::run(*offer, lst, window, tempFile, suggestedFileName, asn);
191 }
192 
193 bool KRun::displayOpenWithDialog(const KUrl::List& lst, QWidget* window, bool tempFiles,
194  const QString& suggestedFileName, const QByteArray& asn)
195 {
196  if (!KAuthorized::authorizeKAction("openwith")) {
197  KMessageBox::sorry(window,
198  i18n("You are not authorized to select an application to open this file."));
199  return false;
200  }
201 
202 #ifdef Q_WS_WIN
203  KConfigGroup cfgGroup(KGlobal::config(), "KOpenWithDialog Settings");
204  if (cfgGroup.readEntry("Native", true)) {
205  return KRun::KRunPrivate::displayNativeOpenWithDialog(lst, window, tempFiles,
206  suggestedFileName, asn);
207  }
208 #endif
209  KOpenWithDialog l(lst, i18n("Open with:"), QString(), window);
210  if (l.exec()) {
211  KService::Ptr service = l.service();
212  if (!service) {
213  kDebug(7010) << "No service set, running " << l.text();
214  service = KService::Ptr(new KService(QString() /*name*/, l.text(), QString() /*icon*/));
215  }
216  return KRun::run(*service, lst, window, tempFiles, suggestedFileName, asn);
217  }
218  return false;
219 }
220 
221 #ifndef KDE_NO_DEPRECATED
222 void KRun::shellQuote(QString &_str)
223 {
224  // Credits to Walter, says Bernd G. :)
225  if (_str.isEmpty()) { // Don't create an explicit empty parameter
226  return;
227  }
228  QChar q('\'');
229  _str.replace(q, "'\\''").prepend(q).append(q);
230 }
231 #endif
232 
233 
234 class KRunMX1 : public KMacroExpanderBase
235 {
236 public:
237  KRunMX1(const KService &_service) :
238  KMacroExpanderBase('%'), hasUrls(false), hasSpec(false), service(_service) {}
239 
240  bool hasUrls: 1, hasSpec: 1;
241 
242 protected:
243  virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret);
244 
245 private:
246  const KService &service;
247 };
248 
249 int
250 KRunMX1::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
251 {
252  uint option = str[pos + 1].unicode();
253  switch (option) {
254  case 'c':
255  ret << service.name().replace('%', "%%");
256  break;
257  case 'k':
258  ret << service.entryPath().replace('%', "%%");
259  break;
260  case 'i':
261  ret << "--icon" << service.icon().replace('%', "%%");
262  break;
263  case 'm':
264 // ret << "-miniicon" << service.icon().replace( '%', "%%" );
265  kWarning() << "-miniicon isn't supported anymore (service"
266  << service.name() << ')';
267  break;
268  case 'u':
269  case 'U':
270  hasUrls = true;
271  /* fallthrough */
272  case 'f':
273  case 'F':
274  case 'n':
275  case 'N':
276  case 'd':
277  case 'D':
278  case 'v':
279  hasSpec = true;
280  /* fallthrough */
281  default:
282  return -2; // subst with same and skip
283  }
284  return 2;
285 }
286 
287 class KRunMX2 : public KMacroExpanderBase
288 {
289 public:
290  KRunMX2(const KUrl::List &_urls) :
291  KMacroExpanderBase('%'), ignFile(false), urls(_urls) {}
292 
293  bool ignFile: 1;
294 
295 protected:
296  virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret);
297 
298 private:
299  void subst(int option, const KUrl &url, QStringList &ret);
300 
301  const KUrl::List &urls;
302 };
303 
304 void
305 KRunMX2::subst(int option, const KUrl &url, QStringList &ret)
306 {
307  switch (option) {
308  case 'u':
309  ret << ((url.isLocalFile() && url.fragment().isNull() && url.encodedQuery().isNull()) ?
310  QDir::toNativeSeparators(url.toLocalFile()) : url.url());
311  break;
312  case 'd':
313  ret << url.directory();
314  break;
315  case 'f':
316  ret << QDir::toNativeSeparators(url.toLocalFile());
317  break;
318  case 'n':
319  ret << url.fileName();
320  break;
321  case 'v':
322  if (url.isLocalFile() && QFile::exists(url.toLocalFile())) {
323  ret << KDesktopFile(url.toLocalFile()).desktopGroup().readEntry("Dev");
324  }
325  break;
326  }
327  return;
328 }
329 
330 int
331 KRunMX2::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
332 {
333  uint option = str[pos + 1].unicode();
334  switch (option) {
335  case 'f':
336  case 'u':
337  case 'n':
338  case 'd':
339  case 'v':
340  if (urls.isEmpty()) {
341  if (!ignFile) {
342  kDebug() << "No URLs supplied to single-URL service" << str;
343  }
344  }
345  else if (urls.count() > 1) {
346  kWarning() << urls.count() << "URLs supplied to single-URL service" << str;
347  }
348  else {
349  subst(option, urls.first(), ret);
350  }
351  break;
352  case 'F':
353  case 'U':
354  case 'N':
355  case 'D':
356  option += 'a' - 'A';
357  for (KUrl::List::ConstIterator it = urls.begin(); it != urls.end(); ++it)
358  subst(option, *it, ret);
359  break;
360  case '%':
361  ret = QStringList(QLatin1String("%"));
362  break;
363  default:
364  return -2; // subst with same and skip
365  }
366  return 2;
367 }
368 
369 static QStringList supportedProtocols(const KService& _service)
370 {
371  // Check which protocols the application supports.
372  // This can be a list of actual protocol names, or just KIO for KDE apps.
373  QStringList supportedProtocols = _service.property("X-KDE-Protocols").toStringList();
374  KRunMX1 mx1(_service);
375  QString exec = _service.exec();
376  if (mx1.expandMacrosShellQuote(exec) && !mx1.hasUrls) {
377  Q_ASSERT(supportedProtocols.isEmpty()); // huh? If you support protocols you need %u or %U...
378  }
379  else {
380  if (supportedProtocols.isEmpty()) {
381  // compat mode: assume KIO if not set and it's a KDE app (or a KDE service)
382  const QStringList categories = _service.property("Categories").toStringList();
383  if (categories.contains("KDE")
384  || !_service.isApplication()
385  || _service.entryPath().isEmpty() /*temp service*/) {
386  supportedProtocols.append("KIO");
387  }
388  else { // if no KDE app, be a bit over-generic
389  supportedProtocols.append("http");
390  supportedProtocols.append("https"); // #253294
391  supportedProtocols.append("ftp");
392  }
393  }
394  }
395  kDebug(7010) << "supportedProtocols:" << supportedProtocols;
396  return supportedProtocols;
397 }
398 
399 static bool isProtocolInSupportedList(const KUrl& url, const QStringList& supportedProtocols)
400 {
401  if (supportedProtocols.contains("KIO"))
402  return true;
403  return url.isLocalFile() || supportedProtocols.contains(url.protocol().toLower());
404 }
405 
406 QStringList KRun::processDesktopExec(const KService &_service, const KUrl::List& _urls, bool tempFiles, const QString& suggestedFileName)
407 {
408  QString exec = _service.exec();
409  if (exec.isEmpty()) {
410  kWarning() << "KRun: no Exec field in `" << _service.entryPath() << "' !";
411  return QStringList();
412  }
413 
414  QStringList result;
415  bool appHasTempFileOption;
416 
417  KRunMX1 mx1(_service);
418  KRunMX2 mx2(_urls);
419 
420  if (!mx1.expandMacrosShellQuote(exec)) { // Error in shell syntax
421  kWarning() << "KRun: syntax error in command" << _service.exec() << ", service" << _service.name();
422  return QStringList();
423  }
424 
425  // FIXME: the current way of invoking kioexec disables term and su use
426 
427  // Check if we need "tempexec" (kioexec in fact)
428  appHasTempFileOption = tempFiles && _service.property("X-KDE-HasTempFileOption").toBool();
429  if (tempFiles && !appHasTempFileOption && _urls.size()) {
430  const QString kioexec = KStandardDirs::findExe("kioexec");
431  Q_ASSERT(!kioexec.isEmpty());
432  result << kioexec << "--tempfiles" << exec;
433  if (!suggestedFileName.isEmpty()) {
434  result << "--suggestedfilename";
435  result << suggestedFileName;
436  }
437  result += _urls.toStringList();
438  return result;
439  }
440 
441  // Check if we need kioexec
442  bool useKioexec = false;
443  if (!mx1.hasUrls) {
444  for (KUrl::List::ConstIterator it = _urls.begin(); it != _urls.end(); ++it)
445  if (!(*it).isLocalFile() && !KProtocolInfo::isHelperProtocol(*it)) {
446  useKioexec = true;
447  kDebug(7010) << "non-local files, application does not support urls, using kioexec";
448  break;
449  }
450  } else { // app claims to support %u/%U, check which protocols
451  QStringList appSupportedProtocols = supportedProtocols(_service);
452  for (KUrl::List::ConstIterator it = _urls.begin(); it != _urls.end(); ++it)
453  if (!isProtocolInSupportedList(*it, appSupportedProtocols) && !KProtocolInfo::isHelperProtocol(*it)) {
454  useKioexec = true;
455  kDebug(7010) << "application does not support url, using kioexec:" << *it;
456  break;
457  }
458  }
459  if (useKioexec) {
460  // We need to run the app through kioexec
461  const QString kioexec = KStandardDirs::findExe("kioexec");
462  Q_ASSERT(!kioexec.isEmpty());
463  result << kioexec;
464  if (tempFiles) {
465  result << "--tempfiles";
466  }
467  if (!suggestedFileName.isEmpty()) {
468  result << "--suggestedfilename";
469  result << suggestedFileName;
470  }
471  result << exec;
472  result += _urls.toStringList();
473  return result;
474  }
475 
476  if (appHasTempFileOption) {
477  exec += " --tempfile";
478  }
479 
480  // Did the user forget to append something like '%f'?
481  // If so, then assume that '%f' is the right choice => the application
482  // accepts only local files.
483  if (!mx1.hasSpec) {
484  exec += " %f";
485  mx2.ignFile = true;
486  }
487 
488  mx2.expandMacrosShellQuote(exec); // syntax was already checked, so don't check return value
489 
490  /*
491  1 = need_shell, 2 = terminal, 4 = su
492 
493  0 << split(cmd)
494  1 << "sh" << "-c" << cmd
495  2 << split(term) << "-e" << split(cmd)
496  3 << split(term) << "-e" << "sh" << "-c" << cmd
497 
498  4 << "kdesu" << "-u" << user << "-c" << cmd
499  5 << "kdesu" << "-u" << user << "-c" << ("sh -c " + quote(cmd))
500  6 << split(term) << "-e" << "su" << user << "-c" << cmd
501  7 << split(term) << "-e" << "su" << user << "-c" << ("sh -c " + quote(cmd))
502 
503  "sh -c" is needed in the "su" case, too, as su uses the user's login shell, not sh.
504  this could be optimized with the -s switch of some su versions (e.g., debian linux).
505  */
506 
507  if (_service.terminal()) {
508  KConfigGroup cg(KGlobal::config(), "General");
509  QString terminal = cg.readPathEntry("TerminalApplication", "konsole");
510  if (terminal == "konsole") {
511  if (!_service.path().isEmpty()) {
512  terminal += " --workdir " + KShell::quoteArg(_service.path());
513  }
514  terminal += " -caption=%c %i %m";
515  }
516  terminal += ' ';
517  terminal += _service.terminalOptions();
518  if (!mx1.expandMacrosShellQuote(terminal)) {
519  kWarning() << "KRun: syntax error in command" << terminal << ", service" << _service.name();
520  return QStringList();
521  }
522  mx2.expandMacrosShellQuote(terminal);
523  result = KShell::splitArgs(terminal); // assuming that the term spec never needs a shell!
524  result << "-e";
525  }
526 
527  KShell::Errors err;
528  QStringList execlist = KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
529  if (err == KShell::NoError && !execlist.isEmpty()) { // mx1 checked for syntax errors already
530  // Resolve the executable to ensure that helpers in lib/kde4/libexec/ are found.
531  // Too bad for commands that need a shell - they must reside in $PATH.
532  const QString exePath = KStandardDirs::findExe(execlist[0]);
533  if (!exePath.isEmpty()) {
534  execlist[0] = exePath;
535  }
536  }
537  if (_service.substituteUid()) {
538  if (_service.terminal()) {
539  result << "su";
540  }
541  else {
542  result << KStandardDirs::findExe("kdesu") << "-u";
543  }
544 
545  result << _service.username() << "-c";
546  if (err == KShell::FoundMeta) {
547  exec = "/bin/sh -c " + KShell::quoteArg(exec);
548  }
549  else {
550  exec = KShell::joinArgs(execlist);
551  }
552  result << exec;
553  }
554  else {
555  if (err == KShell::FoundMeta) {
556  result << "/bin/sh" << "-c" << exec;
557  }
558  else {
559  result += execlist;
560  }
561  }
562 
563  return result;
564 }
565 
566 //static
567 QString KRun::binaryName(const QString & execLine, bool removePath)
568 {
569  // Remove parameters and/or trailing spaces.
570  const QStringList args = KShell::splitArgs(execLine);
571  for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it)
572  if (!(*it).contains('=')) {
573  // Remove path if wanted
574  return removePath ? (*it).mid((*it).lastIndexOf('/') + 1) : *it;
575  }
576  return QString();
577 }
578 
579 static bool runCommandInternal(KProcess* proc, const KService* service, const QString& executable,
580  const QString &userVisibleName, const QString & iconName, QWidget* window,
581  const QByteArray& asn)
582 {
583  if (window != NULL) {
584  window = window->topLevelWidget();
585  }
586  if (service && !service->entryPath().isEmpty()
587  && !KDesktopFile::isAuthorizedDesktopFile(service->entryPath()))
588  {
589  kWarning() << "No authorization to execute " << service->entryPath();
590  KMessageBox::sorry(window, i18n("You are not authorized to execute this file."));
591  delete proc;
592  return false;
593  }
594 
595  QString bin = KRun::binaryName(executable, true);
596 #ifdef Q_WS_X11 // Startup notification doesn't work with QT/E, service isn't needed without Startup notification
597  bool silent;
598  QByteArray wmclass;
599  KStartupInfoId id;
600  bool startup_notify = (asn != "0" && KRun::checkStartupNotify(QString() /*unused*/, service, &silent, &wmclass));
601  if (startup_notify) {
602  id.initId(asn);
603  id.setupStartupEnv();
604  KStartupInfoData data;
605  data.setHostname();
606  data.setBin(bin);
607  if (!userVisibleName.isEmpty()) {
608  data.setName(userVisibleName);
609  }
610  else if (service && !service->name().isEmpty()) {
611  data.setName(service->name());
612  }
613  data.setDescription(i18n("Launching %1" , data.name()));
614  if (!iconName.isEmpty()) {
615  data.setIcon(iconName);
616  }
617  else if (service && !service->icon().isEmpty()) {
618  data.setIcon(service->icon());
619  }
620  if (!wmclass.isEmpty()) {
621  data.setWMClass(wmclass);
622  }
623  if (silent) {
624  data.setSilent(KStartupInfoData::Yes);
625  }
626  data.setDesktop(KWindowSystem::currentDesktop());
627  if (window) {
628  data.setLaunchedBy(window->winId());
629  }
630  if(service && !service->entryPath().isEmpty())
631  data.setApplicationId(service->entryPath());
632  KStartupInfo::sendStartup(id, data);
633  }
634  int pid = KProcessRunner::run(proc, executable, id);
635  if (startup_notify && pid) {
636  KStartupInfoData data;
637  data.addPid(pid);
638  KStartupInfo::sendChange(id, data);
639  KStartupInfo::resetStartupEnv();
640  }
641  return pid != 0;
642 #else
643  Q_UNUSED(userVisibleName);
644  Q_UNUSED(iconName);
645  return KProcessRunner::run(proc, bin) != 0;
646 #endif
647 }
648 
649 // This code is also used in klauncher.
650 bool KRun::checkStartupNotify(const QString& /*binName*/, const KService* service, bool* silent_arg, QByteArray* wmclass_arg)
651 {
652  bool silent = false;
653  QByteArray wmclass;
654  if (service && service->property("StartupNotify").isValid()) {
655  silent = !service->property("StartupNotify").toBool();
656  wmclass = service->property("StartupWMClass").toString().toLatin1();
657  }
658  else if (service && service->property("X-KDE-StartupNotify").isValid()) {
659  silent = !service->property("X-KDE-StartupNotify").toBool();
660  wmclass = service->property("X-KDE-WMClass").toString().toLatin1();
661  }
662  else { // non-compliant app
663  if (service) {
664  if (service->isApplication()) { // doesn't have .desktop entries needed, start as non-compliant
665  wmclass = "0"; // krazy:exclude=doublequote_chars
666  }
667  else {
668  return false; // no startup notification at all
669  }
670  }
671  else {
672 #if 0
673  // Create startup notification even for apps for which there shouldn't be any,
674  // just without any visual feedback. This will ensure they'll be positioned on the proper
675  // virtual desktop, and will get user timestamp from the ASN ID.
676  wmclass = '0';
677  silent = true;
678 #else // That unfortunately doesn't work, when the launched non-compliant application
679  // launches another one that is compliant and there is any delay inbetween (bnc:#343359)
680  return false;
681 #endif
682  }
683  }
684  if (silent_arg != NULL) {
685  *silent_arg = silent;
686  }
687  if (wmclass_arg != NULL) {
688  *wmclass_arg = wmclass;
689  }
690  return true;
691 }
692 
693 static bool runTempService(const KService& _service, const KUrl::List& _urls, QWidget* window,
694  bool tempFiles, const QString& suggestedFileName, const QByteArray& asn)
695 {
696  if (!_urls.isEmpty()) {
697  kDebug(7010) << "runTempService: first url " << _urls.first().url();
698  }
699 
700  QStringList args;
701  if ((_urls.count() > 1) && !_service.allowMultipleFiles()) {
702  // We need to launch the application N times. That sucks.
703  // We ignore the result for application 2 to N.
704  // For the first file we launch the application in the
705  // usual way. The reported result is based on this
706  // application.
707  KUrl::List::ConstIterator it = _urls.begin();
708  while (++it != _urls.end()) {
709  KUrl::List singleUrl;
710  singleUrl.append(*it);
711  runTempService(_service, singleUrl, window, tempFiles, suggestedFileName, QByteArray());
712  }
713  KUrl::List singleUrl;
714  singleUrl.append(_urls.first());
715  args = KRun::processDesktopExec(_service, singleUrl, tempFiles, suggestedFileName);
716  }
717  else {
718  args = KRun::processDesktopExec(_service, _urls, tempFiles, suggestedFileName);
719  }
720  if (args.isEmpty()) {
721  KMessageBox::sorry(window, i18n("Error processing Exec field in %1", _service.entryPath()));
722  return false;
723  }
724  kDebug(7010) << "runTempService: KProcess args=" << args;
725 
726  KProcess * proc = new KProcess;
727  *proc << args;
728 
729  if (!_service.path().isEmpty()) {
730  proc->setWorkingDirectory(_service.path());
731  }
732 
733  return runCommandInternal(proc, &_service, KRun::binaryName(_service.exec(), false),
734  _service.name(), _service.icon(), window, asn);
735 }
736 
737 // WARNING: don't call this from processDesktopExec, since klauncher uses that too...
738 static KUrl::List resolveURLs(const KUrl::List& _urls, const KService& _service)
739 {
740  // Check which protocols the application supports.
741  // This can be a list of actual protocol names, or just KIO for KDE apps.
742  QStringList appSupportedProtocols = supportedProtocols(_service);
743  KUrl::List urls(_urls);
744  if (!appSupportedProtocols.contains("KIO")) {
745  for (KUrl::List::Iterator it = urls.begin(); it != urls.end(); ++it) {
746  const KUrl url = *it;
747  bool supported = isProtocolInSupportedList(url, appSupportedProtocols);
748  kDebug(7010) << "Looking at url=" << url << " supported=" << supported;
749  if (!supported && KProtocolInfo::protocolClass(url.protocol()) == ":local") {
750  // Maybe we can resolve to a local URL?
751  KUrl localURL = KIO::NetAccess::mostLocalUrl(url, 0);
752  if (localURL != url) {
753  *it = localURL;
754  kDebug(7010) << "Changed to " << localURL;
755  }
756  }
757  }
758  }
759  return urls;
760 }
761 
762 // Simple KDialog that resizes the given text edit after being shown to more
763 // or less fit the enclosed text.
764 class SecureMessageDialog : public KDialog
765 {
766  public:
767  SecureMessageDialog(QWidget *parent) : KDialog(parent), m_textEdit(0)
768  {
769  }
770 
771  void setTextEdit(QPlainTextEdit *textEdit)
772  {
773  m_textEdit = textEdit;
774  }
775 
776  protected:
777  virtual void showEvent(QShowEvent* e)
778  {
779  // Now that we're shown, use our width to calculate a good
780  // bounding box for the text, and resize m_textEdit appropriately.
781  KDialog::showEvent(e);
782 
783  if(!m_textEdit)
784  return;
785 
786  QSize fudge(20, 24); // About what it sounds like :-/
787 
788  // Form rect with a lot of height for bounding. Use no more than
789  // 5 lines.
790  QRect curRect(m_textEdit->rect());
791  QFontMetrics metrics(fontMetrics());
792  curRect.setHeight(5 * metrics.lineSpacing());
793  curRect.setWidth(qMax(curRect.width(), 300)); // At least 300 pixels ok?
794 
795  QString text(m_textEdit->toPlainText());
796  curRect = metrics.boundingRect(curRect, Qt::TextWordWrap | Qt::TextSingleLine, text);
797 
798  // Scroll bars interfere. If we don't think there's enough room, enable
799  // the vertical scrollbar however.
800  m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
801  if(curRect.height() < m_textEdit->height()) { // then we've got room
802  m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
803  m_textEdit->setMaximumHeight(curRect.height() + fudge.height());
804  }
805 
806  m_textEdit->setMinimumSize(curRect.size() + fudge);
807  m_textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
808  updateGeometry();
809  }
810 
811  private:
812  QPlainTextEdit *m_textEdit;
813 };
814 
815 // Helper function to make the given .desktop file executable by ensuring
816 // that a #!/usr/bin/env xdg-open line is added if necessary and the file has
817 // the +x bit set for the user. Returns false if either fails.
818 static bool makeFileExecutable(const QString &fileName)
819 {
820  // Open the file and read the first two characters, check if it's
821  // #!. If not, create a new file, prepend appropriate lines, and copy
822  // over.
823  QFile desktopFile(fileName);
824  if (!desktopFile.open(QFile::ReadOnly)) {
825  kError(7010) << "Error opening service" << fileName << desktopFile.errorString();
826  return false;
827  }
828 
829  QByteArray header = desktopFile.peek(2); // First two chars of file
830  if (header.size() == 0) {
831  kError(7010) << "Error inspecting service" << fileName << desktopFile.errorString();
832  return false; // Some kind of error
833  }
834 
835  if (header != "#!") {
836  // Add header
837  KSaveFile saveFile;
838  saveFile.setFileName(fileName);
839  if (!saveFile.open()) {
840  kError(7010) << "Unable to open replacement file for" << fileName << saveFile.errorString();
841  return false;
842  }
843 
844  QByteArray shebang("#!/usr/bin/env xdg-open\n");
845  if (saveFile.write(shebang) != shebang.size()) {
846  kError(7010) << "Error occurred adding header for" << fileName << saveFile.errorString();
847  saveFile.abort();
848  return false;
849  }
850 
851  // Now copy the one into the other and then close and reopen desktopFile
852  QByteArray desktopData(desktopFile.readAll());
853  if (desktopData.isEmpty()) {
854  kError(7010) << "Unable to read service" << fileName << desktopFile.errorString();
855  saveFile.abort();
856  return false;
857  }
858 
859  if (saveFile.write(desktopData) != desktopData.size()) {
860  kError(7010) << "Error copying service" << fileName << saveFile.errorString();
861  saveFile.abort();
862  return false;
863  }
864 
865  desktopFile.close();
866  if (!saveFile.finalize()) { // Figures....
867  kError(7010) << "Error committing changes to service" << fileName << saveFile.errorString();
868  return false;
869  }
870 
871  if (!desktopFile.open(QFile::ReadOnly)) {
872  kError(7010) << "Error re-opening service" << fileName << desktopFile.errorString();
873  return false;
874  }
875  } // Add header
876 
877  // corresponds to owner on unix, which will have to do since if the user
878  // isn't the owner we can't change perms anyways.
879  if (!desktopFile.setPermissions(QFile::ExeUser | desktopFile.permissions())) {
880  kError(7010) << "Unable to change permissions for" << fileName << desktopFile.errorString();
881  return false;
882  }
883 
884  // whew
885  return true;
886 }
887 
888 // Helper function to make a .desktop file executable if prompted by the user.
889 // returns true if KRun::run() should continue with execution, false if user declined
890 // to make the file executable or we failed to make it executable.
891 static bool makeServiceExecutable(const KService& service, QWidget* window)
892 {
893  if (!KAuthorized::authorize("run_desktop_files")) {
894  kWarning() << "No authorization to execute " << service.entryPath();
895  KMessageBox::sorry(window, i18n("You are not authorized to execute this service."));
896  return false; // Don't circumvent the Kiosk
897  }
898 
899  KGuiItem continueItem = KStandardGuiItem::cont();
900 
901  SecureMessageDialog *baseDialog = new SecureMessageDialog(window);
902 
903  baseDialog->setButtons(KDialog::Ok | KDialog::Cancel);
904  baseDialog->setButtonGuiItem(KDialog::Ok, continueItem);
905  baseDialog->setDefaultButton(KDialog::Cancel);
906  baseDialog->setButtonFocus(KDialog::Cancel);
907  baseDialog->setCaption(i18nc("Warning about executing unknown .desktop file", "Warning"));
908 
909  // Dialog will have explanatory text with a disabled lineedit with the
910  // Exec= to make it visually distinct.
911  QWidget *baseWidget = new QWidget(baseDialog);
912  QHBoxLayout *mainLayout = new QHBoxLayout(baseWidget);
913 
914  QLabel *iconLabel = new QLabel(baseWidget);
915  QPixmap warningIcon(KIconLoader::global()->loadIcon("dialog-warning", KIconLoader::NoGroup, KIconLoader::SizeHuge));
916  mainLayout->addWidget(iconLabel);
917  iconLabel->setPixmap(warningIcon);
918 
919  QVBoxLayout *contentLayout = new QVBoxLayout;
920  QString warningMessage = i18nc("program name follows in a line edit below",
921  "This will start the program:");
922 
923  QLabel *message = new QLabel(warningMessage, baseWidget);
924  contentLayout->addWidget(message);
925 
926  // We can use KStandardDirs::findExe to resolve relative pathnames
927  // but that gets rid of the command line arguments.
928  QString program = KStandardDirs::realFilePath(service.exec());
929 
930  QPlainTextEdit *textEdit = new QPlainTextEdit(baseWidget);
931  textEdit->setPlainText(program);
932  textEdit->setReadOnly(true);
933  contentLayout->addWidget(textEdit);
934 
935  QLabel *footerLabel = new QLabel(i18n("If you do not trust this program, click Cancel"));
936  contentLayout->addWidget(footerLabel);
937  contentLayout->addStretch(0); // Don't allow the text edit to expand
938 
939  mainLayout->addLayout(contentLayout);
940 
941  baseDialog->setMainWidget(baseWidget);
942  baseDialog->setTextEdit(textEdit);
943 
944  // Constrain maximum size. Minimum size set in
945  // the dialog's show event.
946  QSize screenSize = QApplication::desktop()->screen()->size();
947  baseDialog->resize(screenSize.width() / 4, 50);
948  baseDialog->setMaximumHeight(screenSize.height() / 3);
949  baseDialog->setMaximumWidth(screenSize.width() / 10 * 8);
950 
951  int result = baseDialog->exec();
952  if (result != KDialog::Accepted) {
953  return false;
954  }
955 
956  // Assume that service is an absolute path since we're being called (relative paths
957  // would have been allowed unless Kiosk said no, therefore we already know where the
958  // .desktop file is. Now add a header to it if it doesn't already have one
959  // and add the +x bit.
960 
961  if (!::makeFileExecutable(service.entryPath())) {
962  QString serviceName = service.name();
963  if(serviceName.isEmpty())
964  serviceName = service.genericName();
965 
966  KMessageBox::sorry(
967  window,
968  i18n("Unable to make the service %1 executable, aborting execution", serviceName)
969  );
970 
971  return false;
972  }
973 
974  return true;
975 }
976 
977 bool KRun::run(const KService& _service, const KUrl::List& _urls, QWidget* window,
978  bool tempFiles, const QString& suggestedFileName, const QByteArray& asn)
979 {
980  if (!_service.entryPath().isEmpty() &&
981  !KDesktopFile::isAuthorizedDesktopFile(_service.entryPath()) &&
982  !::makeServiceExecutable(_service, window))
983  {
984  return false;
985  }
986 
987  if (!tempFiles) {
988  // Remember we opened those urls, for the "recent documents" menu in kicker
989  KUrl::List::ConstIterator it = _urls.begin();
990  for (; it != _urls.end(); ++it) {
991  //kDebug(7010) << "KRecentDocument::adding " << (*it).url();
992  KRecentDocument::add(*it, _service.desktopEntryName());
993  }
994  }
995 
996  if (tempFiles || _service.entryPath().isEmpty() || !suggestedFileName.isEmpty()) {
997  return runTempService(_service, _urls, window, tempFiles, suggestedFileName, asn);
998  }
999 
1000  kDebug(7010) << "KRun::run " << _service.entryPath();
1001 
1002  if (!_urls.isEmpty()) {
1003  kDebug(7010) << "First url " << _urls.first().url();
1004  }
1005 
1006  // Resolve urls if needed, depending on what the app supports
1007  const KUrl::List urls = resolveURLs(_urls, _service);
1008 
1009  QString error;
1010  int pid = 0;
1011 
1012  QByteArray myasn = asn;
1013  // startServiceByDesktopPath() doesn't take QWidget*, add it to the startup info now
1014  if (window != NULL) {
1015  if (myasn.isEmpty()) {
1016  myasn = KStartupInfo::createNewStartupId();
1017  }
1018  if (myasn != "0") {
1019  KStartupInfoId id;
1020  id.initId(myasn);
1021  KStartupInfoData data;
1022  data.setLaunchedBy(window->winId());
1023  KStartupInfo::sendChange(id, data);
1024  }
1025  }
1026 
1027  int i = KToolInvocation::startServiceByDesktopPath(
1028  _service.entryPath(), urls.toStringList(), &error, 0L, &pid, myasn
1029  );
1030 
1031  if (i != 0) {
1032  kDebug(7010) << error;
1033  KMessageBox::sorry(window, error);
1034  return false;
1035  }
1036 
1037  kDebug(7010) << "startServiceByDesktopPath worked fine";
1038  return true;
1039 }
1040 
1041 
1042 bool KRun::run(const QString& _exec, const KUrl::List& _urls, QWidget* window, const QString& _name,
1043  const QString& _icon, const QByteArray& asn)
1044 {
1045  KService::Ptr service(new KService(_name, _exec, _icon));
1046 
1047  return run(*service, _urls, window, false, QString(), asn);
1048 }
1049 
1050 bool KRun::runCommand(const QString &cmd, QWidget* window)
1051 {
1052  return runCommand(cmd, window, QString());
1053 }
1054 
1055 bool KRun::runCommand(const QString& cmd, QWidget* window, const QString& workingDirectory)
1056 {
1057  if (cmd.isEmpty()) {
1058  kWarning() << "Command was empty, nothing to run";
1059  return false;
1060  }
1061 
1062  const QStringList args = KShell::splitArgs(cmd);
1063  if (args.isEmpty()) {
1064  kWarning() << "Command could not be parsed.";
1065  return false;
1066  }
1067 
1068  const QString bin = args.first();
1069  return KRun::runCommand(cmd, bin, bin /*iconName*/, window, QByteArray(), workingDirectory);
1070 }
1071 
1072 bool KRun::runCommand(const QString& cmd, const QString &execName, const QString & iconName, QWidget* window, const QByteArray& asn)
1073 {
1074  return runCommand(cmd, execName, iconName, window, asn, QString());
1075 }
1076 
1077 bool KRun::runCommand(const QString& cmd, const QString &execName, const QString & iconName,
1078  QWidget* window, const QByteArray& asn, const QString& workingDirectory)
1079 {
1080  kDebug(7010) << "runCommand " << cmd << "," << execName;
1081  KProcess * proc = new KProcess;
1082  proc->setShellCommand(cmd);
1083  if (!workingDirectory.isEmpty()) {
1084  proc->setWorkingDirectory(workingDirectory);
1085  }
1086  QString bin = binaryName(execName, true);
1087  KService::Ptr service = KService::serviceByDesktopName(bin);
1088  return runCommandInternal(proc, service.data(),
1089  execName /*executable to check for in slotProcessExited*/,
1090  execName /*user-visible name*/,
1091  iconName, window, asn);
1092 }
1093 
1094 KRun::KRun(const KUrl& url, QWidget* window, mode_t mode, bool isLocalFile,
1095  bool showProgressInfo, const QByteArray& asn)
1096  : d(new KRunPrivate(this))
1097 {
1098  d->m_timer.setObjectName("KRun::timer");
1099  d->m_timer.setSingleShot(true);
1100  d->init(url, window, mode, isLocalFile, showProgressInfo, asn);
1101 }
1102 
1103 void KRun::KRunPrivate::init(const KUrl& url, QWidget* window, mode_t mode, bool isLocalFile,
1104  bool showProgressInfo, const QByteArray& asn)
1105 {
1106  m_bFault = false;
1107  m_bAutoDelete = true;
1108  m_bProgressInfo = showProgressInfo;
1109  m_bFinished = false;
1110  m_job = 0L;
1111  m_strURL = url;
1112  m_bScanFile = false;
1113  m_bIsDirectory = false;
1114  m_bIsLocalFile = isLocalFile;
1115  m_mode = mode;
1116  m_runExecutables = true;
1117  m_window = window;
1118  m_asn = asn;
1119  q->setEnableExternalBrowser(true);
1120 
1121  // Start the timer. This means we will return to the event
1122  // loop and do initialization afterwards.
1123  // Reason: We must complete the constructor before we do anything else.
1124  m_bInit = true;
1125  q->connect(&m_timer, SIGNAL(timeout()), q, SLOT(slotTimeout()));
1126  startTimer();
1127  //kDebug(7010) << "new KRun" << q << url << "timer=" << &m_timer;
1128 
1129  KGlobal::ref();
1130 }
1131 
1132 void KRun::init()
1133 {
1134  kDebug(7010) << "INIT called";
1135  if (!d->m_strURL.isValid()) {
1136  // TODO KDE5: call virtual method on error (see BrowserRun::init)
1137  d->m_showingDialog = true;
1138  KMessageBoxWrapper::error(d->m_window, i18n("Malformed URL\n%1", d->m_strURL.url()));
1139  d->m_showingDialog = false;
1140  d->m_bFault = true;
1141  d->m_bFinished = true;
1142  d->startTimer();
1143  return;
1144  }
1145  if (!KAuthorized::authorizeUrlAction("open", KUrl(), d->m_strURL)) {
1146  QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->m_strURL.prettyUrl());
1147  d->m_showingDialog = true;
1148  KMessageBoxWrapper::error(d->m_window, msg);
1149  d->m_showingDialog = false;
1150  d->m_bFault = true;
1151  d->m_bFinished = true;
1152  d->startTimer();
1153  return;
1154  }
1155 
1156  if (!d->m_bIsLocalFile && d->m_strURL.isLocalFile()) {
1157  d->m_bIsLocalFile = true;
1158  }
1159 
1160  if (!d->m_externalBrowser.isEmpty() && d->m_strURL.protocol().startsWith(QLatin1String("http"))) {
1161  if (d->runExecutable(d->m_externalBrowser)) {
1162  return;
1163  }
1164  } else if (d->m_bIsLocalFile) {
1165  if (d->m_mode == 0) {
1166  KDE_struct_stat buff;
1167  if (KDE::stat(d->m_strURL.toLocalFile(), &buff) == -1) {
1168  d->m_showingDialog = true;
1169  KMessageBoxWrapper::error(d->m_window,
1170  i18n("<qt>Unable to run the command specified. "
1171  "The file or folder <b>%1</b> does not exist.</qt>" ,
1172  Qt::escape(d->m_strURL.prettyUrl())));
1173  d->m_showingDialog = false;
1174  d->m_bFault = true;
1175  d->m_bFinished = true;
1176  d->startTimer();
1177  return;
1178  }
1179  d->m_mode = buff.st_mode;
1180  }
1181 
1182  KMimeType::Ptr mime = KMimeType::findByUrl(d->m_strURL, d->m_mode, true /*local*/);
1183  assert(mime);
1184  kDebug(7010) << "MIME TYPE is " << mime->name();
1185  if (!d->m_externalBrowser.isEmpty() &&
1186  (mime->is(QLatin1String("text/html")) ||
1187  mime->is(QLatin1String("application/xhtml+xml")))) {
1188  if (d->runExecutable(d->m_externalBrowser)) {
1189  return;
1190  }
1191  } else if (mime->isDefault() && !QFileInfo(d->m_strURL.toLocalFile()).isReadable()) {
1192  // Unknown mimetype because the file is unreadable, no point in showing an open-with dialog (#261002)
1193  const QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->m_strURL.prettyUrl());
1194  d->m_showingDialog = true;
1195  KMessageBoxWrapper::error(d->m_window, msg);
1196  d->m_showingDialog = false;
1197  d->m_bFault = true;
1198  d->m_bFinished = true;
1199  d->startTimer();
1200  return;
1201  } else {
1202  mimeTypeDetermined(mime->name());
1203  return;
1204  }
1205  }
1206  else if (KProtocolInfo::isHelperProtocol(d->m_strURL)) {
1207  kDebug(7010) << "Helper protocol";
1208  const QString exec = KProtocolInfo::exec(d->m_strURL.protocol());
1209  if (exec.isEmpty()) {
1210  mimeTypeDetermined(KProtocolManager::defaultMimetype(d->m_strURL));
1211  return;
1212  } else {
1213  if (run(exec, KUrl::List() << d->m_strURL, d->m_window, QString(), QString(), d->m_asn)) {
1214  d->m_bFinished = true;
1215  d->startTimer();
1216  return;
1217  }
1218  }
1219  }
1220 
1221  // Did we already get the information that it is a directory ?
1222  if (S_ISDIR(d->m_mode)) {
1223  mimeTypeDetermined("inode/directory");
1224  return;
1225  }
1226 
1227  // Let's see whether it is a directory
1228 
1229  if (!KProtocolManager::supportsListing(d->m_strURL)) {
1230  //kDebug(7010) << "Protocol has no support for listing";
1231  // No support for listing => it can't be a directory (example: http)
1232  scanFile();
1233  return;
1234  }
1235 
1236  kDebug(7010) << "Testing directory (stating)";
1237 
1238  // It may be a directory or a file, let's stat
1239  KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
1240  KIO::StatJob *job = KIO::stat(d->m_strURL, KIO::StatJob::SourceSide, 0 /* no details */, flags);
1241  job->ui()->setWindow(d->m_window);
1242  connect(job, SIGNAL(result(KJob*)),
1243  this, SLOT(slotStatResult(KJob*)));
1244  d->m_job = job;
1245  kDebug(7010) << " Job " << job << " is about stating " << d->m_strURL.url();
1246 }
1247 
1248 KRun::~KRun()
1249 {
1250  //kDebug(7010) << this;
1251  d->m_timer.stop();
1252  killJob();
1253  KGlobal::deref();
1254  //kDebug(7010) << this << "done";
1255  delete d;
1256 }
1257 
1258 bool KRun::KRunPrivate::runExecutable(const QString& _exec)
1259 {
1260  KUrl::List urls;
1261  urls.append(m_strURL);
1262  if (_exec.startsWith('!')) {
1263  QString exec = _exec.mid(1); // Literal command
1264  exec += " %u";
1265  if (q->run(exec, urls, m_window, QString(), QString(), m_asn)) {
1266  m_bFinished = true;
1267  startTimer();
1268  return true;
1269  }
1270  }
1271  else {
1272  KService::Ptr service = KService::serviceByStorageId(_exec);
1273  if (service && q->run(*service, urls, m_window, false, QString(), m_asn)) {
1274  m_bFinished = true;
1275  startTimer();
1276  return true;
1277  }
1278  }
1279  return false;
1280 }
1281 
1282 void KRun::scanFile()
1283 {
1284  kDebug(7010) << d->m_strURL;
1285  // First, let's check for well-known extensions
1286  // Not when there is a query in the URL, in any case.
1287  if (d->m_strURL.query().isEmpty()) {
1288  KMimeType::Ptr mime = KMimeType::findByUrl(d->m_strURL);
1289  assert(mime);
1290  if (!mime->isDefault() || d->m_bIsLocalFile) {
1291  kDebug(7010) << "Scanfile: MIME TYPE is " << mime->name();
1292  mimeTypeDetermined(mime->name());
1293  return;
1294  }
1295  }
1296 
1297  // No mimetype found, and the URL is not local (or fast mode not allowed).
1298  // We need to apply the 'KIO' method, i.e. either asking the server or
1299  // getting some data out of the file, to know what mimetype it is.
1300 
1301  if (!KProtocolManager::supportsReading(d->m_strURL)) {
1302  kError(7010) << "#### NO SUPPORT FOR READING!";
1303  d->m_bFault = true;
1304  d->m_bFinished = true;
1305  d->startTimer();
1306  return;
1307  }
1308  kDebug(7010) << this << " Scanning file " << d->m_strURL.url();
1309 
1310  KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
1311  KIO::TransferJob *job = KIO::get(d->m_strURL, KIO::NoReload /*reload*/, flags);
1312  job->ui()->setWindow(d->m_window);
1313  connect(job, SIGNAL(result(KJob*)),
1314  this, SLOT(slotScanFinished(KJob*)));
1315  connect(job, SIGNAL(mimetype(KIO::Job*,QString)),
1316  this, SLOT(slotScanMimeType(KIO::Job*,QString)));
1317  d->m_job = job;
1318  kDebug(7010) << " Job " << job << " is about getting from " << d->m_strURL.url();
1319 }
1320 
1321 // When arriving in that method there are 5 possible states:
1322 // must_init, must_scan_file, found_dir, done+error or done+success.
1323 void KRun::slotTimeout()
1324 {
1325  kDebug(7010) << this << " slotTimeout called";
1326  if (d->m_bInit) {
1327  d->m_bInit = false;
1328  init();
1329  return;
1330  }
1331 
1332  if (d->m_bFault) {
1333  emit error();
1334  }
1335  if (d->m_bFinished) {
1336  emit finished();
1337  }
1338  else {
1339  if (d->m_bScanFile) {
1340  d->m_bScanFile = false;
1341  scanFile();
1342  return;
1343  }
1344  else if (d->m_bIsDirectory) {
1345  d->m_bIsDirectory = false;
1346  mimeTypeDetermined("inode/directory");
1347  return;
1348  }
1349  }
1350 
1351  if (d->m_bAutoDelete) {
1352  deleteLater();
1353  return;
1354  }
1355 }
1356 
1357 void KRun::slotStatResult(KJob * job)
1358 {
1359  d->m_job = 0L;
1360  const int errCode = job->error();
1361  if (errCode) {
1362  // ERR_NO_CONTENT is not an error, but an indication no further
1363  // actions needs to be taken.
1364  if (errCode != KIO::ERR_NO_CONTENT) {
1365  d->m_showingDialog = true;
1366  kError(7010) << this << "ERROR" << job->error() << job->errorString();
1367  job->uiDelegate()->showErrorMessage();
1368  //kDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us";
1369  d->m_showingDialog = false;
1370  d->m_bFault = true;
1371  }
1372 
1373  d->m_bFinished = true;
1374 
1375  // will emit the error and autodelete this
1376  d->startTimer();
1377  }
1378  else {
1379  kDebug(7010) << "Finished";
1380 
1381  KIO::StatJob* statJob = qobject_cast<KIO::StatJob*>(job);
1382  if (!statJob) {
1383  kFatal() << "job is a " << typeid(*job).name() << " should be a StatJob";
1384  }
1385 
1386  // Update our URL in case of a redirection
1387  setUrl(statJob->url());
1388 
1389  const KIO::UDSEntry entry = statJob->statResult();
1390  const mode_t mode = entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE);
1391  if (S_ISDIR(mode)) {
1392  d->m_bIsDirectory = true; // it's a dir
1393  }
1394  else {
1395  d->m_bScanFile = true; // it's a file
1396  }
1397 
1398  d->m_localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
1399 
1400  // mimetype already known? (e.g. print:/manager)
1401  const QString knownMimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE) ;
1402 
1403  if (!knownMimeType.isEmpty()) {
1404  mimeTypeDetermined(knownMimeType);
1405  d->m_bFinished = true;
1406  }
1407 
1408  // We should have found something
1409  assert(d->m_bScanFile || d->m_bIsDirectory);
1410 
1411  // Start the timer. Once we get the timer event this
1412  // protocol server is back in the pool and we can reuse it.
1413  // This gives better performance than starting a new slave
1414  d->startTimer();
1415  }
1416 }
1417 
1418 void KRun::slotScanMimeType(KIO::Job *, const QString &mimetype)
1419 {
1420  if (mimetype.isEmpty()) {
1421  kWarning(7010) << "get() didn't emit a mimetype! Probably a kioslave bug, please check the implementation of" << url().protocol();
1422  }
1423  mimeTypeDetermined(mimetype);
1424  d->m_job = 0;
1425 }
1426 
1427 void KRun::slotScanFinished(KJob *job)
1428 {
1429  d->m_job = 0;
1430  const int errCode = job->error();
1431  if (errCode) {
1432  // ERR_NO_CONTENT is not an error, but an indication no further
1433  // actions needs to be taken.
1434  if (errCode != KIO::ERR_NO_CONTENT) {
1435  d->m_showingDialog = true;
1436  kError(7010) << this << "ERROR (stat):" << job->error() << ' ' << job->errorString();
1437  job->uiDelegate()->showErrorMessage();
1438  //kDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us";
1439  d->m_showingDialog = false;
1440 
1441  d->m_bFault = true;
1442  }
1443 
1444  d->m_bFinished = true;
1445  // will emit the error and autodelete this
1446  d->startTimer();
1447  }
1448 }
1449 
1450 void KRun::mimeTypeDetermined(const QString& mimeType)
1451 {
1452  // foundMimeType reimplementations might show a dialog box;
1453  // make sure some timer doesn't kill us meanwhile (#137678, #156447)
1454  Q_ASSERT(!d->m_showingDialog);
1455  d->m_showingDialog = true;
1456 
1457  foundMimeType(mimeType);
1458 
1459  d->m_showingDialog = false;
1460 
1461  // We cannot assume that we're finished here. Some reimplementations
1462  // start a KIO job and call setFinished only later.
1463 }
1464 
1465 void KRun::foundMimeType(const QString& type)
1466 {
1467  kDebug(7010) << "Resulting mime type is " << type;
1468 
1469  KIO::TransferJob *job = qobject_cast<KIO::TransferJob *>(d->m_job);
1470  if (job) {
1471  // Update our URL in case of a redirection
1472  setUrl( job->url() );
1473 
1474  job->putOnHold();
1475  KIO::Scheduler::publishSlaveOnHold();
1476  d->m_job = 0;
1477  }
1478 
1479  Q_ASSERT(!d->m_bFinished);
1480 
1481  // Support for preferred service setting, see setPreferredService
1482  if (!d->m_preferredService.isEmpty()) {
1483  kDebug(7010) << "Attempting to open with preferred service: " << d->m_preferredService;
1484  KService::Ptr serv = KService::serviceByDesktopName(d->m_preferredService);
1485  if (serv && serv->hasMimeType(type)) {
1486  KUrl::List lst;
1487  lst.append(d->m_strURL);
1488  if (KRun::run(*serv, lst, d->m_window, false, QString(), d->m_asn)) {
1489  setFinished(true);
1490  return;
1491  }
1496  }
1497  }
1498 
1499  // Resolve .desktop files from media:/, remote:/, applications:/ etc.
1500  KMimeType::Ptr mime = KMimeType::mimeType(type, KMimeType::ResolveAliases);
1501  if (!mime) {
1502  kWarning(7010) << "Unknown mimetype " << type;
1503  }
1504  if (mime && mime->is("application/x-desktop") && !d->m_localPath.isEmpty()) {
1505  d->m_strURL = KUrl();
1506  d->m_strURL.setPath(d->m_localPath);
1507  }
1508 
1509  if (!KRun::runUrl(d->m_strURL, type, d->m_window, false /*tempfile*/, d->m_runExecutables, d->m_suggestedFileName, d->m_asn)) {
1510  d->m_bFault = true;
1511  }
1512  setFinished(true);
1513 }
1514 
1515 void KRun::killJob()
1516 {
1517  if (d->m_job) {
1518  kDebug(7010) << this << "m_job=" << d->m_job;
1519  d->m_job->kill();
1520  d->m_job = 0L;
1521  }
1522 }
1523 
1524 void KRun::abort()
1525 {
1526  if (d->m_bFinished) {
1527  return;
1528  }
1529  kDebug(7010) << this << "m_showingDialog=" << d->m_showingDialog;
1530  killJob();
1531  // If we're showing an error message box, the rest will be done
1532  // after closing the msgbox -> don't autodelete nor emit signals now.
1533  if (d->m_showingDialog) {
1534  return;
1535  }
1536  d->m_bFault = true;
1537  d->m_bFinished = true;
1538  d->m_bInit = false;
1539  d->m_bScanFile = false;
1540 
1541  // will emit the error and autodelete this
1542  d->startTimer();
1543 }
1544 
1545 QWidget* KRun::window() const
1546 {
1547  return d->m_window;
1548 }
1549 
1550 bool KRun::hasError() const
1551 {
1552  return d->m_bFault;
1553 }
1554 
1555 bool KRun::hasFinished() const
1556 {
1557  return d->m_bFinished;
1558 }
1559 
1560 bool KRun::autoDelete() const
1561 {
1562  return d->m_bAutoDelete;
1563 }
1564 
1565 void KRun::setAutoDelete(bool b)
1566 {
1567  d->m_bAutoDelete = b;
1568 }
1569 
1570 void KRun::setEnableExternalBrowser(bool b)
1571 {
1572  if (b) {
1573  d->m_externalBrowser = KConfigGroup(KGlobal::config(), "General").readEntry("BrowserApplication");
1574  }
1575  else {
1576  d->m_externalBrowser.clear();
1577  }
1578 }
1579 
1580 void KRun::setPreferredService(const QString& desktopEntryName)
1581 {
1582  d->m_preferredService = desktopEntryName;
1583 }
1584 
1585 void KRun::setRunExecutables(bool b)
1586 {
1587  d->m_runExecutables = b;
1588 }
1589 
1590 void KRun::setSuggestedFileName(const QString& fileName)
1591 {
1592  d->m_suggestedFileName = fileName;
1593 }
1594 
1595 QString KRun::suggestedFileName() const
1596 {
1597  return d->m_suggestedFileName;
1598 }
1599 
1600 bool KRun::isExecutable(const QString& serviceType)
1601 {
1602  return (serviceType == "application/x-desktop" ||
1603  serviceType == "application/x-executable" ||
1604  serviceType == "application/x-ms-dos-executable" ||
1605  serviceType == "application/x-shellscript");
1606 }
1607 
1608 void KRun::setUrl(const KUrl &url)
1609 {
1610  d->m_strURL = url;
1611 }
1612 
1613 KUrl KRun::url() const
1614 {
1615  return d->m_strURL;
1616 }
1617 
1618 void KRun::setError(bool error)
1619 {
1620  d->m_bFault = error;
1621 }
1622 
1623 void KRun::setProgressInfo(bool progressInfo)
1624 {
1625  d->m_bProgressInfo = progressInfo;
1626 }
1627 
1628 bool KRun::progressInfo() const
1629 {
1630  return d->m_bProgressInfo;
1631 }
1632 
1633 void KRun::setFinished(bool finished)
1634 {
1635  d->m_bFinished = finished;
1636  if (finished)
1637  d->startTimer();
1638 }
1639 
1640 void KRun::setJob(KIO::Job *job)
1641 {
1642  d->m_job = job;
1643 }
1644 
1645 KIO::Job* KRun::job()
1646 {
1647  return d->m_job;
1648 }
1649 
1650 #ifndef KDE_NO_DEPRECATED
1651 QTimer& KRun::timer()
1652 {
1653  return d->m_timer;
1654 }
1655 #endif
1656 
1657 #ifndef KDE_NO_DEPRECATED
1658 void KRun::setDoScanFile(bool scanFile)
1659 {
1660  d->m_bScanFile = scanFile;
1661 }
1662 #endif
1663 
1664 #ifndef KDE_NO_DEPRECATED
1665 bool KRun::doScanFile() const
1666 {
1667  return d->m_bScanFile;
1668 }
1669 #endif
1670 
1671 #ifndef KDE_NO_DEPRECATED
1672 void KRun::setIsDirecory(bool isDirectory)
1673 {
1674  d->m_bIsDirectory = isDirectory;
1675 }
1676 #endif
1677 
1678 bool KRun::isDirectory() const
1679 {
1680  return d->m_bIsDirectory;
1681 }
1682 
1683 #ifndef KDE_NO_DEPRECATED
1684 void KRun::setInitializeNextAction(bool initialize)
1685 {
1686  d->m_bInit = initialize;
1687 }
1688 #endif
1689 
1690 #ifndef KDE_NO_DEPRECATED
1691 bool KRun::initializeNextAction() const
1692 {
1693  return d->m_bInit;
1694 }
1695 #endif
1696 
1697 void KRun::setIsLocalFile(bool isLocalFile)
1698 {
1699  d->m_bIsLocalFile = isLocalFile;
1700 }
1701 
1702 bool KRun::isLocalFile() const
1703 {
1704  return d->m_bIsLocalFile;
1705 }
1706 
1707 void KRun::setMode(mode_t mode)
1708 {
1709  d->m_mode = mode;
1710 }
1711 
1712 mode_t KRun::mode() const
1713 {
1714  return d->m_mode;
1715 }
1716 
1717 /****************/
1718 
1719 #ifndef Q_WS_X11
1720 int KProcessRunner::run(KProcess * p, const QString & executable)
1721 {
1722  return (new KProcessRunner(p, executable))->pid();
1723 }
1724 #else
1725 int KProcessRunner::run(KProcess * p, const QString & executable, const KStartupInfoId& id)
1726 {
1727  return (new KProcessRunner(p, executable, id))->pid();
1728 }
1729 #endif
1730 
1731 #ifndef Q_WS_X11
1732 KProcessRunner::KProcessRunner(KProcess * p, const QString & executable)
1733 #else
1734 KProcessRunner::KProcessRunner(KProcess * p, const QString & executable, const KStartupInfoId& _id) :
1735  id(_id)
1736 #endif
1737 {
1738  m_pid = 0;
1739  process = p;
1740  m_executable = executable;
1741  connect(process, SIGNAL(finished(int,QProcess::ExitStatus)),
1742  this, SLOT(slotProcessExited(int,QProcess::ExitStatus)));
1743 
1744  process->start();
1745  if (!process->waitForStarted()) {
1746  //kDebug() << "wait for started failed, exitCode=" << process->exitCode()
1747  // << "exitStatus=" << process->exitStatus();
1748  // Note that exitCode is 255 here (the first time), and 0 later on (bug?).
1749  slotProcessExited(255, process->exitStatus());
1750  }
1751  else {
1752 #ifdef Q_WS_X11
1753  m_pid = process->pid();
1754 #endif
1755  }
1756 }
1757 
1758 KProcessRunner::~KProcessRunner()
1759 {
1760  delete process;
1761 }
1762 
1763 int KProcessRunner::pid() const
1764 {
1765  return m_pid;
1766 }
1767 
1768 void KProcessRunner::terminateStartupNotification()
1769 {
1770 #ifdef Q_WS_X11
1771  if (!id.none()) {
1772  KStartupInfoData data;
1773  data.addPid(m_pid); // announce this pid for the startup notification has finished
1774  data.setHostname();
1775  KStartupInfo::sendFinish(id, data);
1776  }
1777 #endif
1778 
1779 }
1780 
1781 void
1782 KProcessRunner::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
1783 {
1784  kDebug(7010) << m_executable << "exitCode=" << exitCode << "exitStatus=" << exitStatus;
1785  Q_UNUSED(exitStatus);
1786 
1787  terminateStartupNotification(); // do this before the messagebox
1788  if (exitCode != 0 && !m_executable.isEmpty()) {
1789  // Let's see if the error is because the exe doesn't exist.
1790  // When this happens, waitForStarted returns false, but not if kioexec
1791  // was involved, then we come here, that's why the code is here.
1792  //
1793  // We'll try to find the executable relatively to current directory,
1794  // (or with a full path, if m_executable is absolute), and then in the PATH.
1795  if (!QFile(m_executable).exists() && KStandardDirs::findExe(m_executable).isEmpty()) {
1796  KGlobal::ref();
1797  KMessageBox::sorry(0L, i18n("Could not find the program '%1'", m_executable));
1798  KGlobal::deref();
1799  }
1800  else {
1801  kDebug() << process->readAllStandardError();
1802  }
1803  }
1804  deleteLater();
1805 }
1806 
1807 #include "krun.moc"
1808 #include "krun_p.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Sep 23 2014 09:58:52 by doxygen 1.8.3.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIO

Skip menu "KIO"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs-4.11.5 API Reference

Skip menu "kdelibs-4.11.5 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal