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

Skip menu "kdelibs-4.10.3 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