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

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