• Skip to content
  • Skip to link menu
KDE 4.3 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

KIO

krun.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE libraries
00002     Copyright (C) 2000 Torben Weis <weis@kde.org>
00003     Copyright (C) 2006 David Faure <faure@kde.org>
00004     Copyright (C) 2009 Michael Pyne <michael.pyne@kdemail.net>
00005 
00006     This library is free software; you can redistribute it and/or
00007     modify it under the terms of the GNU Library General Public
00008     License as published by the Free Software Foundation; either
00009     version 2 of the License, or (at your option) any later version.
00010 
00011     This library is distributed in the hope that it will be useful,
00012     but WITHOUT ANY WARRANTY; without even the implied warranty of
00013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014     Library General Public License for more details.
00015 
00016     You should have received a copy of the GNU Library General Public License
00017     along with this library; see the file COPYING.LIB.  If not, write to
00018     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019     Boston, MA 02110-1301, USA.
00020 */
00021 
00022 #include "krun.h"
00023 #include "krun_p.h"
00024 
00025 #include <config.h>
00026 
00027 #include <assert.h>
00028 #include <stdlib.h>
00029 #include <string.h>
00030 #include <unistd.h>
00031 #include <typeinfo>
00032 #include <sys/stat.h>
00033 
00034 #include <QtGui/QWidget>
00035 #include <QtGui/QLabel>
00036 #include <QtGui/QVBoxLayout>
00037 #include <QtGui/QHBoxLayout>
00038 #include <QtGui/QPlainTextEdit>
00039 #include <QtGui/QApplication>
00040 #include <QtGui/QDesktopWidget>
00041 
00042 #include "kmimetypetrader.h"
00043 #include "kmimetype.h"
00044 #include "kio/jobclasses.h" // for KIO::JobFlags
00045 #include "kio/job.h"
00046 #include "kio/jobuidelegate.h"
00047 #include "kio/global.h"
00048 #include "kio/scheduler.h"
00049 #include "kio/netaccess.h"
00050 #include "kfile/kopenwithdialog.h"
00051 #include "kfile/krecentdocument.h"
00052 #include "kdesktopfileactions.h"
00053 
00054 #include <kauthorized.h>
00055 #include <kmessageboxwrapper.h>
00056 #include <kurl.h>
00057 #include <kglobal.h>
00058 #include <ktoolinvocation.h>
00059 #include <kauthorized.h>
00060 #include <kdebug.h>
00061 #include <klocale.h>
00062 #include <kprotocolmanager.h>
00063 #include <kstandarddirs.h>
00064 #include <kprocess.h>
00065 #include <QtCore/QFile>
00066 #include <QtCore/QFileInfo>
00067 #include <QtCore/QTextIStream>
00068 #include <QtCore/QDate>
00069 #include <QtCore/QRegExp>
00070 #include <kdesktopfile.h>
00071 #include <kmacroexpander.h>
00072 #include <kshell.h>
00073 #include <QTextDocument>
00074 #include <kde_file.h>
00075 #include <kconfiggroup.h>
00076 #include <kdialog.h>
00077 #include <kstandardguiitem.h>
00078 #include <kguiitem.h>
00079 #include <ksavefile.h>
00080 
00081 #ifdef Q_WS_X11
00082 #include <kwindowsystem.h>
00083 #endif
00084 
00085 KRun::KRunPrivate::KRunPrivate(KRun *parent)
00086         : q(parent),
00087         m_showingDialog(false)
00088 {
00089 }
00090 
00091 void KRun::KRunPrivate::startTimer()
00092 {
00093     m_timer.start(0);
00094 }
00095 
00096 // ---------------------------------------------------------------------------
00097 
00098 bool KRun::isExecutableFile(const KUrl& url, const QString &mimetype)
00099 {
00100     if (!url.isLocalFile()) {
00101         return false;
00102     }
00103     QFileInfo file(url.toLocalFile());
00104     if (file.isExecutable()) {    // Got a prospective file to run
00105         KMimeType::Ptr mimeType = KMimeType::mimeType(mimetype, KMimeType::ResolveAliases);
00106         if (mimeType && (mimeType->is(QLatin1String("application/x-executable")) ||
00107 #ifdef Q_WS_WIN
00108                          mimeType->is(QLatin1String("application/x-ms-dos-executable")) ||
00109 #endif
00110                          mimeType->is(QLatin1String("application/x-executable-script")))
00111            )
00112         {
00113             return true;
00114         }
00115     }
00116     return false;
00117 }
00118 
00119 // This is called by foundMimeType, since it knows the mimetype of the URL
00120 bool KRun::runUrl(const KUrl& u, const QString& _mimetype, QWidget* window, bool tempFile, bool runExecutables, const QString& suggestedFileName, const QByteArray& asn)
00121 {
00122     bool noRun = false;
00123     bool noAuth = false;
00124     if (_mimetype == QLatin1String("inode/directory-locked")) {
00125         KMessageBoxWrapper::error(window,
00126                                   i18n("<qt>Unable to enter <b>%1</b>.\nYou do not have access rights to this location.</qt>", Qt::escape(u.prettyUrl())));
00127         return false;
00128     }
00129     else if (_mimetype == QLatin1String("application/x-desktop")) {
00130         if (u.isLocalFile() && runExecutables) {
00131             return KDesktopFileActions::run(u, true);
00132         }
00133     }
00134     else if (isExecutableFile(u, _mimetype)) {
00135         if (u.isLocalFile() && runExecutables) {
00136             if (KAuthorized::authorize("shell_access")) {
00137                 return (KRun::runCommand(KShell::quoteArg(u.toLocalFile()), QString(), QString(), window, asn)); // just execute the url as a command
00138                 // ## TODO implement deleting the file if tempFile==true
00139             }
00140             else {
00141                 noAuth = true;
00142             }
00143         }
00144         else if (_mimetype == QLatin1String("application/x-executable")) {
00145             noRun = true;
00146         }
00147     }
00148     else if (isExecutable(_mimetype)) {
00149         if (!runExecutables) {
00150             noRun = true;
00151         }
00152 
00153         if (!KAuthorized::authorize("shell_access")) {
00154             noAuth = true;
00155         }
00156     }
00157 
00158     if (noRun) {
00159         KMessageBox::sorry(window,
00160                            i18n("<qt>The file <b>%1</b> is an executable program. "
00161                                 "For safety it will not be started.</qt>", Qt::escape(u.prettyUrl())));
00162         return false;
00163     }
00164     if (noAuth) {
00165         KMessageBoxWrapper::error(window,
00166                                   i18n("<qt>You do not have permission to run <b>%1</b>.</qt>", Qt::escape(u.prettyUrl())));
00167         return false;
00168     }
00169 
00170     KUrl::List lst;
00171     lst.append(u);
00172 
00173     KService::Ptr offer = KMimeTypeTrader::self()->preferredService(_mimetype);
00174 
00175     if (!offer) {
00176         // Open-with dialog
00177         // TODO : pass the mimetype as a parameter, to show it (comment field) in the dialog !
00178         // Hmm, in fact KOpenWithDialog::setServiceType already guesses the mimetype from the first URL of the list...
00179         return displayOpenWithDialog(lst, window, tempFile, suggestedFileName, asn);
00180     }
00181 
00182     return KRun::run(*offer, lst, window, tempFile, suggestedFileName, asn);
00183 }
00184 
00185 bool KRun::displayOpenWithDialog(const KUrl::List& lst, QWidget* window, bool tempFiles,
00186                                  const QString& suggestedFileName, const QByteArray& asn)
00187 {
00188     if (!KAuthorized::authorizeKAction("openwith")) {
00189         KMessageBox::sorry(window,
00190                            i18n("You are not authorized to select an application to open this file."));
00191         return false;
00192     }
00193 
00194 #ifdef Q_WS_WIN
00195     KConfigGroup cfgGroup(KGlobal::config(), "KOpenWithDialog Settings");
00196     if (cfgGroup.readEntry("Native", true)) {
00197         return KRun::KRunPrivate::displayNativeOpenWithDialog(lst, window, tempFiles,
00198                 suggestedFileName, asn);
00199     }
00200 #endif
00201     KOpenWithDialog l(lst, i18n("Open with:"), QString(), window);
00202     if (l.exec()) {
00203         KService::Ptr service = l.service();
00204         if (service) {
00205             return KRun::run(*service, lst, window, tempFiles, suggestedFileName, asn);
00206         }
00207 
00208         kDebug(7010) << "No service set, running " << l.text();
00209         return KRun::run(l.text(), lst, window, false, suggestedFileName, asn);   // TODO handle tempFiles
00210     }
00211     return false;
00212 }
00213 
00214 void KRun::shellQuote(QString &_str)
00215 {
00216     // Credits to Walter, says Bernd G. :)
00217     if (_str.isEmpty()) { // Don't create an explicit empty parameter
00218         return;
00219     }
00220     QChar q('\'');
00221     _str.replace(q, "'\\''").prepend(q).append(q);
00222 }
00223 
00224 
00225 class KRunMX1 : public KMacroExpanderBase
00226 {
00227 public:
00228     KRunMX1(const KService &_service) :
00229             KMacroExpanderBase('%'), hasUrls(false), hasSpec(false), service(_service) {}
00230 
00231     bool hasUrls: 1, hasSpec: 1;
00232 
00233 protected:
00234     virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret);
00235 
00236 private:
00237     const KService &service;
00238 };
00239 
00240 int
00241 KRunMX1::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
00242 {
00243     uint option = str[pos + 1].unicode();
00244     switch (option) {
00245     case 'c':
00246         ret << service.name().replace('%', "%%");
00247         break;
00248     case 'k':
00249         ret << service.entryPath().replace('%', "%%");
00250         break;
00251     case 'i':
00252         ret << "-icon" << service.icon().replace('%', "%%");
00253         break;
00254     case 'm':
00255 //       ret << "-miniicon" << service.icon().replace( '%', "%%" );
00256         kWarning() << "-miniicon isn't supported anymore (service"
00257         << service.name() << ')';
00258         break;
00259     case 'u':
00260     case 'U':
00261         hasUrls = true;
00262         /* fallthrough */
00263     case 'f':
00264     case 'F':
00265     case 'n':
00266     case 'N':
00267     case 'd':
00268     case 'D':
00269     case 'v':
00270         hasSpec = true;
00271         /* fallthrough */
00272     default:
00273         return -2; // subst with same and skip
00274     }
00275     return 2;
00276 }
00277 
00278 class KRunMX2 : public KMacroExpanderBase
00279 {
00280 public:
00281     KRunMX2(const KUrl::List &_urls) :
00282             KMacroExpanderBase('%'), ignFile(false), urls(_urls) {}
00283 
00284     bool ignFile: 1;
00285 
00286 protected:
00287     virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret);
00288 
00289 private:
00290     void subst(int option, const KUrl &url, QStringList &ret);
00291 
00292     const KUrl::List &urls;
00293 };
00294 
00295 void
00296 KRunMX2::subst(int option, const KUrl &url, QStringList &ret)
00297 {
00298     switch (option) {
00299     case 'u':
00300         ret << ((url.isLocalFile() && url.fragment().isNull() && url.encodedQuery().isNull()) ?
00301                 url.toLocalFile()  : url.url());
00302         break;
00303     case 'd':
00304         ret << url.directory();
00305         break;
00306     case 'f':
00307         ret << url.path();
00308         break;
00309     case 'n':
00310         ret << url.fileName();
00311         break;
00312     case 'v':
00313         if (url.isLocalFile() && QFile::exists(url.toLocalFile())) {
00314             ret << KDesktopFile(url.path()).desktopGroup().readEntry("Dev");
00315         }
00316         break;
00317     }
00318     return;
00319 }
00320 
00321 int
00322 KRunMX2::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
00323 {
00324     uint option = str[pos + 1].unicode();
00325     switch (option) {
00326     case 'f':
00327     case 'u':
00328     case 'n':
00329     case 'd':
00330     case 'v':
00331         if (urls.isEmpty()) {
00332             if (!ignFile) {
00333                 kDebug() << "No URLs supplied to single-URL service" << str;
00334             }
00335         }
00336         else if (urls.count() > 1) {
00337             kWarning() << urls.count() << "URLs supplied to single-URL service" << str;
00338         }
00339         else {
00340             subst(option, urls.first(), ret);
00341         }
00342         break;
00343     case 'F':
00344     case 'U':
00345     case 'N':
00346     case 'D':
00347         option += 'a' - 'A';
00348         for (KUrl::List::ConstIterator it = urls.begin(); it != urls.end(); ++it)
00349             subst(option, *it, ret);
00350         break;
00351     case '%':
00352         ret = QStringList(QLatin1String("%"));
00353         break;
00354     default:
00355         return -2; // subst with same and skip
00356     }
00357     return 2;
00358 }
00359 
00360 QStringList KRun::processDesktopExec(const KService &_service, const KUrl::List& _urls, bool tempFiles, const QString& suggestedFileName)
00361 {
00362     QString exec = _service.exec();
00363     if (exec.isEmpty()) {
00364         kWarning() << "KRun: no Exec field in `" << _service.entryPath() << "' !";
00365         return QStringList();
00366     }
00367 
00368     QStringList result;
00369     bool appHasTempFileOption;
00370 
00371     KRunMX1 mx1(_service);
00372     KRunMX2 mx2(_urls);
00373 
00374     if (!mx1.expandMacrosShellQuote(exec)) {    // Error in shell syntax
00375         kWarning() << "KRun: syntax error in command" << _service.exec() << ", service" << _service.name();
00376         return QStringList();
00377     }
00378 
00379     // FIXME: the current way of invoking kioexec disables term and su use
00380 
00381     // Check if we need "tempexec" (kioexec in fact)
00382     appHasTempFileOption = tempFiles && _service.property("X-KDE-HasTempFileOption").toBool();
00383     if (tempFiles && !appHasTempFileOption && _urls.size()) {
00384         const QString kioexec = KStandardDirs::findExe("kioexec");
00385         Q_ASSERT(!kioexec.isEmpty());
00386         result << kioexec << "--tempfiles" << exec;
00387         if (!suggestedFileName.isEmpty()) {
00388             result << "--suggestedfilename";
00389             result << suggestedFileName;
00390         }
00391         result += _urls.toStringList();
00392         return result;
00393     }
00394 
00395     // Check if we need kioexec
00396     if (!mx1.hasUrls) {
00397         for (KUrl::List::ConstIterator it = _urls.begin(); it != _urls.end(); ++it)
00398             if (!(*it).isLocalFile() && !KProtocolInfo::isHelperProtocol(*it)) {
00399                 // We need to run the app through kioexec
00400                 const QString kioexec = KStandardDirs::findExe("kioexec");
00401                 Q_ASSERT(!kioexec.isEmpty());
00402                 result << kioexec;
00403                 if (tempFiles) {
00404                     result << "--tempfiles";
00405                 }
00406                 if (!suggestedFileName.isEmpty()) {
00407                     result << "--suggestedfilename";
00408                     result << suggestedFileName;
00409                 }
00410                 result << exec;
00411                 result += _urls.toStringList();
00412                 return result;
00413             }
00414     }
00415 
00416     if (appHasTempFileOption) {
00417         exec += " --tempfile";
00418     }
00419 
00420     // Did the user forget to append something like '%f'?
00421     // If so, then assume that '%f' is the right choice => the application
00422     // accepts only local files.
00423     if (!mx1.hasSpec) {
00424         exec += " %f";
00425         mx2.ignFile = true;
00426     }
00427 
00428     mx2.expandMacrosShellQuote(exec);   // syntax was already checked, so don't check return value
00429 
00430     /*
00431      1 = need_shell, 2 = terminal, 4 = su
00432 
00433      0                                                           << split(cmd)
00434      1                                                           << "sh" << "-c" << cmd
00435      2 << split(term) << "-e"                                    << split(cmd)
00436      3 << split(term) << "-e"                                    << "sh" << "-c" << cmd
00437 
00438      4                        << "kdesu" << "-u" << user << "-c" << cmd
00439      5                        << "kdesu" << "-u" << user << "-c" << ("sh -c " + quote(cmd))
00440      6 << split(term) << "-e" << "su"            << user << "-c" << cmd
00441      7 << split(term) << "-e" << "su"            << user << "-c" << ("sh -c " + quote(cmd))
00442 
00443      "sh -c" is needed in the "su" case, too, as su uses the user's login shell, not sh.
00444      this could be optimized with the -s switch of some su versions (e.g., debian linux).
00445     */
00446 
00447     if (_service.terminal()) {
00448         KConfigGroup cg(KGlobal::config(), "General");
00449         QString terminal = cg.readPathEntry("TerminalApplication", "konsole");
00450         if (terminal == "konsole") {
00451             if (!_service.path().isEmpty()) {
00452                 terminal += " --workdir " + KShell::quoteArg(_service.path());
00453             }
00454             terminal += " -caption=%c %i %m";
00455         }
00456         terminal += ' ';
00457         terminal += _service.terminalOptions();
00458         if (!mx1.expandMacrosShellQuote(terminal)) {
00459             kWarning() << "KRun: syntax error in command" << terminal << ", service" << _service.name();
00460             return QStringList();
00461         }
00462         mx2.expandMacrosShellQuote(terminal);
00463         result = KShell::splitArgs(terminal);   // assuming that the term spec never needs a shell!
00464         result << "-e";
00465     }
00466 
00467     KShell::Errors err;
00468     QStringList execlist = KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
00469     if (err == KShell::NoError && !execlist.isEmpty()) { // mx1 checked for syntax errors already
00470         // Resolve the executable to ensure that helpers in lib/kde4/libexec/ are found.
00471         // Too bad for commands that need a shell - they must reside in $PATH.
00472         const QString exePath = KStandardDirs::findExe(execlist[0]);
00473         if (!exePath.isEmpty()) {
00474             execlist[0] = exePath;
00475         }
00476     }
00477     if (_service.substituteUid()) {
00478         if (_service.terminal()) {
00479             result << "su";
00480         }
00481         else {
00482             result << KStandardDirs::findExe("kdesu") << "-u";
00483         }
00484 
00485         result << _service.username() << "-c";
00486         if (err == KShell::FoundMeta) {
00487             exec = "/bin/sh -c " + KShell::quoteArg(exec);
00488         }
00489         else {
00490             exec = KShell::joinArgs(execlist);
00491         }
00492         result << exec;
00493     }
00494     else {
00495         if (err == KShell::FoundMeta) {
00496             result << "/bin/sh" << "-c" << exec;
00497         }
00498         else {
00499             result += execlist;
00500         }
00501     }
00502 
00503     return result;
00504 }
00505 
00506 //static
00507 QString KRun::binaryName(const QString & execLine, bool removePath)
00508 {
00509     // Remove parameters and/or trailing spaces.
00510     const QStringList args = KShell::splitArgs(execLine);
00511     for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it)
00512         if (!(*it).contains('=')) {
00513             // Remove path if wanted
00514             return removePath ? (*it).mid((*it).lastIndexOf('/') + 1) : *it;
00515         }
00516     return QString();
00517 }
00518 
00519 static bool runCommandInternal(KProcess* proc, const KService* service, const QString& executable,
00520                                const QString &userVisibleName, const QString & iconName, QWidget* window,
00521                                const QByteArray& asn)
00522 {
00523     if (window != NULL) {
00524         window = window->topLevelWidget();
00525     }
00526     if (service && !service->entryPath().isEmpty()
00527             && !KDesktopFile::isAuthorizedDesktopFile(service->entryPath()))
00528     {
00529         kWarning() << "No authorization to execute " << service->entryPath();
00530         KMessageBox::sorry(window, i18n("You are not authorized to execute this file."));
00531         delete proc;
00532         return false;
00533     }
00534 
00535     QString bin = KRun::binaryName(executable, true);
00536 #ifdef Q_WS_X11 // Startup notification doesn't work with QT/E, service isn't needed without Startup notification
00537     bool silent;
00538     QByteArray wmclass;
00539     KStartupInfoId id;
00540     bool startup_notify = (asn != "0" && KRun::checkStartupNotify(QString() /*unused*/, service, &silent, &wmclass));
00541     if (startup_notify) {
00542         id.initId(asn);
00543         id.setupStartupEnv();
00544         KStartupInfoData data;
00545         data.setHostname();
00546         data.setBin(bin);
00547         if (!userVisibleName.isEmpty()) {
00548             data.setName(userVisibleName);
00549         }
00550         else if (service && !service->name().isEmpty()) {
00551             data.setName(service->name());
00552         }
00553         data.setDescription(i18n("Launching %1" ,  data.name()));
00554         if (!iconName.isEmpty()) {
00555             data.setIcon(iconName);
00556         }
00557         else if (service && !service->icon().isEmpty()) {
00558             data.setIcon(service->icon());
00559         }
00560         if (!wmclass.isEmpty()) {
00561             data.setWMClass(wmclass);
00562         }
00563         if (silent) {
00564             data.setSilent(KStartupInfoData::Yes);
00565         }
00566         data.setDesktop(KWindowSystem::currentDesktop());
00567         if (window) {
00568             data.setLaunchedBy(window->winId());
00569         }
00570         KStartupInfo::sendStartup(id, data);
00571     }
00572     int pid = KProcessRunner::run(proc, executable, id);
00573     if (startup_notify && pid) {
00574         KStartupInfoData data;
00575         data.addPid(pid);
00576         KStartupInfo::sendChange(id, data);
00577         KStartupInfo::resetStartupEnv();
00578     }
00579     return pid != 0;
00580 #else
00581     Q_UNUSED(userVisibleName);
00582     Q_UNUSED(iconName);
00583     return KProcessRunner::run(proc, bin) != 0;
00584 #endif
00585 }
00586 
00587 // This code is also used in klauncher.
00588 bool KRun::checkStartupNotify(const QString& /*binName*/, const KService* service, bool* silent_arg, QByteArray* wmclass_arg)
00589 {
00590     bool silent = false;
00591     QByteArray wmclass;
00592     if (service && service->property("StartupNotify").isValid()) {
00593         silent = !service->property("StartupNotify").toBool();
00594         wmclass = service->property("StartupWMClass").toString().toLatin1();
00595     }
00596     else if (service && service->property("X-KDE-StartupNotify").isValid()) {
00597         silent = !service->property("X-KDE-StartupNotify").toBool();
00598         wmclass = service->property("X-KDE-WMClass").toString().toLatin1();
00599     }
00600     else { // non-compliant app
00601         if (service) {
00602             if (service->isApplication()) {
00603                 wmclass = "0"; // doesn't have .desktop entries needed, start as non-compliant
00604             }
00605             else {
00606                 return false; // no startup notification at all
00607             }
00608         }
00609         else {
00610 #if 0
00611             // Create startup notification even for apps for which there shouldn't be any,
00612             // just without any visual feedback. This will ensure they'll be positioned on the proper
00613             // virtual desktop, and will get user timestamp from the ASN ID.
00614             wmclass = "0";
00615             silent = true;
00616 #else   // That unfortunately doesn't work, when the launched non-compliant application
00617             // launches another one that is compliant and there is any delay inbetween (bnc:#343359)
00618             return false;
00619 #endif
00620         }
00621     }
00622     if (silent_arg != NULL) {
00623         *silent_arg = silent;
00624     }
00625     if (wmclass_arg != NULL) {
00626         *wmclass_arg = wmclass;
00627     }
00628     return true;
00629 }
00630 
00631 static bool runTempService(const KService& _service, const KUrl::List& _urls, QWidget* window,
00632                            bool tempFiles, const QString& suggestedFileName, const QByteArray& asn)
00633 {
00634     if (!_urls.isEmpty()) {
00635         kDebug(7010) << "runTempService: first url " << _urls.first().url();
00636     }
00637 
00638     QStringList args;
00639     if ((_urls.count() > 1) && !_service.allowMultipleFiles()) {
00640         // We need to launch the application N times. That sucks.
00641         // We ignore the result for application 2 to N.
00642         // For the first file we launch the application in the
00643         // usual way. The reported result is based on this
00644         // application.
00645         KUrl::List::ConstIterator it = _urls.begin();
00646         while (++it != _urls.end()) {
00647             KUrl::List singleUrl;
00648             singleUrl.append(*it);
00649             runTempService(_service, singleUrl, window, tempFiles, suggestedFileName, QByteArray());
00650         }
00651         KUrl::List singleUrl;
00652         singleUrl.append(_urls.first());
00653         args = KRun::processDesktopExec(_service, singleUrl, tempFiles, suggestedFileName);
00654     }
00655     else {
00656         args = KRun::processDesktopExec(_service, _urls, tempFiles, suggestedFileName);
00657     }
00658     if (args.isEmpty()) {
00659         KMessageBox::sorry(window, i18n("Error processing Exec field in %1", _service.entryPath()));
00660         return false;
00661     }
00662     kDebug(7010) << "runTempService: KProcess args=" << args;
00663 
00664     KProcess * proc = new KProcess;
00665     *proc << args;
00666 
00667     if (!_service.path().isEmpty()) {
00668         proc->setWorkingDirectory(_service.path());
00669     }
00670 
00671     return runCommandInternal(proc, &_service, KRun::binaryName(_service.exec(), false),
00672                               _service.name(), _service.icon(), window, asn);
00673 }
00674 
00675 // WARNING: don't call this from processDesktopExec, since klauncher uses that too...
00676 static KUrl::List resolveURLs(const KUrl::List& _urls, const KService& _service)
00677 {
00678     // Check which protocols the application supports.
00679     // This can be a list of actual protocol names, or just KIO for KDE apps.
00680     QStringList supportedProtocols = _service.property("X-KDE-Protocols").toStringList();
00681     KRunMX1 mx1(_service);
00682     QString exec = _service.exec();
00683     if (mx1.expandMacrosShellQuote(exec) && !mx1.hasUrls) {
00684         Q_ASSERT(supportedProtocols.isEmpty());   // huh? If you support protocols you need %u or %U...
00685     }
00686     else {
00687         if (supportedProtocols.isEmpty()) {
00688             // compat mode: assume KIO if not set and it's a KDE app
00689             QStringList categories = _service.property("Categories").toStringList();
00690             if (categories.contains("KDE")) {
00691                 supportedProtocols.append("KIO");
00692             }
00693             else { // if no KDE app, be a bit over-generic
00694                 supportedProtocols.append("http");
00695                 supportedProtocols.append("ftp");
00696             }
00697         }
00698     }
00699     kDebug(7010) << "supportedProtocols:" << supportedProtocols;
00700 
00701     KUrl::List urls(_urls);
00702     if (!supportedProtocols.contains("KIO")) {
00703         for (KUrl::List::Iterator it = urls.begin(); it != urls.end(); ++it) {
00704             const KUrl url = *it;
00705             bool supported = url.isLocalFile() || supportedProtocols.contains(url.protocol().toLower());
00706             kDebug(7010) << "Looking at url=" << url << " supported=" << supported;
00707             if (!supported && KProtocolInfo::protocolClass(url.protocol()) == ":local") {
00708                 // Maybe we can resolve to a local URL?
00709                 KUrl localURL = KIO::NetAccess::mostLocalUrl(url, 0);
00710                 if (localURL != url) {
00711                     *it = localURL;
00712                     kDebug(7010) << "Changed to " << localURL;
00713                 }
00714             }
00715         }
00716     }
00717     return urls;
00718 }
00719 
00720 // Simple KDialog that resizes the given text edit after being shown to more
00721 // or less fit the enclosed text.
00722 class SecureMessageDialog : public KDialog
00723 {
00724     public:
00725     SecureMessageDialog(QWidget *parent) : KDialog(parent), m_textEdit(0)
00726     {
00727     }
00728 
00729     void setTextEdit(QPlainTextEdit *textEdit)
00730     {
00731         m_textEdit = textEdit;
00732     }
00733 
00734     protected:
00735     virtual void showEvent(QShowEvent* e)
00736     {
00737         // Now that we're shown, use our width to calculate a good
00738         // bounding box for the text, and resize m_textEdit appropriately.
00739         KDialog::showEvent(e);
00740 
00741         if(!m_textEdit)
00742             return;
00743 
00744         QSize fudge(20, 24); // About what it sounds like :-/
00745 
00746         // Form rect with a lot of height for bounding.  Use no more than
00747         // 5 lines.
00748         QRect curRect(m_textEdit->rect());
00749         QFontMetrics metrics(fontMetrics());
00750         curRect.setHeight(5 * metrics.lineSpacing());
00751         curRect.setWidth(qMax(curRect.width(), 300)); // At least 300 pixels ok?
00752 
00753         QString text(m_textEdit->toPlainText());
00754         curRect = metrics.boundingRect(curRect, Qt::TextWordWrap | Qt::TextSingleLine, text);
00755 
00756         // Scroll bars interfere.  If we don't think there's enough room, enable
00757         // the vertical scrollbar however.
00758         m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
00759         if(curRect.height() < m_textEdit->height()) { // then we've got room
00760             m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
00761             m_textEdit->setMaximumHeight(curRect.height() + fudge.height());
00762         }
00763 
00764         m_textEdit->setMinimumSize(curRect.size() + fudge);
00765         m_textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
00766         updateGeometry();
00767     }
00768 
00769     private:
00770     QPlainTextEdit *m_textEdit;
00771 };
00772 
00773 // Helper function to make the given .desktop file executable by ensuring
00774 // that a #!/usr/bin/env xdg-open line is added if necessary and the file has
00775 // the +x bit set for the user.  Returns false if either fails.
00776 static bool makeFileExecutable(const QString &fileName)
00777 {
00778     // Open the file and read the first two characters, check if it's
00779     // #!.  If not, create a new file, prepend appropriate lines, and copy
00780     // over.
00781     QFile desktopFile(fileName);
00782     if (!desktopFile.open(QFile::ReadOnly)) {
00783         kError(7010) << "Error opening service" << fileName << desktopFile.errorString();
00784         return false;
00785     }
00786 
00787     QByteArray header = desktopFile.peek(2);   // First two chars of file
00788     if (header.size() == 0) {
00789         kError(7010) << "Error inspecting service" << fileName << desktopFile.errorString();
00790         return false; // Some kind of error
00791     }
00792 
00793     if (header != "#!") {
00794         // Add header
00795         KSaveFile saveFile;
00796         saveFile.setFileName(fileName);
00797         if (!saveFile.open()) {
00798             kError(7010) << "Unable to open replacement file for" << fileName << saveFile.errorString();
00799             return false;
00800         }
00801 
00802         QByteArray shebang("#!/usr/bin/env xdg-open\n");
00803         if (saveFile.write(shebang) != shebang.size()) {
00804             kError(7010) << "Error occurred adding header for" << fileName << saveFile.errorString();
00805             saveFile.abort();
00806             return false;
00807         }
00808 
00809         // Now copy the one into the other and then close and reopen desktopFile
00810         QByteArray desktopData(desktopFile.readAll());
00811         if (desktopData.isEmpty()) {
00812             kError(7010) << "Unable to read service" << fileName << desktopFile.errorString();
00813             saveFile.abort();
00814             return false;
00815         }
00816 
00817         if (saveFile.write(desktopData) != desktopData.size()) {
00818             kError(7010) << "Error copying service" << fileName << saveFile.errorString();
00819             saveFile.abort();
00820             return false;
00821         }
00822 
00823         desktopFile.close();
00824         if (!saveFile.finalize()) { // Figures....
00825             kError(7010) << "Error committing changes to service" << fileName << saveFile.errorString();
00826             return false;
00827         }
00828 
00829         if (!desktopFile.open(QFile::ReadOnly)) {
00830             kError(7010) << "Error re-opening service" << fileName << desktopFile.errorString();
00831             return false;
00832         }
00833     } // Add header
00834 
00835     // corresponds to owner on unix, which will have to do since if the user
00836     // isn't the owner we can't change perms anyways.
00837     if (!desktopFile.setPermissions(QFile::ExeUser | desktopFile.permissions())) {
00838         kError(7010) << "Unable to change permissions for" << fileName << desktopFile.errorString();
00839         return false;
00840     }
00841 
00842     // whew
00843     return true;
00844 }
00845 
00846 // Helper function to make a .desktop file executable if prompted by the user.
00847 // returns true if KRun::run() should continue with execution, false if user declined
00848 // to make the file executable or we failed to make it executable.
00849 static bool makeServiceExecutable(const KService& service, QWidget* window)
00850 {
00851     if (!KAuthorized::authorize("run_desktop_files")) {
00852         kWarning() << "No authorization to execute " << service.entryPath();
00853         KMessageBox::sorry(window, i18n("You are not authorized to execute this service."));
00854         return false; // Don't circumvent the Kiosk
00855     }
00856 
00857     KGuiItem continueItem = KStandardGuiItem::cont();
00858 
00859     SecureMessageDialog *baseDialog = new SecureMessageDialog(window);
00860 
00861     baseDialog->setButtons(KDialog::Ok | KDialog::Cancel);
00862     baseDialog->setButtonGuiItem(KDialog::Ok, continueItem);
00863     baseDialog->setDefaultButton(KDialog::Cancel);
00864     baseDialog->setButtonFocus(KDialog::Cancel);
00865     baseDialog->setCaption(i18nc("Warning about executing unknown .desktop file", "Warning"));
00866 
00867     // Dialog will have explanatory text with a disabled lineedit with the
00868     // Exec= to make it visually distinct.
00869     QWidget *baseWidget = new QWidget(baseDialog);
00870     QHBoxLayout *mainLayout = new QHBoxLayout(baseWidget);
00871 
00872     QLabel *iconLabel = new QLabel(baseWidget);
00873     QPixmap warningIcon(KIconLoader::global()->loadIcon("dialog-warning", KIconLoader::NoGroup, KIconLoader::SizeHuge));
00874     mainLayout->addWidget(iconLabel);
00875     iconLabel->setPixmap(warningIcon);
00876 
00877     QVBoxLayout *contentLayout = new QVBoxLayout;
00878     QString warningMessage = i18nc("program name follows in a line edit below",
00879                                    "This will start the program:");
00880 
00881     QLabel *message = new QLabel(warningMessage, baseWidget);
00882     contentLayout->addWidget(message);
00883 
00884     // We can use KStandardDirs::findExe to resolve relative pathnames
00885     // but that gets rid of the command line arguments.
00886     QString program = KStandardDirs::realFilePath(service.exec());
00887 
00888     QPlainTextEdit *textEdit = new QPlainTextEdit(baseWidget);
00889     textEdit->setPlainText(program);
00890     textEdit->setReadOnly(true);
00891     contentLayout->addWidget(textEdit);
00892 
00893     QLabel *footerLabel = new QLabel(i18n("If you do not trust this program, click Cancel"));
00894     contentLayout->addWidget(footerLabel);
00895     contentLayout->addStretch(0); // Don't allow the text edit to expand
00896 
00897     mainLayout->addLayout(contentLayout);
00898 
00899     baseDialog->setMainWidget(baseWidget);
00900     baseDialog->setTextEdit(textEdit);
00901 
00902     // Constrain maximum size.  Minimum size set in
00903     // the dialog's show event.
00904     QSize screenSize = QApplication::desktop()->screen()->size();
00905     baseDialog->resize(screenSize.width() / 4, 50);
00906     baseDialog->setMaximumHeight(screenSize.height() / 3);
00907     baseDialog->setMaximumWidth(screenSize.width() / 10 * 8);
00908 
00909     int result = baseDialog->exec();
00910     if (result != KDialog::Accepted) {
00911         return false;
00912     }
00913 
00914     // Assume that service is an absolute path since we're being called (relative paths
00915     // would have been allowed unless Kiosk said no, therefore we already know where the
00916     // .desktop file is.  Now add a header to it if it doesn't already have one
00917     // and add the +x bit.
00918 
00919     if (!::makeFileExecutable(service.entryPath())) {
00920         QString serviceName = service.name();
00921         if(serviceName.isEmpty())
00922             serviceName = service.genericName();
00923 
00924         KMessageBox::sorry(
00925             window,
00926             i18n("Unable to make the service %1 executable, aborting execution", serviceName)
00927         );
00928 
00929         return false;
00930     }
00931 
00932     return true;
00933 }
00934 
00935 bool KRun::run(const KService& _service, const KUrl::List& _urls, QWidget* window,
00936                bool tempFiles, const QString& suggestedFileName, const QByteArray& asn)
00937 {
00938     if (!_service.entryPath().isEmpty() &&
00939             !KDesktopFile::isAuthorizedDesktopFile(_service.entryPath()) &&
00940             !::makeServiceExecutable(_service, window))
00941     {
00942         return false;
00943     }
00944 
00945     if (!tempFiles) {
00946         // Remember we opened those urls, for the "recent documents" menu in kicker
00947         KUrl::List::ConstIterator it = _urls.begin();
00948         for (; it != _urls.end(); ++it) {
00949             //kDebug(7010) << "KRecentDocument::adding " << (*it).url();
00950             KRecentDocument::add(*it, _service.desktopEntryName());
00951         }
00952     }
00953 
00954     if (tempFiles || _service.entryPath().isEmpty() || !suggestedFileName.isEmpty()) {
00955         return runTempService(_service, _urls, window, tempFiles, suggestedFileName, asn);
00956     }
00957 
00958     kDebug(7010) << "KRun::run " << _service.entryPath();
00959 
00960     if (!_urls.isEmpty()) {
00961         kDebug(7010) << "First url " << _urls.first().url();
00962     }
00963 
00964     // Resolve urls if needed, depending on what the app supports
00965     const KUrl::List urls = resolveURLs(_urls, _service);
00966 
00967     QString error;
00968     int pid = 0;
00969 
00970     QByteArray myasn = asn;
00971     // startServiceByDesktopPath() doesn't take QWidget*, add it to the startup info now
00972     if (window != NULL) {
00973         if (myasn.isEmpty()) {
00974             myasn = KStartupInfo::createNewStartupId();
00975         }
00976         if (myasn != "0") {
00977             KStartupInfoId id;
00978             id.initId(myasn);
00979             KStartupInfoData data;
00980             data.setLaunchedBy(window->winId());
00981             KStartupInfo::sendChange(id, data);
00982         }
00983     }
00984 
00985     int i = KToolInvocation::startServiceByDesktopPath(
00986                 _service.entryPath(), urls.toStringList(), &error, 0L, &pid, myasn
00987             );
00988 
00989     if (i != 0) {
00990         kDebug(7010) << error;
00991         KMessageBox::sorry(window, error);
00992         return false;
00993     }
00994 
00995     kDebug(7010) << "startServiceByDesktopPath worked fine";
00996     return true;
00997 }
00998 
00999 
01000 bool KRun::run(const QString& _exec, const KUrl::List& _urls, QWidget* window, const QString& _name,
01001                const QString& _icon, const QByteArray& asn)
01002 {
01003     KService::Ptr service(new KService(_name, _exec, _icon));
01004 
01005     return run(*service, _urls, window, false, QString(), asn);
01006 }
01007 
01008 bool KRun::runCommand(const QString &cmd, QWidget* window)
01009 {
01010     if (cmd.isEmpty()) {
01011         kWarning() << "Command was empty, nothing to run";
01012         return false;
01013     }
01014     const QString bin = KShell::splitArgs(cmd).first();
01015     return KRun::runCommand(cmd, bin, bin /*iconName*/, window, QByteArray());
01016 }
01017 
01018 bool KRun::runCommand(const QString& cmd, const QString &execName, const QString & iconName, QWidget* window, const QByteArray& asn)
01019 {
01020     kDebug(7010) << "runCommand " << cmd << "," << execName;
01021     KProcess * proc = new KProcess;
01022     proc->setShellCommand(cmd);
01023     QString bin = binaryName(execName, true);
01024     KService::Ptr service = KService::serviceByDesktopName(bin);
01025     return runCommandInternal(proc, service.data(),
01026                               execName /*executable to check for in slotProcessExited*/,
01027                               execName /*user-visible name*/,
01028                               iconName, window, asn);
01029 }
01030 
01031 KRun::KRun(const KUrl& url, QWidget* window, mode_t mode, bool isLocalFile,
01032            bool showProgressInfo, const QByteArray& asn)
01033         : d(new KRunPrivate(this))
01034 {
01035     d->m_timer.setObjectName("KRun::timer");
01036     d->m_timer.setSingleShot(true);
01037     d->init(url, window, mode, isLocalFile, showProgressInfo, asn);
01038 }
01039 
01040 void KRun::KRunPrivate::init(const KUrl& url, QWidget* window, mode_t mode, bool isLocalFile,
01041                              bool showProgressInfo, const QByteArray& asn)
01042 {
01043     m_bFault = false;
01044     m_bAutoDelete = true;
01045     m_bProgressInfo = showProgressInfo;
01046     m_bFinished = false;
01047     m_job = 0L;
01048     m_strURL = url;
01049     m_bScanFile = false;
01050     m_bIsDirectory = false;
01051     m_bIsLocalFile = isLocalFile;
01052     m_mode = mode;
01053     m_runExecutables = true;
01054     m_window = window;
01055     m_asn = asn;
01056     q->setEnableExternalBrowser(true);
01057 
01058     // Start the timer. This means we will return to the event
01059     // loop and do initialization afterwards.
01060     // Reason: We must complete the constructor before we do anything else.
01061     m_bInit = true;
01062     q->connect(&m_timer, SIGNAL(timeout()), q, SLOT(slotTimeout()));
01063     startTimer();
01064     //kDebug(7010) << "new KRun" << q << url << "timer=" << &m_timer;
01065 
01066     KGlobal::ref();
01067 }
01068 
01069 void KRun::init()
01070 {
01071     kDebug(7010) << "INIT called";
01072     if (!d->m_strURL.isValid()) {
01073         // TODO KDE5: call virtual method on error (see BrowserRun::init)
01074         d->m_showingDialog = true;
01075         KMessageBoxWrapper::error(d->m_window, i18n("Malformed URL\n%1", d->m_strURL.url()));
01076         d->m_showingDialog = false;
01077         d->m_bFault = true;
01078         d->m_bFinished = true;
01079         d->startTimer();
01080         return;
01081     }
01082     if (!KAuthorized::authorizeUrlAction("open", KUrl(), d->m_strURL)) {
01083         QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->m_strURL.prettyUrl());
01084         d->m_showingDialog = true;
01085         KMessageBoxWrapper::error(d->m_window, msg);
01086         d->m_showingDialog = false;
01087         d->m_bFault = true;
01088         d->m_bFinished = true;
01089         d->startTimer();
01090         return;
01091     }
01092 
01093     if (!d->m_bIsLocalFile && d->m_strURL.isLocalFile()) {
01094         d->m_bIsLocalFile = true;
01095     }
01096 
01097     QString exec;
01098     if (d->m_strURL.protocol().startsWith("http")) {
01099         exec = d->m_externalBrowser;
01100     }
01101 
01102     if (d->m_bIsLocalFile) {
01103         if (d->m_mode == 0) {
01104             KDE_struct_stat buff;
01105             if (KDE::stat(d->m_strURL.path(), &buff) == -1) {
01106                 d->m_showingDialog = true;
01107                 KMessageBoxWrapper::error(d->m_window,
01108                                           i18n("<qt>Unable to run the command specified. "
01109                                           "The file or folder <b>%1</b> does not exist.</qt>" ,
01110                                           Qt::escape(d->m_strURL.prettyUrl())));
01111                 d->m_showingDialog = false;
01112                 d->m_bFault = true;
01113                 d->m_bFinished = true;
01114                 d->startTimer();
01115                 return;
01116             }
01117             d->m_mode = buff.st_mode;
01118         }
01119 
01120         KMimeType::Ptr mime = KMimeType::findByUrl(d->m_strURL, d->m_mode, d->m_bIsLocalFile);
01121         assert(mime);
01122         kDebug(7010) << "MIME TYPE is " << mime->name();
01123         mimeTypeDetermined(mime->name());
01124         return;
01125     }
01126     else if (!exec.isEmpty() || KProtocolInfo::isHelperProtocol(d->m_strURL)) {
01127         kDebug(7010) << "Helper protocol";
01128 
01129         bool ok = false;
01130         KUrl::List urls;
01131         urls.append(d->m_strURL);
01132         if (exec.isEmpty()) {
01133             exec = KProtocolInfo::exec(d->m_strURL.protocol());
01134             if (exec.isEmpty()) {
01135                 mimeTypeDetermined(KProtocolManager::defaultMimetype(d->m_strURL));
01136                 return;
01137             }
01138             run(exec, urls, d->m_window, false, QString(), d->m_asn);
01139             ok = true;
01140         }
01141         else if (exec.startsWith('!')) {
01142             exec = exec.mid(1); // Literal command
01143             exec += " %u";
01144             run(exec, urls, d->m_window, false, QString(), d->m_asn);
01145             ok = true;
01146         }
01147         else {
01148             KService::Ptr service = KService::serviceByStorageId(exec);
01149             if (service) {
01150                 run(*service, urls, d->m_window, false, QString(), d->m_asn);
01151                 ok = true;
01152             }
01153         }
01154 
01155         if (ok) {
01156             d->m_bFinished = true;
01157             // will emit the error and autodelete this
01158             d->startTimer();
01159             return;
01160         }
01161     }
01162 
01163     // Did we already get the information that it is a directory ?
01164     if (S_ISDIR(d->m_mode)) {
01165         mimeTypeDetermined("inode/directory");
01166         return;
01167     }
01168 
01169     // Let's see whether it is a directory
01170 
01171     if (!KProtocolManager::supportsListing(d->m_strURL)) {
01172         //kDebug(7010) << "Protocol has no support for listing";
01173         // No support for listing => it can't be a directory (example: http)
01174         scanFile();
01175         return;
01176     }
01177 
01178     kDebug(7010) << "Testing directory (stating)";
01179 
01180     // It may be a directory or a file, let's stat
01181     KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
01182     KIO::StatJob *job = KIO::stat(d->m_strURL, KIO::StatJob::SourceSide, 0 /* no details */, flags);
01183     job->ui()->setWindow(d->m_window);
01184     connect(job, SIGNAL(result(KJob *)),
01185             this, SLOT(slotStatResult(KJob *)));
01186     d->m_job = job;
01187     kDebug(7010) << " Job " << job << " is about stating " << d->m_strURL.url();
01188 }
01189 
01190 KRun::~KRun()
01191 {
01192     //kDebug(7010) << this;
01193     d->m_timer.stop();
01194     killJob();
01195     KGlobal::deref();
01196     //kDebug(7010) << this << "done";
01197     delete d;
01198 }
01199 
01200 void KRun::scanFile()
01201 {
01202     kDebug(7010) << d->m_strURL;
01203     // First, let's check for well-known extensions
01204     // Not when there is a query in the URL, in any case.
01205     if (d->m_strURL.query().isEmpty()) {
01206         KMimeType::Ptr mime = KMimeType::findByUrl(d->m_strURL);
01207         assert(mime);
01208         if (mime->name() != "application/octet-stream" || d->m_bIsLocalFile) {
01209             kDebug(7010) << "Scanfile: MIME TYPE is " << mime->name();
01210             mimeTypeDetermined(mime->name());
01211             return;
01212         }
01213     }
01214 
01215     // No mimetype found, and the URL is not local  (or fast mode not allowed).
01216     // We need to apply the 'KIO' method, i.e. either asking the server or
01217     // getting some data out of the file, to know what mimetype it is.
01218 
01219     if (!KProtocolManager::supportsReading(d->m_strURL)) {
01220         kError(7010) << "#### NO SUPPORT FOR READING!";
01221         d->m_bFault = true;
01222         d->m_bFinished = true;
01223         d->startTimer();
01224         return;
01225     }
01226     kDebug(7010) << this << " Scanning file " << d->m_strURL.url();
01227 
01228     KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
01229     KIO::TransferJob *job = KIO::get(d->m_strURL, KIO::NoReload /*reload*/, flags);
01230     job->ui()->setWindow(d->m_window);
01231     connect(job, SIGNAL(result(KJob *)),
01232             this, SLOT(slotScanFinished(KJob *)));
01233     connect(job, SIGNAL(mimetype(KIO::Job *, const QString &)),
01234             this, SLOT(slotScanMimeType(KIO::Job *, const QString &)));
01235     d->m_job = job;
01236     kDebug(7010) << " Job " << job << " is about getting from " << d->m_strURL.url();
01237 }
01238 
01239 void KRun::slotTimeout()
01240 {
01241     kDebug(7010) << this << " slotTimeout called";
01242     if (d->m_bInit) {
01243         d->m_bInit = false;
01244         init();
01245         return;
01246     }
01247 
01248     if (d->m_bFault) {
01249         emit error();
01250     }
01251     if (d->m_bFinished) {
01252         emit finished();
01253     }
01254     else {
01255         if (d->m_bScanFile) {
01256             d->m_bScanFile = false;
01257             scanFile();
01258             return;
01259         }
01260         else if (d->m_bIsDirectory) {
01261             d->m_bIsDirectory = false;
01262             mimeTypeDetermined("inode/directory");
01263             return;
01264         }
01265     }
01266 
01267     if (d->m_bAutoDelete) {
01268         deleteLater();
01269         return;
01270     }
01271 }
01272 
01273 void KRun::slotStatResult(KJob * job)
01274 {
01275     d->m_job = 0L;
01276     if (job->error()) {
01277         d->m_showingDialog = true;
01278         kError(7010) << this << "ERROR" << job->error() << ' ' << job->errorString();
01279         job->uiDelegate()->showErrorMessage();
01280         //kDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us";
01281         d->m_showingDialog = false;
01282 
01283         d->m_bFault = true;
01284         d->m_bFinished = true;
01285 
01286         // will emit the error and autodelete this
01287         d->startTimer();
01288 
01289     }
01290     else {
01291 
01292         kDebug(7010) << "Finished";
01293         if (!qobject_cast<KIO::StatJob*>(job)) {
01294             kFatal() << "job is a " << typeid(*job).name() << " should be a StatJob";
01295         }
01296 
01297         const KIO::UDSEntry entry = ((KIO::StatJob*)job)->statResult();
01298         const mode_t mode = entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE);
01299         if (S_ISDIR(mode)) {
01300             d->m_bIsDirectory = true; // it's a dir
01301         }
01302         else {
01303             d->m_bScanFile = true; // it's a file
01304         }
01305 
01306         d->m_localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
01307 
01308         // mimetype already known? (e.g. print:/manager)
01309         const QString knownMimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE) ;
01310 
01311         if (!knownMimeType.isEmpty()) {
01312             mimeTypeDetermined(knownMimeType);
01313             d->m_bFinished = true;
01314         }
01315 
01316         // We should have found something
01317         assert(d->m_bScanFile || d->m_bIsDirectory);
01318 
01319         // Start the timer. Once we get the timer event this
01320         // protocol server is back in the pool and we can reuse it.
01321         // This gives better performance than starting a new slave
01322         d->startTimer();
01323     }
01324 }
01325 
01326 void KRun::slotScanMimeType(KIO::Job *, const QString &mimetype)
01327 {
01328     if (mimetype.isEmpty()) {
01329         kWarning(7010) << "get() didn't emit a mimetype! Probably a kioslave bug, please check the implementation of" << url().protocol();
01330     }
01331     mimeTypeDetermined(mimetype);
01332     d->m_job = 0;
01333 }
01334 
01335 void KRun::slotScanFinished(KJob *job)
01336 {
01337     d->m_job = 0;
01338     if (job->error()) {
01339         d->m_showingDialog = true;
01340         kError(7010) << this << "ERROR (stat):" << job->error() << ' ' << job->errorString();
01341         job->uiDelegate()->showErrorMessage();
01342         //kDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us";
01343         d->m_showingDialog = false;
01344 
01345         d->m_bFault = true;
01346         d->m_bFinished = true;
01347 
01348         // will emit the error and autodelete this
01349         d->startTimer();
01350     }
01351 }
01352 
01353 void KRun::mimeTypeDetermined(const QString& mimeType)
01354 {
01355     // foundMimeType reimplementations might show a dialog box;
01356     // make sure some timer doesn't kill us meanwhile (#137678, #156447)
01357     Q_ASSERT(!d->m_showingDialog);
01358     d->m_showingDialog = true;
01359 
01360     foundMimeType(mimeType);
01361 
01362     d->m_showingDialog = false;
01363 }
01364 
01365 void KRun::foundMimeType(const QString& type)
01366 {
01367     kDebug(7010) << "Resulting mime type is " << type;
01368 
01369     /*
01370       // Automatically unzip stuff
01371 
01372       // Disabled since the new KIO doesn't have filters yet.
01373 
01374       if ( type == "application/x-gzip"  ||
01375            type == "application/x-bzip"  ||
01376            type == "application/x-bzip"  ||
01377            type == "application/x-lzma"  ||
01378            type == "application/x-xz" )
01379       {
01380         KUrl::List lst = KUrl::split( m_strURL );
01381         if ( lst.isEmpty() )
01382         {
01383           QString tmp = i18n( "Malformed URL" );
01384           tmp += "\n";
01385           tmp += m_strURL.url();
01386           KMessageBoxWrapper::error( 0L, tmp );
01387           return;
01388         }
01389 
01390         if ( type == "application/x-gzip" )
01391           lst.prepend( KUrl( "gzip:/decompress" ) );
01392         else if ( type == "application/x-bzip" )
01393           lst.prepend( KUrl( "bzip:/decompress" ) );
01394         else if ( type == "application/x-bzip" )
01395           lst.prepend( KUrl( "bzip2:/decompress" ) );
01396         else if ( type == "application/x-lzma" )
01397           lst.prepend( KUrl( "lzma:/decompress" ) );
01398         else if ( type == "application/x-xz" )
01399           lst.prepend( KUrl( "xz:/decompress" ) );
01400         else if ( type == "application/x-tar" )
01401           lst.prepend( KUrl( "tar:/" ) );
01402 
01403         // Move the HTML style reference to the leftmost URL
01404         KUrl::List::Iterator it = lst.begin();
01405         ++it;
01406         (*lst.begin()).setRef( (*it).ref() );
01407         (*it).setRef( QString() );
01408 
01409         // Create the new URL
01410         m_strURL = KUrl::join( lst );
01411 
01412         kDebug(7010) << "Now trying with " << debugString(m_strURL.url());
01413 
01414         killJob();
01415 
01416         // We don't know if this is a file or a directory. Let's test this first.
01417         // (For instance a tar.gz is a directory contained inside a file)
01418         // It may be a directory or a file, let's stat
01419         KIO::StatJob *job = KIO::stat( m_strURL, m_bProgressInfo );
01420         connect( job, SIGNAL( result( KJob * ) ),
01421                  this, SLOT( slotStatResult( KJob * ) ) );
01422         m_job = job;
01423 
01424         return;
01425       }
01426     */
01427     KIO::TransferJob *job = qobject_cast<KIO::TransferJob *>(d->m_job);
01428     if (job) {
01429         job->putOnHold();
01430         KIO::Scheduler::publishSlaveOnHold();
01431         d->m_job = 0;
01432     }
01433 
01434     Q_ASSERT(!d->m_bFinished);
01435 
01436     KMimeType::Ptr mime = KMimeType::mimeType(type, KMimeType::ResolveAliases);
01437     if (!mime) {
01438         kWarning(7010) << "Unknown mimetype " << type;
01439     }
01440 
01441     // Support for preferred service setting, see setPreferredService
01442     if (!d->m_preferredService.isEmpty()) {
01443         kDebug(7010) << "Attempting to open with preferred service: " << d->m_preferredService;
01444         KService::Ptr serv = KService::serviceByDesktopName(d->m_preferredService);
01445         if (serv && serv->hasMimeType(mime.data())) {
01446             KUrl::List lst;
01447             lst.append(d->m_strURL);
01448             d->m_bFinished = KRun::run(*serv, lst, d->m_window, false, QString(), d->m_asn);
01453         }
01454     }
01455 
01456     // Resolve .desktop files from media:/, remote:/, applications:/ etc.
01457     if (mime && mime->is("application/x-desktop") && !d->m_localPath.isEmpty()) {
01458         d->m_strURL = KUrl();
01459         d->m_strURL.setPath(d->m_localPath);
01460     }
01461 
01462     if (!d->m_bFinished && KRun::runUrl(d->m_strURL, type, d->m_window, false /*tempfile*/, d->m_runExecutables, d->m_suggestedFileName, d->m_asn)) {
01463         d->m_bFinished = true;
01464     }
01465     else {
01466         d->m_bFinished = true;
01467         d->m_bFault = true;
01468     }
01469 
01470     d->startTimer();
01471 }
01472 
01473 void KRun::killJob()
01474 {
01475     if (d->m_job) {
01476         kDebug(7010) << this << "m_job=" << d->m_job;
01477         d->m_job->kill();
01478         d->m_job = 0L;
01479     }
01480 }
01481 
01482 void KRun::abort()
01483 {
01484     kDebug(7010) << this << "m_showingDialog=" << d->m_showingDialog;
01485     killJob();
01486     // If we're showing an error message box, the rest will be done
01487     // after closing the msgbox -> don't autodelete nor emit signals now.
01488     if (d->m_showingDialog) {
01489         return;
01490     }
01491     d->m_bFault = true;
01492     d->m_bFinished = true;
01493     d->m_bInit = false;
01494     d->m_bScanFile = false;
01495 
01496     // will emit the error and autodelete this
01497     d->startTimer();
01498 }
01499 
01500 bool KRun::hasError() const
01501 {
01502     return d->m_bFault;
01503 }
01504 
01505 bool KRun::hasFinished() const
01506 {
01507     return d->m_bFinished;
01508 }
01509 
01510 bool KRun::autoDelete() const
01511 {
01512     return d->m_bAutoDelete;
01513 }
01514 
01515 void KRun::setAutoDelete(bool b)
01516 {
01517     d->m_bAutoDelete = b;
01518 }
01519 
01520 void KRun::setEnableExternalBrowser(bool b)
01521 {
01522     if (b) {
01523         d->m_externalBrowser = KConfigGroup(KGlobal::config(), "General").readEntry("BrowserApplication");
01524     }
01525     else {
01526         d->m_externalBrowser.clear();
01527     }
01528 }
01529 
01530 void KRun::setPreferredService(const QString& desktopEntryName)
01531 {
01532     d->m_preferredService = desktopEntryName;
01533 }
01534 
01535 void KRun::setRunExecutables(bool b)
01536 {
01537     d->m_runExecutables = b;
01538 }
01539 
01540 void KRun::setSuggestedFileName(const QString& fileName)
01541 {
01542     d->m_suggestedFileName = fileName;
01543 }
01544 
01545 QString KRun::suggestedFileName() const
01546 {
01547     return d->m_suggestedFileName;
01548 }
01549 
01550 bool KRun::isExecutable(const QString& serviceType)
01551 {
01552     return (serviceType == "application/x-desktop" ||
01553             serviceType == "application/x-executable" ||
01554             serviceType == "application/x-ms-dos-executable" ||
01555             serviceType == "application/x-shellscript");
01556 }
01557 
01558 void KRun::setUrl(const KUrl &url)
01559 {
01560     d->m_strURL = url;
01561 }
01562 
01563 KUrl KRun::url() const
01564 {
01565     return d->m_strURL;
01566 }
01567 
01568 void KRun::setError(bool error)
01569 {
01570     d->m_bFault = error;
01571 }
01572 
01573 void KRun::setProgressInfo(bool progressInfo)
01574 {
01575     d->m_bProgressInfo = progressInfo;
01576 }
01577 
01578 bool KRun::progressInfo() const
01579 {
01580     return d->m_bProgressInfo;
01581 }
01582 
01583 void KRun::setFinished(bool finished)
01584 {
01585     d->m_bFinished = finished;
01586     // TODO d->startTimer(); (and later on remove it from callers...)
01587 }
01588 
01589 void KRun::setJob(KIO::Job *job)
01590 {
01591     d->m_job = job;
01592 }
01593 
01594 KIO::Job* KRun::job()
01595 {
01596     return d->m_job;
01597 }
01598 
01599 QTimer& KRun::timer()
01600 {
01601     return d->m_timer;
01602 }
01603 
01604 void KRun::setDoScanFile(bool scanFile)
01605 {
01606     d->m_bScanFile = scanFile;
01607 }
01608 
01609 bool KRun::doScanFile() const
01610 {
01611     return d->m_bScanFile;
01612 }
01613 
01614 void KRun::setIsDirecory(bool isDirectory)
01615 {
01616     d->m_bIsDirectory = isDirectory;
01617 }
01618 
01619 bool KRun::isDirectory() const
01620 {
01621     return d->m_bIsDirectory;
01622 }
01623 
01624 void KRun::setInitializeNextAction(bool initialize)
01625 {
01626     d->m_bInit = initialize;
01627 }
01628 
01629 bool KRun::initializeNextAction() const
01630 {
01631     return d->m_bInit;
01632 }
01633 
01634 void KRun::setIsLocalFile(bool isLocalFile)
01635 {
01636     d->m_bIsLocalFile = isLocalFile;
01637 }
01638 
01639 bool KRun::isLocalFile() const
01640 {
01641     return d->m_bIsLocalFile;
01642 }
01643 
01644 void KRun::setMode(mode_t mode)
01645 {
01646     d->m_mode = mode;
01647 }
01648 
01649 mode_t KRun::mode() const
01650 {
01651     return d->m_mode;
01652 }
01653 
01654 /****************/
01655 
01656 #ifndef Q_WS_X11
01657 int KProcessRunner::run(KProcess * p, const QString & executable)
01658 {
01659     return (new KProcessRunner(p, executable))->pid();
01660 }
01661 #else
01662 int KProcessRunner::run(KProcess * p, const QString & executable, const KStartupInfoId& id)
01663 {
01664     return (new KProcessRunner(p, executable, id))->pid();
01665 }
01666 #endif
01667 
01668 #ifndef Q_WS_X11
01669 KProcessRunner::KProcessRunner(KProcess * p, const QString & executable)
01670 #else
01671 KProcessRunner::KProcessRunner(KProcess * p, const QString & executable, const KStartupInfoId& _id) :
01672         id(_id)
01673 #endif
01674 {
01675     m_pid = 0;
01676     process = p;
01677     m_executable = executable;
01678     connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
01679             this, SLOT(slotProcessExited(int, QProcess::ExitStatus)));
01680 
01681     process->start();
01682     if (!process->waitForStarted()) {
01683         //kDebug() << "wait for started failed, exitCode=" << process->exitCode()
01684         //         << "exitStatus=" << process->exitStatus();
01685         // Note that exitCode is 255 here (the first time), and 0 later on (bug?).
01686         slotProcessExited(255, process->exitStatus());
01687     }
01688     else {
01689 #ifdef Q_WS_X11
01690         m_pid = process->pid();
01691 #endif
01692     }
01693 }
01694 
01695 KProcessRunner::~KProcessRunner()
01696 {
01697     delete process;
01698 }
01699 
01700 int KProcessRunner::pid() const
01701 {
01702     return m_pid;
01703 }
01704 
01705 void KProcessRunner::terminateStartupNotification()
01706 {
01707 #ifdef Q_WS_X11
01708     if (!id.none()) {
01709         KStartupInfoData data;
01710         data.addPid(m_pid); // announce this pid for the startup notification has finished
01711         data.setHostname();
01712         KStartupInfo::sendFinish(id, data);
01713     }
01714 #endif
01715 
01716 }
01717 
01718 void
01719 KProcessRunner::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
01720 {
01721     kDebug(7010) << m_executable << "exitCode=" << exitCode << "exitStatus=" << exitStatus;
01722     Q_UNUSED(exitStatus);
01723 
01724     terminateStartupNotification(); // do this before the messagebox
01725     if (exitCode != 0 && !m_executable.isEmpty()) {
01726         // Let's see if the error is because the exe doesn't exist.
01727         // When this happens, waitForStarted returns false, but not if kioexec
01728         // was involved, then we come here, that's why the code is here.
01729         //
01730         // We'll try to find the executable relatively to current directory,
01731         // (or with a full path, if m_executable is absolute), and then in the PATH.
01732         if (!QFile(m_executable).exists() && KStandardDirs::findExe(m_executable).isEmpty()) {
01733             KGlobal::ref();
01734             KMessageBox::sorry(0L, i18n("Could not find the program '%1'", m_executable));
01735             KGlobal::deref();
01736         }
01737         else {
01738             kDebug() << process->readAllStandardError();
01739         }
01740     }
01741     deleteLater();
01742 }
01743 
01744 #include "krun.moc"
01745 #include "krun_p.moc"

KIO

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal