kio Library API Documentation

krun.cpp

00001 /* This file is part of the KDE libraries
00002     Copyright (C) 2000 Torben Weis <weis@kde.org>
00003 
00004     This library is free software; you can redistribute it and/or
00005     modify it under the terms of the GNU Library General Public
00006     License as published by the Free Software Foundation; either
00007     version 2 of the License, or (at your option) any later version.
00008 
00009     This library is distributed in the hope that it will be useful,
00010     but WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012     Library General Public License for more details.
00013 
00014     You should have received a copy of the GNU Library General Public License
00015     along with this library; see the file COPYING.LIB.  If not, write to
00016     the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00017     Boston, MA 02111-1307, USA.
00018 */
00019 
00020 #include "krun.h"
00021 
00022 #include <assert.h>
00023 #include <stdlib.h>
00024 #include <string.h>
00025 #include <unistd.h>
00026 #include <typeinfo>
00027 
00028 #include <qwidget.h>
00029 #include <qguardedptr.h>
00030 
00031 #include "kuserprofile.h"
00032 #include "kmimetype.h"
00033 #include "kmimemagic.h"
00034 #include "kio/job.h"
00035 #include "kio/global.h"
00036 #include "kio/scheduler.h"
00037 #include "kfile/kopenwith.h"
00038 #include "kfile/krecentdocument.h"
00039 
00040 #include <kdatastream.h>
00041 #include <kmessageboxwrapper.h>
00042 #include <kurl.h>
00043 #include <kapplication.h>
00044 #include <kdebug.h>
00045 #include <klocale.h>
00046 #include <kprotocolinfo.h>
00047 #include <kstandarddirs.h>
00048 #include <kprocess.h>
00049 #include <dcopclient.h>
00050 #include <qfile.h>
00051 #include <qfileinfo.h>
00052 #include <qtextstream.h>
00053 #include <qdatetime.h>
00054 #include <qregexp.h>
00055 #include <kdesktopfile.h>
00056 #include <kstartupinfo.h>
00057 #include <kmacroexpander.h>
00058 #include <kshell.h>
00059 #include <kde_file.h>
00060 
00061 #ifdef Q_WS_X11
00062 #include <kwin.h>
00063 #endif
00064 
00065 class KRun::KRunPrivate
00066 {
00067 public:
00068     KRunPrivate() { m_showingError = false; }
00069 
00070     bool m_showingError;
00071     bool m_runExecutables;
00072 
00073     QString m_preferredService;
00074     QString m_externalBrowser;
00075     QGuardedPtr <QWidget> m_window;
00076 };
00077 
00078 pid_t KRun::runURL( const KURL& u, const QString& _mimetype )
00079 {
00080     return runURL( u, _mimetype, false, true );
00081 }
00082 
00083 pid_t KRun::runURL( const KURL& u, const QString& _mimetype, bool tempFile )
00084 {
00085     return runURL( u, _mimetype, tempFile, true );
00086 }
00087 
00088 bool KRun::isExecutableFile( const KURL& url, const QString &mimetype )
00089 {
00090   if ( !url.isLocalFile() )
00091      return false;
00092   QFileInfo file( url.path() );
00093   if ( file.isExecutable() )  // Got a prospective file to run
00094   {
00095     KMimeType::Ptr mimeType = KMimeType::mimeType( mimetype );
00096 
00097     if ( mimeType->is("application/x-executable") || mimeType->is("application/x-executable-script") )
00098       return true;
00099   }
00100   return false;
00101 }
00102 
00103 // This is called by foundMimeType, since it knows the mimetype of the URL
00104 pid_t KRun::runURL( const KURL& u, const QString& _mimetype, bool tempFile, bool runExecutables )
00105 {
00106   bool noRun = false;
00107   bool noAuth = false;
00108   if ( _mimetype == "inode/directory-locked" )
00109   {
00110     KMessageBoxWrapper::error( 0L,
00111             i18n("<qt>Unable to enter <b>%1</b>.\nYou do not have access rights to this location.</qt>").arg(u.htmlURL()) );
00112     return 0;
00113   }
00114   else if ( _mimetype == "application/x-desktop" )
00115   {
00116     if ( u.isLocalFile() && runExecutables)
00117       return KDEDesktopMimeType::run( u, true );
00118   }
00119   else if ( isExecutableFile(u, _mimetype) )
00120   {
00121     if ( u.isLocalFile() && runExecutables)
00122     {
00123       if (kapp->authorize("shell_access"))
00124       {
00125         QString path = u.path();
00126         shellQuote( path );
00127         return (KRun::runCommand(path)); // just execute the url as a command
00128         // ## TODO implement deleting the file if tempFile==true
00129       }
00130       else
00131       {
00132         noAuth = true;
00133       }
00134     }
00135     else if (_mimetype == "application/x-executable")
00136       noRun = true;
00137   }
00138   else if ( isExecutable(_mimetype) )
00139   {
00140     if (!runExecutables)
00141       noRun = true;
00142 
00143     if (!kapp->authorize("shell_access"))
00144       noAuth = true;
00145   }
00146 
00147   if ( noRun )
00148   {
00149     KMessageBox::sorry( 0L,
00150         i18n("<qt>The file <b>%1</b> is an executable program. "
00151              "For safety it will not be started.</qt>").arg(u.htmlURL()));
00152     return 0;
00153   }
00154   if ( noAuth )
00155   {
00156     KMessageBoxWrapper::error( 0L,
00157         i18n("<qt>You do not have permission to run <b>%1</b>.</qt>").arg(u.htmlURL()) );
00158     return 0;
00159   }
00160 
00161   KURL::List lst;
00162   lst.append( u );
00163 
00164   static const QString& app_str = KGlobal::staticQString("Application");
00165 
00166   KService::Ptr offer = KServiceTypeProfile::preferredService( _mimetype, app_str );
00167 
00168   if ( !offer )
00169   {
00170     // Open-with dialog
00171     // TODO : pass the mimetype as a parameter, to show it (comment field) in the dialog !
00172     // Hmm, in fact KOpenWithDlg::setServiceType already guesses the mimetype from the first URL of the list...
00173     return displayOpenWithDialog( lst, tempFile );
00174   }
00175 
00176   return KRun::run( *offer, lst, tempFile );
00177 }
00178 
00179 bool KRun::displayOpenWithDialog( const KURL::List& lst )
00180 {
00181     return displayOpenWithDialog( lst, false );
00182 }
00183 
00184 bool KRun::displayOpenWithDialog( const KURL::List& lst, bool tempFiles )
00185 {
00186     if (kapp && !kapp->authorizeKAction("openwith"))
00187     {
00188        // TODO: Better message, i18n freeze :-(
00189        KMessageBox::sorry(0L, i18n("You are not authorized to open this file."));
00190        return false;
00191     }
00192 
00193     KOpenWithDlg l( lst, i18n("Open with:"), QString::null, 0L );
00194     if ( l.exec() )
00195     {
00196       KService::Ptr service = l.service();
00197       if ( !!service )
00198         return KRun::run( *service, lst, tempFiles );
00199 
00200       kdDebug(250) << "No service set, running " << l.text() << endl;
00201       return KRun::run( l.text(), lst ); // TODO handle tempFiles
00202     }
00203     return false;
00204 }
00205 
00206 void KRun::shellQuote( QString &_str )
00207 {
00208     // Credits to Walter, says Bernd G. :)
00209     if (_str.isEmpty()) // Don't create an explicit empty parameter
00210         return;
00211     QChar q('\'');
00212     _str.replace(q, "'\\''").prepend(q).append(q);
00213 }
00214 
00215 
00216 class KRunMX1 : public KMacroExpanderBase {
00217 public:
00218     KRunMX1( const KService &_service ) :
00219         KMacroExpanderBase( '%' ), hasUrls( false ), hasSpec( false ), service( _service ) {}
00220     bool hasUrls:1, hasSpec:1;
00221 
00222 protected:
00223     virtual int expandEscapedMacro( const QString &str, uint pos, QStringList &ret );
00224 
00225 private:
00226     const KService &service;
00227 };
00228 
00229 int
00230 KRunMX1::expandEscapedMacro( const QString &str, uint pos, QStringList &ret )
00231 {
00232    uint option = str[pos + 1];
00233    switch( option ) {
00234    case 'c':
00235       ret << service.name().replace( '%', "%%" );
00236       break;
00237    case 'k':
00238       ret << service.desktopEntryPath().replace( '%', "%%" );
00239       break;
00240    case 'i':
00241       ret << "-icon" << service.icon().replace( '%', "%%" );
00242       break;
00243    case 'm':
00244       ret << "-miniicon" << service.icon().replace( '%', "%%" );
00245       break;
00246    case 'u':
00247    case 'U':
00248       hasUrls = true;
00249       /* fallthrough */
00250    case 'f':
00251    case 'F':
00252    case 'n':
00253    case 'N':
00254    case 'd':
00255    case 'D':
00256    case 'v':
00257       hasSpec = true;
00258       /* fallthrough */
00259    default:
00260       return -2; // subst with same and skip
00261    }
00262    return 2;
00263 }
00264 
00265 class KRunMX2 : public KMacroExpanderBase {
00266 public:
00267     KRunMX2( const KURL::List &_urls ) :
00268         KMacroExpanderBase( '%' ), ignFile( false ), urls( _urls ) {}
00269     bool ignFile:1;
00270 
00271 protected:
00272     virtual int expandEscapedMacro( const QString &str, uint pos, QStringList &ret );
00273 
00274 private:
00275     void subst( int option, const KURL &url, QStringList &ret );
00276 
00277     const KURL::List &urls;
00278 };
00279 
00280 void
00281 KRunMX2::subst( int option, const KURL &url, QStringList &ret )
00282 {
00283    switch( option ) {
00284    case 'u':
00285       ret << (url.isLocalFile() ? url.path() : url.url());
00286       break;
00287    case 'd':
00288       ret << url.directory();
00289       break;
00290    case 'f':
00291       ret << url.path();
00292       break;
00293    case 'n':
00294       ret << url.fileName();
00295       break;
00296    case 'v':
00297       if (url.isLocalFile() && QFile::exists( url.path() ) )
00298           ret << KDesktopFile( url.path(), true ).readEntry( "Dev" );
00299       break;
00300    }
00301    return;
00302 }
00303 
00304 int
00305 KRunMX2::expandEscapedMacro( const QString &str, uint pos, QStringList &ret )
00306 {
00307    uint option = str[pos + 1];
00308    switch( option ) {
00309    case 'f':
00310    case 'u':
00311    case 'n':
00312    case 'd':
00313    case 'v':
00314       if( urls.isEmpty() ) {
00315          if (!ignFile)
00316             kdDebug() << "KRun::processDesktopExec: No URLs supplied to single-URL service " << str << endl;
00317       } else if( urls.count() > 1 )
00318           kdWarning() << "KRun::processDesktopExec: " << urls.count() << " URLs supplied to single-URL service " << str << endl;
00319       else
00320          subst( option, urls.first(), ret );
00321       break;
00322    case 'F':
00323    case 'U':
00324    case 'N':
00325    case 'D':
00326       option += 'a' - 'A';
00327       for( KURL::List::ConstIterator it = urls.begin(); it != urls.end(); ++it )
00328          subst( option, *it, ret );
00329       break;
00330    case '%':
00331       ret = "%";
00332       break;
00333    default:
00334       return -2; // subst with same and skip
00335    }
00336    return 2;
00337 }
00338 
00339 // BIC: merge with method below
00340 QStringList KRun::processDesktopExec(const KService &_service, const KURL::List& _urls, bool has_shell) {
00341     return processDesktopExec( _service, _urls, has_shell, false );
00342 }
00343 
00344 QStringList KRun::processDesktopExec(const KService &_service, const KURL::List& _urls, bool has_shell /* KDE4: remove */, bool tempFiles)
00345 {
00346   QString exec = _service.exec();
00347   QStringList result;
00348   bool appHasTempFileOption;
00349 
00350   KRunMX1 mx1( _service );
00351   KRunMX2 mx2( _urls );
00352 
00354   QRegExp re("^\\s*(?:/bin/)?sh\\s+-c\\s+(.*)$");
00355   if (!re.search( exec )) {
00356     exec = re.cap( 1 ).stripWhiteSpace();
00357     for (uint pos = 0; pos < exec.length(); ) {
00358       QChar c = exec.unicode()[pos];
00359       if (c != '\'' && c != '"')
00360         goto synerr; // what else can we do? after normal parsing the substs would be insecure
00361       int pos2 = exec.find( c, pos + 1 ) - 1;
00362       if (pos2 < 0)
00363         goto synerr; // quoting error
00364       memcpy( (void *)(exec.unicode() + pos), exec.unicode() + pos + 1, (pos2 - pos) * sizeof(QChar));
00365       pos = pos2;
00366       exec.remove( pos, 2 );
00367     }
00368   }
00369 
00370   if( !mx1.expandMacrosShellQuote( exec ) )
00371     goto synerr; // error in shell syntax
00372 
00373   // FIXME: the current way of invoking kioexec disables term and su use
00374 
00375   // Check if we need "tempexec" (kioexec in fact)
00376   appHasTempFileOption = tempFiles && _service.property("X-KDE-HasTempFileOption").toBool();
00377   if( tempFiles && !appHasTempFileOption ) {
00378     result << "kioexec" << "--tempfiles" << exec;
00379     result += _urls.toStringList();
00380     if (has_shell)
00381       result = KShell::joinArgs( result );
00382     return result;
00383   }
00384 
00385   // Check if we need kioexec
00386   if( !mx1.hasUrls ) {
00387     for( KURL::List::ConstIterator it = _urls.begin(); it != _urls.end(); ++it )
00388       if ( !(*it).isLocalFile() && !KProtocolInfo::isHelperProtocol(*it) ) {
00389         // We need to run the app through kioexec
00390         result << "kioexec";
00391         if ( tempFiles )
00392             result << "--tempfiles";
00393         result << exec;
00394         result += _urls.toStringList();
00395         if (has_shell)
00396           result = KShell::joinArgs( result );
00397         return result;
00398       }
00399   }
00400 
00401   if ( appHasTempFileOption )
00402       exec += " --tempfile";
00403 
00404   // Did the user forget to append something like '%f'?
00405   // If so, then assume that '%f' is the right choice => the application
00406   // accepts only local files.
00407   if( !mx1.hasSpec ) {
00408     exec += " %f";
00409     mx2.ignFile = true;
00410   }
00411 
00412   mx2.expandMacrosShellQuote( exec ); // syntax was already checked, so don't check return value
00413 
00414 /*
00415  1 = need_shell, 2 = terminal, 4 = su, 8 = has_shell
00416 
00417  0                                                           << split(cmd)
00418  1                                                           << "sh" << "-c" << cmd
00419  2 << split(term) << "-e"                                    << split(cmd)
00420  3 << split(term) << "-e"                                    << "sh" << "-c" << cmd
00421 
00422  4                        << "kdesu" << "-u" << user << "-c" << cmd
00423  5                        << "kdesu" << "-u" << user << "-c" << ("sh -c " + quote(cmd))
00424  6 << split(term) << "-e" << "su"            << user << "-c" << cmd
00425  7 << split(term) << "-e" << "su"            << user << "-c" << ("sh -c " + quote(cmd))
00426 
00427  8                                                           << cmd
00428  9                                                           << cmd
00429  a << term        << "-e"                                    << cmd
00430  b << term        << "-e"                                    << ("sh -c " + quote(cmd))
00431 
00432  c                        << "kdesu" << "-u" << user << "-c" << quote(cmd)
00433  d                        << "kdesu" << "-u" << user << "-c" << quote("sh -c " + quote(cmd))
00434  e << term        << "-e" << "su"            << user << "-c" << quote(cmd)
00435  f << term        << "-e" << "su"            << user << "-c" << quote("sh -c " + quote(cmd))
00436 
00437  "sh -c" is needed in the "su" case, too, as su uses the user's login shell, not sh.
00438  this could be optimized with the -s switch of some su versions (e.g., debian linux).
00439 */
00440 
00441   if (_service.terminal()) {
00442     KConfigGroupSaver gs(KGlobal::config(), "General");
00443     QString terminal = KGlobal::config()->readPathEntry("TerminalApplication", "konsole");
00444     if (terminal == "konsole")
00445       terminal += " -caption=%c %i %m";
00446     terminal += " ";
00447     terminal += _service.terminalOptions();
00448     if( !mx1.expandMacrosShellQuote( terminal ) ) {
00449       kdWarning() << "KRun: syntax error in command `" << terminal << "', service `" << _service.name() << "'" << endl;
00450       return QStringList();
00451     }
00452     mx2.expandMacrosShellQuote( terminal );
00453     if (has_shell)
00454       result << terminal;
00455     else
00456       result = KShell::splitArgs( terminal ); // assuming that the term spec never needs a shell!
00457     result << "-e";
00458   }
00459 
00460   int err;
00461   if (_service.substituteUid()) {
00462     if (_service.terminal())
00463       result << "su";
00464     else
00465       result << "kdesu" << "-u";
00466     result << _service.username() << "-c";
00467     KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
00468     if (err == KShell::FoundMeta) {
00469       shellQuote( exec );
00470       exec.prepend( "/bin/sh -c " );
00471     } else if (err != KShell::NoError)
00472       goto synerr;
00473     if (has_shell)
00474       shellQuote( exec );
00475     result << exec;
00476   } else {
00477     if (has_shell) {
00478       if (_service.terminal()) {
00479         KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
00480         if (err == KShell::FoundMeta) {
00481           shellQuote( exec );
00482           exec.prepend( "/bin/sh -c " );
00483         } else if (err != KShell::NoError)
00484           goto synerr;
00485       }
00486       result << exec;
00487     } else {
00488       result += KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
00489       if (err == KShell::FoundMeta)
00490         result << "/bin/sh" << "-c" << exec;
00491       else if (err != KShell::NoError)
00492         goto synerr;
00493     }
00494   }
00495 
00496   return result;
00497 
00498  synerr:
00499   kdWarning() << "KRun: syntax error in command `" << _service.exec() << "', service `" << _service.name() << "'" << endl;
00500   return QStringList();
00501 }
00502 
00503 //static
00504 QString KRun::binaryName( const QString & execLine, bool removePath )
00505 {
00506   // Remove parameters and/or trailing spaces.
00507   QStringList args = KShell::splitArgs( execLine );
00508   for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it)
00509     if (!(*it).contains('='))
00510       // Remove path if wanted
00511       return removePath ? (*it).mid((*it).findRev('/') + 1) : *it;
00512   return QString::null;
00513 }
00514 
00515 static pid_t runCommandInternal( KProcess* proc, const KService* service, const QString& binName,
00516     const QString &execName, const QString & iconName )
00517 {
00518   if (service && !service->desktopEntryPath().isEmpty()
00519       && !KDesktopFile::isAuthorizedDesktopFile( service->desktopEntryPath() ))
00520   {
00521      kdWarning() << "No authorization to execute " << service->desktopEntryPath() << endl;
00522      KMessageBox::sorry(0, i18n("You are not authorized to execute this file."));
00523      return 0;
00524   }
00525   QString bin = KRun::binaryName( binName, true );
00526 #ifdef Q_WS_X11 // Startup notification doesn't work with QT/E, service isn't needed without Startup notification
00527   bool silent;
00528   QCString wmclass;
00529   KStartupInfoId id;
00530   bool startup_notify = KRun::checkStartupNotify( binName, service, &silent, &wmclass );
00531   if( startup_notify )
00532   {
00533       id.initId();
00534       id.setupStartupEnv();
00535       KStartupInfoData data;
00536       data.setHostname();
00537       data.setBin( bin );
00538       if( !execName.isEmpty())
00539           data.setName( execName );
00540       else if( service && !service->name().isEmpty())
00541           data.setName( service->name());
00542       data.setDescription( i18n( "Launching %1" ).arg( data.name()));
00543       if( !iconName.isEmpty())
00544           data.setIcon( iconName );
00545       else if( service && !service->icon().isEmpty())
00546           data.setIcon( service->icon());
00547       if( !wmclass.isEmpty())
00548           data.setWMClass( wmclass );
00549       if( silent )
00550           data.setSilent( KStartupInfoData::Yes );
00551       data.setDesktop( KWin::currentDesktop());
00552       KStartupInfo::sendStartup( id, data );
00553   }
00554   pid_t pid = KProcessRunner::run( proc, binName, id );
00555   if( startup_notify && pid )
00556   {
00557       KStartupInfoData data;
00558       data.addPid( pid );
00559       KStartupInfo::sendChange( id, data );
00560       KStartupInfo::resetStartupEnv();
00561   }
00562   return pid;
00563 #else
00564   Q_UNUSED( execName );
00565   Q_UNUSED( iconName );
00566   return KProcessRunner::run( proc, bin );
00567 #endif
00568 }
00569 
00570 // This code is also used in klauncher.
00571 bool KRun::checkStartupNotify( const QString& /*binName*/, const KService* service, bool* silent_arg, QCString* wmclass_arg )
00572 {
00573   bool silent = false;
00574   QCString wmclass;
00575   if( service && service->property( "StartupNotify" ).isValid())
00576   {
00577       silent = !service->property( "StartupNotify" ).toBool();
00578       wmclass = service->property( "StartupWMClass" ).toString().latin1();
00579   }
00580   else if( service && service->property( "X-KDE-StartupNotify" ).isValid())
00581   {
00582       silent = !service->property( "X-KDE-StartupNotify" ).toBool();
00583       wmclass = service->property( "X-KDE-WMClass" ).toString().latin1();
00584   }
00585   else // non-compliant app
00586   {
00587       if( service )
00588       {
00589           if( service->type() == "Application" )
00590               wmclass = "0"; // doesn't have .desktop entries needed, start as non-compliant
00591           else
00592               return false; // no startup notification at all
00593       }
00594       else
00595       { // Create startup notification even for apps for which there shouldn't be any,
00596         // just without any visual feedback. This will ensure they'll be positioned on the proper
00597         // virtual desktop, and will get user timestamp from the ASN ID.
00598           wmclass = "0";
00599           silent = true;
00600       }
00601   }
00602   if( silent_arg != NULL )
00603       *silent_arg = silent;
00604   if( wmclass_arg != NULL )
00605       *wmclass_arg = wmclass;
00606   return true;
00607 }
00608 
00609 static pid_t runTempService( const KService& _service, const KURL::List& _urls, bool tempFiles )
00610 {
00611   if (!_urls.isEmpty()) {
00612     kdDebug(7010) << "runTempService: first url " << _urls.first().url() << endl;
00613   }
00614 
00615   QStringList args;
00616   if ((_urls.count() > 1) && !_service.allowMultipleFiles())
00617   {
00618       // We need to launch the application N times. That sucks.
00619       // We ignore the result for application 2 to N.
00620       // For the first file we launch the application in the
00621       // usual way. The reported result is based on this
00622       // application.
00623       KURL::List::ConstIterator it = _urls.begin();
00624       while(++it != _urls.end())
00625       {
00626          KURL::List singleUrl;
00627          singleUrl.append(*it);
00628          runTempService( _service, singleUrl, tempFiles );
00629       }
00630       KURL::List singleUrl;
00631       singleUrl.append(_urls.first());
00632       args = KRun::processDesktopExec(_service, singleUrl, false, tempFiles);
00633   }
00634   else
00635   {
00636       args = KRun::processDesktopExec(_service, _urls, false, tempFiles);
00637   }
00638   kdDebug(7010) << "runTempService: KProcess args=" << args << endl;
00639 
00640   KProcess * proc = new KProcess;
00641   *proc << args;
00642 
00643   if (!_service.path().isEmpty())
00644      proc->setWorkingDirectory(_service.path());
00645 
00646   return runCommandInternal( proc, &_service, KRun::binaryName( _service.exec(), false ),
00647                              _service.name(), _service.icon() );
00648 }
00649 
00650 // BIC merge with method below
00651 pid_t KRun::run( const KService& _service, const KURL::List& _urls )
00652 {
00653     return run( _service, _urls, false );
00654 }
00655 
00656 pid_t KRun::run( const KService& _service, const KURL::List& _urls, bool tempFiles )
00657 {
00658   if (!_service.desktopEntryPath().isEmpty() &&
00659       !KDesktopFile::isAuthorizedDesktopFile( _service.desktopEntryPath()))
00660   {
00661      kdWarning() << "No authorization to execute " << _service.desktopEntryPath() << endl;
00662      KMessageBox::sorry(0, i18n("You are not authorized to execute this service."));
00663      return 0;
00664   }
00665 
00666   if ( !tempFiles )
00667   {
00668       // Remember we opened those urls, for the "recent documents" menu in kicker
00669       KURL::List::ConstIterator it = _urls.begin();
00670       for(; it != _urls.end(); ++it) {
00671           //kdDebug(7010) << "KRecentDocument::adding " << (*it).url() << endl;
00672           KRecentDocument::add( *it, _service.desktopEntryName() );
00673       }
00674   }
00675 
00676   if ( tempFiles || _service.desktopEntryPath().isEmpty())
00677   {
00678      return runTempService(_service, _urls, tempFiles);
00679   }
00680 
00681   kdDebug(7010) << "KRun::run " << _service.desktopEntryPath() << endl;
00682 
00683   if (!_urls.isEmpty()) {
00684     kdDebug(7010) << "First url " << _urls.first().url() << endl;
00685   }
00686 
00687   QString error;
00688   int pid = 0;
00689 
00690   int i = KApplication::startServiceByDesktopPath(
00691         _service.desktopEntryPath(), _urls.toStringList(), &error, 0L, &pid
00692         );
00693 
00694   if (i != 0)
00695   {
00696      kdDebug(7010) << error << endl;
00697      KMessageBox::sorry( 0L, error );
00698      return 0;
00699   }
00700 
00701   kdDebug(7010) << "startServiceByDesktopPath worked fine" << endl;
00702   return (pid_t) pid;
00703 }
00704 
00705 
00706 pid_t KRun::run( const QString& _exec, const KURL::List& _urls, const QString& _name,
00707                 const QString& _icon, const QString&, const QString&)
00708 {
00709   KService::Ptr service = new KService(_name, _exec, _icon);
00710 
00711   return run(*service, _urls);
00712 }
00713 
00714 pid_t KRun::runCommand( QString cmd )
00715 {
00716   return KRun::runCommand( cmd, QString::null, QString::null );
00717 }
00718 
00719 pid_t KRun::runCommand( const QString& cmd, const QString &execName, const QString & iconName )
00720 {
00721   kdDebug(7010) << "runCommand " << cmd << "," << execName << endl;
00722   KProcess * proc = new KProcess;
00723   proc->setUseShell(true);
00724   *proc << cmd;
00725   KService::Ptr service = KService::serviceByDesktopName( binaryName( execName, true ) );
00726   return runCommandInternal( proc, service.data(), binaryName( execName, false ), execName, iconName );
00727 }
00728 
00729 KRun::KRun( const KURL& url, mode_t mode, bool isLocalFile, bool showProgressInfo )
00730      :m_timer(0,"KRun::timer")
00731 {
00732   init (url, 0, mode, isLocalFile, showProgressInfo);
00733 }
00734 
00735 KRun::KRun( const KURL& url, QWidget* window, mode_t mode, bool isLocalFile,
00736             bool showProgressInfo )
00737      :m_timer(0,"KRun::timer")
00738 {
00739   init (url, window, mode, isLocalFile, showProgressInfo);
00740 }
00741 
00742 void KRun::init ( const KURL& url, QWidget* window, mode_t mode, bool isLocalFile,
00743                   bool showProgressInfo )
00744 {
00745   m_bFault = false;
00746   m_bAutoDelete = true;
00747   m_bProgressInfo = showProgressInfo;
00748   m_bFinished = false;
00749   m_job = 0L;
00750   m_strURL = url;
00751   m_bScanFile = false;
00752   m_bIsDirectory = false;
00753   m_bIsLocalFile = isLocalFile;
00754   m_mode = mode;
00755   d = new KRunPrivate;
00756   d->m_runExecutables = true;
00757   d->m_window = window;
00758   setEnableExternalBrowser(true);
00759 
00760   // Start the timer. This means we will return to the event
00761   // loop and do initialization afterwards.
00762   // Reason: We must complete the constructor before we do anything else.
00763   m_bInit = true;
00764   connect( &m_timer, SIGNAL( timeout() ), this, SLOT( slotTimeout() ) );
00765   m_timer.start( 0, true );
00766   kdDebug(7010) << " new KRun " << this << " " << url.prettyURL() << " timer=" << &m_timer << endl;
00767 
00768   kapp->ref();
00769 }
00770 
00771 void KRun::init()
00772 {
00773   kdDebug(7010) << "INIT called" << endl;
00774   if ( !m_strURL.isValid() )
00775   {
00776     d->m_showingError = true;
00777     KMessageBoxWrapper::error( d->m_window, i18n( "Malformed URL\n%1" ).arg( m_strURL.url() ) );
00778     d->m_showingError = false;
00779     m_bFault = true;
00780     m_bFinished = true;
00781     m_timer.start( 0, true );
00782     return;
00783   }
00784   if ( !kapp->authorizeURLAction( "open", KURL(), m_strURL))
00785   {
00786     QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, m_strURL.prettyURL());
00787     d->m_showingError = true;
00788     KMessageBoxWrapper::error( d->m_window, msg );
00789     d->m_showingError = false;
00790     m_bFault = true;
00791     m_bFinished = true;
00792     m_timer.start( 0, true );
00793     return;
00794   }
00795 
00796   if ( !m_bIsLocalFile && m_strURL.isLocalFile() )
00797     m_bIsLocalFile = true;
00798 
00799   QString exec;
00800   if (m_strURL.protocol().startsWith("http"))
00801   {
00802     exec = d->m_externalBrowser;
00803   }
00804 
00805   if ( m_bIsLocalFile )
00806   {
00807     if ( m_mode == 0 )
00808     {
00809       KDE_struct_stat buff;
00810       if ( KDE_stat( QFile::encodeName(m_strURL.path()), &buff ) == -1 )
00811       {
00812         d->m_showingError = true;
00813         KMessageBoxWrapper::error( d->m_window, i18n( "<qt>Unable to run the command specified. The file or folder <b>%1</b> does not exist.</qt>" ).arg( m_strURL.htmlURL() ) );
00814         d->m_showingError = false;
00815         m_bFault = true;
00816         m_bFinished = true;
00817         m_timer.start( 0, true );
00818         return;
00819       }
00820       m_mode = buff.st_mode;
00821     }
00822 
00823     KMimeType::Ptr mime = KMimeType::findByURL( m_strURL, m_mode, m_bIsLocalFile );
00824     assert( mime != 0L );
00825     kdDebug(7010) << "MIME TYPE is " << mime->name() << endl;
00826     foundMimeType( mime->name() );
00827     return;
00828   }
00829   else if ( !exec.isEmpty() || KProtocolInfo::isHelperProtocol( m_strURL ) ) {
00830     kdDebug(7010) << "Helper protocol" << endl;
00831 
00832     bool ok = false;
00833     KURL::List urls;
00834     urls.append( m_strURL );
00835     if (exec.isEmpty())
00836     {
00837        exec = KProtocolInfo::exec( m_strURL.protocol() );
00838        if (exec.isEmpty())
00839        {
00840           foundMimeType(KProtocolInfo::defaultMimetype(m_strURL));
00841           return;
00842        }
00843        run( exec, urls );
00844        ok = true;
00845     }
00846     else if (exec.startsWith("!"))
00847     {
00848        exec = exec.mid(1); // Literal command
00849        exec += " %u";
00850        run( exec, urls );
00851        ok = true;
00852     }
00853     else
00854     {
00855        KService::Ptr service = KService::serviceByStorageId( exec );
00856        if (service)
00857        {
00858           run( *service, urls );
00859           ok = true;
00860        }
00861     }
00862 
00863     if (ok)
00864     {
00865        m_bFinished = true;
00866        // will emit the error and autodelete this
00867        m_timer.start( 0, true );
00868        return;
00869     }
00870   }
00871 
00872   // Did we already get the information that it is a directory ?
00873   if ( S_ISDIR( m_mode ) )
00874   {
00875     foundMimeType( "inode/directory" );
00876     return;
00877   }
00878 
00879   // Let's see whether it is a directory
00880 
00881   if ( !KProtocolInfo::supportsListing( m_strURL ) )
00882   {
00883     //kdDebug(7010) << "Protocol has no support for listing" << endl;
00884     // No support for listing => it can't be a directory (example: http)
00885     scanFile();
00886     return;
00887   }
00888 
00889   kdDebug(7010) << "Testing directory (stating)" << endl;
00890 
00891   // It may be a directory or a file, let's stat
00892   KIO::StatJob *job = KIO::stat( m_strURL, true, 0 /* no details */, m_bProgressInfo );
00893   job->setWindow (d->m_window);
00894   connect( job, SIGNAL( result( KIO::Job * ) ),
00895            this, SLOT( slotStatResult( KIO::Job * ) ) );
00896   m_job = job;
00897   kdDebug(7010) << " Job " << job << " is about stating " << m_strURL.url() << endl;
00898 }
00899 
00900 KRun::~KRun()
00901 {
00902   kdDebug(7010) << "KRun::~KRun() " << this << endl;
00903   m_timer.stop();
00904   killJob();
00905   kapp->deref();
00906   kdDebug(7010) << "KRun::~KRun() done " << this << endl;
00907   delete d;
00908 }
00909 
00910 void KRun::scanFile()
00911 {
00912   kdDebug(7010) << "###### KRun::scanFile " << m_strURL.url() << endl;
00913   // First, let's check for well-known extensions
00914   // Not when there is a query in the URL, in any case.
00915   if ( m_strURL.query().isEmpty() )
00916   {
00917     KMimeType::Ptr mime = KMimeType::findByURL( m_strURL );
00918     assert( mime != 0L );
00919     if ( mime->name() != "application/octet-stream" || m_bIsLocalFile )
00920     {
00921       kdDebug(7010) << "Scanfile: MIME TYPE is " << mime->name() << endl;
00922       foundMimeType( mime->name() );
00923       return;
00924     }
00925   }
00926 
00927   // No mimetype found, and the URL is not local  (or fast mode not allowed).
00928   // We need to apply the 'KIO' method, i.e. either asking the server or
00929   // getting some data out of the file, to know what mimetype it is.
00930 
00931   if ( !KProtocolInfo::supportsReading( m_strURL ) )
00932   {
00933     kdError(7010) << "#### NO SUPPORT FOR READING!" << endl;
00934     m_bFault = true;
00935     m_bFinished = true;
00936     m_timer.start( 0, true );
00937     return;
00938   }
00939   kdDebug(7010) << this << " Scanning file " << m_strURL.url() << endl;
00940 
00941   KIO::TransferJob *job = KIO::get( m_strURL, false /*reload*/, m_bProgressInfo );
00942   job->setWindow (d->m_window);
00943   connect(job, SIGNAL( result(KIO::Job *)),
00944           this, SLOT( slotScanFinished(KIO::Job *)));
00945   connect(job, SIGNAL( mimetype(KIO::Job *, const QString &)),
00946           this, SLOT( slotScanMimeType(KIO::Job *, const QString &)));
00947   m_job = job;
00948   kdDebug(7010) << " Job " << job << " is about getting from " << m_strURL.url() << endl;
00949 }
00950 
00951 void KRun::slotTimeout()
00952 {
00953   kdDebug(7010) << this << " slotTimeout called" << endl;
00954   if ( m_bInit )
00955   {
00956     m_bInit = false;
00957     init();
00958     return;
00959   }
00960 
00961   if ( m_bFault ) {
00962       emit error();
00963   }
00964   if ( m_bFinished ) {
00965       emit finished();
00966   }
00967   else
00968   {
00969   if ( m_bScanFile )
00970   {
00971     m_bScanFile = false;
00972     scanFile();
00973     return;
00974   }
00975   else if ( m_bIsDirectory )
00976   {
00977     m_bIsDirectory = false;
00978     foundMimeType( "inode/directory" );
00979     return;
00980   }
00981   }
00982 
00983   if ( m_bAutoDelete )
00984   {
00985     delete this;
00986     return;
00987   }
00988 }
00989 
00990 void KRun::slotStatResult( KIO::Job * job )
00991 {
00992   m_job = 0L;
00993   if (job->error())
00994   {
00995     d->m_showingError = true;
00996     kdError(7010) << this << " ERROR " << job->error() << " " << job->errorString() << endl;
00997     job->showErrorDialog();
00998     //kdDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us" << endl;
00999     d->m_showingError = false;
01000 
01001     m_bFault = true;
01002     m_bFinished = true;
01003 
01004     // will emit the error and autodelete this
01005     m_timer.start( 0, true );
01006 
01007   } else {
01008 
01009     kdDebug(7010) << "Finished" << endl;
01010     if(!dynamic_cast<KIO::StatJob*>(job))
01011         kdFatal() << "job is a " << typeid(*job).name() << " should be a StatJob" << endl;
01012 
01013     KIO::UDSEntry entry = ((KIO::StatJob*)job)->statResult();
01014     KIO::UDSEntry::ConstIterator it = entry.begin();
01015     for( ; it != entry.end(); it++ ) {
01016         if ( (*it).m_uds == KIO::UDS_FILE_TYPE )
01017         {
01018             if ( S_ISDIR( (mode_t)((*it).m_long) ) )
01019                 m_bIsDirectory = true; // it's a dir
01020             else
01021                 m_bScanFile = true; // it's a file
01022         }
01023         else if ( (*it).m_uds == KIO::UDS_MIME_TYPE ) // mimetype already known? (e.g. print:/manager)
01024         {
01025             foundMimeType( (*it).m_str );
01026             m_bFinished = true;
01027         }
01028     }
01029     // We should have found something
01030     assert ( m_bScanFile || m_bIsDirectory );
01031 
01032     // Start the timer. Once we get the timer event this
01033     // protocol server is back in the pool and we can reuse it.
01034     // This gives better performance than starting a new slave
01035     m_timer.start( 0, true );
01036   }
01037 }
01038 
01039 void KRun::slotScanMimeType( KIO::Job *, const QString &mimetype )
01040 {
01041   if ( mimetype.isEmpty() )
01042     kdWarning(7010) << "KRun::slotScanFinished : MimetypeJob didn't find a mimetype! Probably a kioslave bug." << endl;
01043   foundMimeType( mimetype );
01044   m_job = 0;
01045 }
01046 
01047 void KRun::slotScanFinished( KIO::Job *job )
01048 {
01049   m_job = 0;
01050   if (job->error())
01051   {
01052     d->m_showingError = true;
01053     kdError(7010) << this << " ERROR (stat) : " << job->error() << " " << job->errorString() << endl;
01054     job->showErrorDialog();
01055     //kdDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us" << endl;
01056     d->m_showingError = false;
01057 
01058     m_bFault = true;
01059     m_bFinished = true;
01060 
01061     // will emit the error and autodelete this
01062     m_timer.start( 0, true );
01063   }
01064 }
01065 
01066 void KRun::foundMimeType( const QString& type )
01067 {
01068   kdDebug(7010) << "Resulting mime type is " << type << endl;
01069 
01070 /*
01071   // Automatically unzip stuff
01072 
01073   // Disabled since the new KIO doesn't have filters yet.
01074 
01075   if ( type == "application/x-gzip"  ||
01076        type == "application/x-bzip"  ||
01077        type == "application/x-bzip2"  )
01078   {
01079     KURL::List lst = KURL::split( m_strURL );
01080     if ( lst.isEmpty() )
01081     {
01082       QString tmp = i18n( "Malformed URL" );
01083       tmp += "\n";
01084       tmp += m_strURL.url();
01085       KMessageBoxWrapper::error( 0L, tmp );
01086       return;
01087     }
01088 
01089     if ( type == "application/x-gzip" )
01090       lst.prepend( KURL( "gzip:/decompress" ) );
01091     else if ( type == "application/x-bzip" )
01092       lst.prepend( KURL( "bzip:/decompress" ) );
01093     else if ( type == "application/x-bzip2" )
01094       lst.prepend( KURL( "bzip2:/decompress" ) );
01095     else if ( type == "application/x-tar" )
01096       lst.prepend( KURL( "tar:/" ) );
01097 
01098     // Move the HTML style reference to the leftmost URL
01099     KURL::List::Iterator it = lst.begin();
01100     ++it;
01101     (*lst.begin()).setRef( (*it).ref() );
01102     (*it).setRef( QString::null );
01103 
01104     // Create the new URL
01105     m_strURL = KURL::join( lst );
01106 
01107     kdDebug(7010) << "Now trying with " << debugString(m_strURL.url()) << endl;
01108 
01109     killJob();
01110 
01111     // We don't know if this is a file or a directory. Let's test this first.
01112     // (For instance a tar.gz is a directory contained inside a file)
01113     // It may be a directory or a file, let's stat
01114     KIO::StatJob *job = KIO::stat( m_strURL, m_bProgressInfo );
01115     connect( job, SIGNAL( result( KIO::Job * ) ),
01116              this, SLOT( slotStatResult( KIO::Job * ) ) );
01117     m_job = job;
01118 
01119     return;
01120   }
01121 */
01122   if (m_job && m_job->inherits("KIO::TransferJob"))
01123   {
01124      KIO::TransferJob *job = static_cast<KIO::TransferJob *>(m_job);
01125      job->putOnHold();
01126      KIO::Scheduler::publishSlaveOnHold();
01127      m_job = 0;
01128   }
01129 
01130   Q_ASSERT( !m_bFinished );
01131 
01132   // Suport for preferred service setting, see setPreferredService
01133   if ( !d->m_preferredService.isEmpty() ) {
01134       kdDebug(7010) << "Attempting to open with preferred service: " << d->m_preferredService << endl;
01135       KService::Ptr serv = KService::serviceByDesktopName( d->m_preferredService );
01136       if ( serv && serv->hasServiceType( type ) )
01137       {
01138           KURL::List lst;
01139           lst.append( m_strURL );
01140           m_bFinished = KRun::run( *serv, lst );
01145       }
01146   }
01147 
01148   if (!m_bFinished && KRun::runURL( m_strURL, type, false, d->m_runExecutables )){
01149     m_bFinished = true;
01150   }
01151   else{
01152     m_bFinished = true;
01153      m_bFault = true;
01154   }
01155 
01156   m_timer.start( 0, true );
01157 }
01158 
01159 void KRun::killJob()
01160 {
01161   if ( m_job )
01162   {
01163     kdDebug(7010) << "KRun::killJob run=" << this << " m_job=" << m_job << endl;
01164     m_job->kill();
01165     m_job = 0L;
01166   }
01167 }
01168 
01169 void KRun::abort()
01170 {
01171   kdDebug(7010) << "KRun::abort " << this << " m_showingError=" << d->m_showingError << endl;
01172   killJob();
01173   // If we're showing an error message box, the rest will be done
01174   // after closing the msgbox -> don't autodelete nor emit signals now.
01175   if ( d->m_showingError )
01176     return;
01177   m_bFault = true;
01178   m_bFinished = true;
01179   m_bInit = false;
01180   m_bScanFile = false;
01181 
01182   // will emit the error and autodelete this
01183   m_timer.start( 0, true );
01184 }
01185 
01186 void KRun::setEnableExternalBrowser(bool b)
01187 {
01188    if (b)
01189       d->m_externalBrowser = KConfigGroup(KGlobal::config(), "General").readEntry("BrowserApplication");
01190    else
01191       d->m_externalBrowser = QString::null;
01192 }
01193 
01194 void KRun::setPreferredService( const QString& desktopEntryName )
01195 {
01196     d->m_preferredService = desktopEntryName;
01197 }
01198 
01199 void KRun::setRunExecutables(bool b)
01200 {
01201     d->m_runExecutables = b;
01202 }
01203 
01204 bool KRun::isExecutable( const QString& serviceType )
01205 {
01206     return ( serviceType == "application/x-desktop" ||
01207              serviceType == "application/x-executable" ||
01208              serviceType == "application/x-msdos-program" ||
01209              serviceType == "application/x-shellscript" );
01210 }
01211 
01212 /****************/
01213 
01214 pid_t
01215 KProcessRunner::run(KProcess * p, const QString & binName)
01216 {
01217   return (new KProcessRunner(p, binName))->pid();
01218 }
01219 
01220 #ifdef Q_WS_X11
01221 pid_t
01222 KProcessRunner::run(KProcess * p, const QString & binName, const KStartupInfoId& id )
01223 {
01224   return (new KProcessRunner(p, binName, id))->pid();
01225 }
01226 #endif
01227 
01228 KProcessRunner::KProcessRunner(KProcess * p, const QString & _binName )
01229   : QObject(),
01230     process_(p),
01231     binName( _binName )
01232 {
01233   QObject::connect(
01234       process_, SIGNAL(processExited(KProcess *)),
01235       this,     SLOT(slotProcessExited(KProcess *)));
01236 
01237   process_->start();
01238   if ( !process_->pid() )
01239       slotProcessExited( process_ );
01240 }
01241 
01242 #ifdef Q_WS_X11
01243 KProcessRunner::KProcessRunner(KProcess * p, const QString & _binName, const KStartupInfoId& id )
01244   : QObject(),
01245     process_(p),
01246     binName( _binName ),
01247     id_( id )
01248 {
01249   QObject::connect(
01250       process_, SIGNAL(processExited(KProcess *)),
01251       this,     SLOT(slotProcessExited(KProcess *)));
01252 
01253   process_->start();
01254   if ( !process_->pid() )
01255       slotProcessExited( process_ );
01256 }
01257 #endif
01258 
01259 KProcessRunner::~KProcessRunner()
01260 {
01261   delete process_;
01262 }
01263 
01264   pid_t
01265 KProcessRunner::pid() const
01266 {
01267   return process_->pid();
01268 }
01269 
01270   void
01271 KProcessRunner::slotProcessExited(KProcess * p)
01272 {
01273   if (p != process_)
01274     return; // Eh ?
01275 
01276   kdDebug(7010) << "slotProcessExited " << binName << endl;
01277   kdDebug(7010) << "normalExit " << process_->normalExit() << endl;
01278   kdDebug(7010) << "exitStatus " << process_->exitStatus() << endl;
01279   bool showErr = process_->normalExit()
01280                  && ( process_->exitStatus() == 127 || process_->exitStatus() == 1 );
01281   if ( !binName.isEmpty() && ( showErr || process_->pid() == 0 ) )
01282   {
01283     // Often we get 1 (zsh, csh) or 127 (ksh, bash) because the binary doesn't exist.
01284     // We can't just rely on that, but it's a good hint.
01285     // Before assuming its really so, we'll try to find the binName
01286     // relatively to current directory,  and then in the PATH.
01287     if ( !QFile( binName ).exists() && KStandardDirs::findExe( binName ).isEmpty() )
01288     {
01289       kapp->ref();
01290       KMessageBox::sorry( 0L, i18n("Could not find the program '%1'").arg( binName ) );
01291       kapp->deref();
01292     }
01293   }
01294 #ifdef Q_WS_X11
01295   if( !id_.none())
01296   {
01297       KStartupInfoData data;
01298       data.addPid( pid()); // announce this pid for the startup notification has finished
01299       data.setHostname();
01300       KStartupInfo::sendFinish( id_, data );
01301   }
01302 #endif
01303   deleteLater();
01304 }
01305 
01306 void KRun::virtual_hook( int, void* )
01307 { /*BASE::virtual_hook( id, data );*/ }
01308 
01309 #include "krun.moc"
KDE Logo
This file is part of the documentation for kio Library Version 3.4.3.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sun Oct 9 08:00:06 2005 by doxygen 1.4.4 written by Dimitri van Heesch, © 1997-2003