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

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