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

KDE's Doxygen guidelines are available online.

KIO

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

kdelibs-4.8.5 API Reference

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

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