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");
00444     if( terminal.isEmpty() )
00445     {
00446         if( !KStandardDirs::findExe( "konsole" ).isEmpty() )
00447                 terminal = "konsole";
00448         else
00449                 terminal = "xvt";
00450     }
00451     if (terminal == "konsole")
00452       terminal += " -caption=%c %i %m";
00453     terminal += " ";
00454     terminal += _service.terminalOptions();
00455     if( !mx1.expandMacrosShellQuote( terminal ) ) {
00456       kdWarning() << "KRun: syntax error in command `" << terminal << "', service `" << _service.name() << "'" << endl;
00457       return QStringList();
00458     }
00459     mx2.expandMacrosShellQuote( terminal );
00460     if (has_shell)
00461       result << terminal;
00462     else
00463       result = KShell::splitArgs( terminal ); // assuming that the term spec never needs a shell!
00464     result << "-e";
00465   }
00466 
00467   int err;
00468   if (_service.substituteUid()) {
00469     if (_service.terminal())
00470       result << "su";
00471     else
00472       result << "kdesu" << "-u";
00473     result << _service.username() << "-c";
00474     KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
00475     if (err == KShell::FoundMeta) {
00476       shellQuote( exec );
00477       exec.prepend( "/bin/sh -c " );
00478     } else if (err != KShell::NoError)
00479       goto synerr;
00480     if (has_shell)
00481       shellQuote( exec );
00482     result << exec;
00483   } else {
00484     if (has_shell) {
00485       if (_service.terminal()) {
00486         KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
00487         if (err == KShell::FoundMeta) {
00488           shellQuote( exec );
00489           exec.prepend( "/bin/sh -c " );
00490         } else if (err != KShell::NoError)
00491           goto synerr;
00492       }
00493       result << exec;
00494     } else {
00495       result += KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
00496       if (err == KShell::FoundMeta)
00497         result << "/bin/sh" << "-c" << exec;
00498       else if (err != KShell::NoError)
00499         goto synerr;
00500     }
00501   }
00502 
00503   return result;
00504 
00505  synerr:
00506   kdWarning() << "KRun: syntax error in command `" << _service.exec() << "', service `" << _service.name() << "'" << endl;
00507   return QStringList();
00508 }
00509 
00510 //static
00511 QString KRun::binaryName( const QString & execLine, bool removePath )
00512 {
00513   // Remove parameters and/or trailing spaces.
00514   QStringList args = KShell::splitArgs( execLine );
00515   for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it)
00516     if (!(*it).contains('='))
00517       // Remove path if wanted
00518       return removePath ? (*it).mid((*it).findRev('/') + 1) : *it;
00519   return QString::null;
00520 }
00521 
00522 static pid_t runCommandInternal( KProcess* proc, const KService* service, const QString& binName,
00523     const QString &execName, const QString & iconName )
00524 {
00525   if (service && !service->desktopEntryPath().isEmpty()
00526       && !KDesktopFile::isAuthorizedDesktopFile( service->desktopEntryPath() ))
00527   {
00528      kdWarning() << "No authorization to execute " << service->desktopEntryPath() << endl;
00529      KMessageBox::sorry(0, i18n("You are not authorized to execute this file."));
00530      return 0;
00531   }
00532   QString bin = KRun::binaryName( binName, true );
00533 #ifdef Q_WS_X11 // Startup notification doesn't work with QT/E, service isn't needed without Startup notification
00534   bool silent;
00535   QCString wmclass;
00536   KStartupInfoId id;
00537   bool startup_notify = KRun::checkStartupNotify( binName, service, &silent, &wmclass );
00538   if( startup_notify )
00539   {
00540       id.initId();
00541       id.setupStartupEnv();
00542       KStartupInfoData data;
00543       data.setHostname();
00544       data.setBin( bin );
00545       if( !execName.isEmpty())
00546           data.setName( execName );
00547       else if( service && !service->name().isEmpty())
00548           data.setName( service->name());
00549       data.setDescription( i18n( "Launching %1" ).arg( data.name()));
00550       if( !iconName.isEmpty())
00551           data.setIcon( iconName );
00552       else if( service && !service->icon().isEmpty())
00553           data.setIcon( service->icon());
00554       if( !wmclass.isEmpty())
00555           data.setWMClass( wmclass );
00556       if( silent )
00557           data.setSilent( KStartupInfoData::Yes );
00558       data.setDesktop( KWin::currentDesktop());
00559       KStartupInfo::sendStartup( id, data );
00560   }
00561   pid_t pid = KProcessRunner::run( proc, binName, id );
00562   if( startup_notify && pid )
00563   {
00564       KStartupInfoData data;
00565       data.addPid( pid );
00566       KStartupInfo::sendChange( id, data );
00567       KStartupInfo::resetStartupEnv();
00568   }
00569   return pid;
00570 #else
00571   Q_UNUSED( execName );
00572   Q_UNUSED( iconName );
00573   return KProcessRunner::run( proc, bin );
00574 #endif
00575 }
00576 
00577 // This code is also used in klauncher.
00578 bool KRun::checkStartupNotify( const QString& /*binName*/, const KService* service, bool* silent_arg, QCString* wmclass_arg )
00579 {
00580   bool silent = false;
00581   QCString wmclass;
00582   if( service && service->property( "StartupNotify" ).isValid())
00583   {
00584       silent = !service->property( "StartupNotify" ).toBool();
00585       wmclass = service->property( "StartupWMClass" ).toString().latin1();
00586   }
00587   else if( service && service->property( "X-KDE-StartupNotify" ).isValid())
00588   {
00589       silent = !service->property( "X-KDE-StartupNotify" ).toBool();
00590       wmclass = service->property( "X-KDE-WMClass" ).toString().latin1();
00591   }
00592   else // non-compliant app
00593   {
00594       if( service )
00595       {
00596           if( service->type() == "Application" )
00597               wmclass = "0"; // doesn't have .desktop entries needed, start as non-compliant
00598           else
00599               return false; // no startup notification at all
00600       }
00601       else
00602       { // Create startup notification even for apps for which there shouldn't be any,
00603         // just without any visual feedback. This will ensure they'll be positioned on the proper
00604         // virtual desktop, and will get user timestamp from the ASN ID.
00605           wmclass = "0";
00606           silent = true;
00607       }
00608   }
00609   if( silent_arg != NULL )
00610       *silent_arg = silent;
00611   if( wmclass_arg != NULL )
00612       *wmclass_arg = wmclass;
00613   return true;
00614 }
00615 
00616 static pid_t runTempService( const KService& _service, const KURL::List& _urls, bool tempFiles )
00617 {
00618   if (!_urls.isEmpty()) {
00619     kdDebug(7010) << "runTempService: first url " << _urls.first().url() << endl;
00620   }
00621 
00622   QStringList args;
00623   if ((_urls.count() > 1) && !_service.allowMultipleFiles())
00624   {
00625       // We need to launch the application N times. That sucks.
00626       // We ignore the result for application 2 to N.
00627       // For the first file we launch the application in the
00628       // usual way. The reported result is based on this
00629       // application.
00630       KURL::List::ConstIterator it = _urls.begin();
00631       while(++it != _urls.end())
00632       {
00633          KURL::List singleUrl;
00634          singleUrl.append(*it);
00635          runTempService( _service, singleUrl, tempFiles );
00636       }
00637       KURL::List singleUrl;
00638       singleUrl.append(_urls.first());
00639       args = KRun::processDesktopExec(_service, singleUrl, false, tempFiles);
00640   }
00641   else
00642   {
00643       args = KRun::processDesktopExec(_service, _urls, false, tempFiles);
00644   }
00645   kdDebug(7010) << "runTempService: KProcess args=" << args << endl;
00646 
00647   KProcess * proc = new KProcess;
00648   *proc << args;
00649 
00650   if (!_service.path().isEmpty())
00651      proc->setWorkingDirectory(_service.path());
00652 
00653   return runCommandInternal( proc, &_service, KRun::binaryName( _service.exec(), false ),
00654                              _service.name(), _service.icon() );
00655 }
00656 
00657 // BIC merge with method below
00658 pid_t KRun::run( const KService& _service, const KURL::List& _urls )
00659 {
00660     return run( _service, _urls, false );
00661 }
00662 
00663 pid_t KRun::run( const KService& _service, const KURL::List& _urls, bool tempFiles )
00664 {
00665   if (!_service.desktopEntryPath().isEmpty() &&
00666       !KDesktopFile::isAuthorizedDesktopFile( _service.desktopEntryPath()))
00667   {
00668      kdWarning() << "No authorization to execute " << _service.desktopEntryPath() << endl;
00669      KMessageBox::sorry(0, i18n("You are not authorized to execute this service."));
00670      return 0;
00671   }
00672 
00673   if ( !tempFiles )
00674   {
00675       // Remember we opened those urls, for the "recent documents" menu in kicker
00676       KURL::List::ConstIterator it = _urls.begin();
00677       for(; it != _urls.end(); ++it) {
00678           //kdDebug(7010) << "KRecentDocument::adding " << (*it).url() << endl;
00679           KRecentDocument::add( *it, _service.desktopEntryName() );
00680       }
00681   }
00682 
00683   if ( tempFiles || _service.desktopEntryPath().isEmpty())
00684   {
00685      return runTempService(_service, _urls, tempFiles);
00686   }
00687 
00688   kdDebug(7010) << "KRun::run " << _service.desktopEntryPath() << endl;
00689 
00690   if (!_urls.isEmpty()) {
00691     kdDebug(7010) << "First url " << _urls.first().url() << endl;
00692   }
00693 
00694   QString error;
00695   int pid = 0;
00696 
00697   int i = KApplication::startServiceByDesktopPath(
00698         _service.desktopEntryPath(), _urls.toStringList(), &error, 0L, &pid
00699         );
00700 
00701   if (i != 0)
00702   {
00703      kdDebug(7010) << error << endl;
00704      KMessageBox::sorry( 0L, error );
00705      return 0;
00706   }
00707 
00708   kdDebug(7010) << "startServiceByDesktopPath worked fine" << endl;
00709   return (pid_t) pid;
00710 }
00711 
00712 
00713 pid_t KRun::run( const QString& _exec, const KURL::List& _urls, const QString& _name,
00714                 const QString& _icon, const QString&, const QString&)
00715 {
00716   KService::Ptr service = new KService(_name, _exec, _icon);
00717 
00718   return run(*service, _urls);
00719 }
00720 
00721 pid_t KRun::runCommand( QString cmd )
00722 {
00723   return KRun::runCommand( cmd, QString::null, QString::null );
00724 }
00725 
00726 pid_t KRun::runCommand( const QString& cmd, const QString &execName, const QString & iconName )
00727 {
00728   kdDebug(7010) << "runCommand " << cmd << "," << execName << endl;
00729   KProcess * proc = new KProcess;
00730   proc->setUseShell(true);
00731   *proc << cmd;
00732   KService::Ptr service = KService::serviceByDesktopName( binaryName( execName, true ) );
00733   return runCommandInternal( proc, service.data(), binaryName( execName, false ), execName, iconName );
00734 }
00735 
00736 KRun::KRun( const KURL& url, mode_t mode, bool isLocalFile, bool showProgressInfo )
00737      :m_timer(0,"KRun::timer")
00738 {
00739   init (url, 0, mode, isLocalFile, showProgressInfo);
00740 }
00741 
00742 KRun::KRun( const KURL& url, QWidget* window, mode_t mode, bool isLocalFile,
00743             bool showProgressInfo )
00744      :m_timer(0,"KRun::timer")
00745 {
00746   init (url, window, mode, isLocalFile, showProgressInfo);
00747 }
00748 
00749 void KRun::init ( const KURL& url, QWidget* window, mode_t mode, bool isLocalFile,
00750                   bool showProgressInfo )
00751 {
00752   m_bFault = false;
00753   m_bAutoDelete = true;
00754   m_bProgressInfo = showProgressInfo;
00755   m_bFinished = false;
00756   m_job = 0L;
00757   m_strURL = url;
00758   m_bScanFile = false;
00759   m_bIsDirectory = false;
00760   m_bIsLocalFile = isLocalFile;
00761   m_mode = mode;
00762   d = new KRunPrivate;
00763   d->m_runExecutables = true;
00764   d->m_window = window;
00765   setEnableExternalBrowser(true);
00766 
00767   // Start the timer. This means we will return to the event
00768   // loop and do initialization afterwards.
00769   // Reason: We must complete the constructor before we do anything else.
00770   m_bInit = true;
00771   connect( &m_timer, SIGNAL( timeout() ), this, SLOT( slotTimeout() ) );
00772   m_timer.start( 0, true );
00773   kdDebug(7010) << " new KRun " << this << " " << url.prettyURL() << " timer=" << &m_timer << endl;
00774 
00775   kapp->ref();
00776 }
00777 
00778 void KRun::init()
00779 {
00780   kdDebug(7010) << "INIT called" << endl;
00781   if ( !m_strURL.isValid() )
00782   {
00783     d->m_showingError = true;
00784     KMessageBoxWrapper::error( d->m_window, i18n( "Malformed URL\n%1" ).arg( m_strURL.url() ) );
00785     d->m_showingError = false;
00786     m_bFault = true;
00787     m_bFinished = true;
00788     m_timer.start( 0, true );
00789     return;
00790   }
00791   if ( !kapp->authorizeURLAction( "open", KURL(), m_strURL))
00792   {
00793     QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, m_strURL.prettyURL());
00794     d->m_showingError = true;
00795     KMessageBoxWrapper::error( d->m_window, msg );
00796     d->m_showingError = false;
00797     m_bFault = true;
00798     m_bFinished = true;
00799     m_timer.start( 0, true );
00800     return;
00801   }
00802 
00803   if ( !m_bIsLocalFile && m_strURL.isLocalFile() )
00804     m_bIsLocalFile = true;
00805 
00806   QString exec;
00807   if (m_strURL.protocol().startsWith("http"))
00808   {
00809     exec = d->m_externalBrowser;
00810   }
00811 
00812   if ( m_bIsLocalFile )
00813   {
00814     if ( m_mode == 0 )
00815     {
00816       KDE_struct_stat buff;
00817       if ( KDE_stat( QFile::encodeName(m_strURL.path()), &buff ) == -1 )
00818       {
00819         d->m_showingError = true;
00820         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() ) );
00821         d->m_showingError = false;
00822         m_bFault = true;
00823         m_bFinished = true;
00824         m_timer.start( 0, true );
00825         return;
00826       }
00827       m_mode = buff.st_mode;
00828     }
00829 
00830     KMimeType::Ptr mime = KMimeType::findByURL( m_strURL, m_mode, m_bIsLocalFile );
00831     assert( mime != 0L );
00832     kdDebug(7010) << "MIME TYPE is " << mime->name() << endl;
00833     foundMimeType( mime->name() );
00834     return;
00835   }
00836   else if ( !exec.isEmpty() || KProtocolInfo::isHelperProtocol( m_strURL ) ) {
00837     kdDebug(7010) << "Helper protocol" << endl;
00838 
00839     bool ok = false;
00840     KURL::List urls;
00841     urls.append( m_strURL );
00842     if (exec.isEmpty())
00843     {
00844        exec = KProtocolInfo::exec( m_strURL.protocol() );
00845        if (exec.isEmpty())
00846        {
00847           foundMimeType(KProtocolInfo::defaultMimetype(m_strURL));
00848           return;
00849        }
00850        run( exec, urls );
00851        ok = true;
00852     }
00853     else if (exec.startsWith("!"))
00854     {
00855        exec = exec.mid(1); // Literal command
00856        exec += " %u";
00857        run( exec, urls );
00858        ok = true;
00859     }
00860     else
00861     {
00862        KService::Ptr service = KService::serviceByStorageId( exec );
00863        if (service)
00864        {
00865           run( *service, urls );
00866           ok = true;
00867        }
00868     }
00869 
00870     if (ok)
00871     {
00872        m_bFinished = true;
00873        // will emit the error and autodelete this
00874        m_timer.start( 0, true );
00875        return;
00876     }
00877   }
00878 
00879   // Did we already get the information that it is a directory ?
00880   if ( S_ISDIR( m_mode ) )
00881   {
00882     foundMimeType( "inode/directory" );
00883     return;
00884   }
00885 
00886   // Let's see whether it is a directory
00887 
00888   if ( !KProtocolInfo::supportsListing( m_strURL ) )
00889   {
00890     //kdDebug(7010) << "Protocol has no support for listing" << endl;
00891     // No support for listing => it can't be a directory (example: http)
00892     scanFile();
00893     return;
00894   }
00895 
00896   kdDebug(7010) << "Testing directory (stating)" << endl;
00897 
00898   // It may be a directory or a file, let's stat
00899   KIO::StatJob *job = KIO::stat( m_strURL, true, 0 /* no details */, m_bProgressInfo );
00900   job->setWindow (d->m_window);
00901   connect( job, SIGNAL( result( KIO::Job * ) ),
00902            this, SLOT( slotStatResult( KIO::Job * ) ) );
00903   m_job = job;
00904   kdDebug(7010) << " Job " << job << " is about stating " << m_strURL.url() << endl;
00905 }
00906 
00907 KRun::~KRun()
00908 {
00909   kdDebug(7010) << "KRun::~KRun() " << this << endl;
00910   m_timer.stop();
00911   killJob();
00912   kapp->deref();
00913   kdDebug(7010) << "KRun::~KRun() done " << this << endl;
00914   delete d;
00915 }
00916 
00917 void KRun::scanFile()
00918 {
00919   kdDebug(7010) << "###### KRun::scanFile " << m_strURL.url() << endl;
00920   // First, let's check for well-known extensions
00921   // Not when there is a query in the URL, in any case.
00922   if ( m_strURL.query().isEmpty() )
00923   {
00924     KMimeType::Ptr mime = KMimeType::findByURL( m_strURL );
00925     assert( mime != 0L );
00926     if ( mime->name() != "application/octet-stream" || m_bIsLocalFile )
00927     {
00928       kdDebug(7010) << "Scanfile: MIME TYPE is " << mime->name() << endl;
00929       foundMimeType( mime->name() );
00930       return;
00931     }
00932   }
00933 
00934   // No mimetype found, and the URL is not local  (or fast mode not allowed).
00935   // We need to apply the 'KIO' method, i.e. either asking the server or
00936   // getting some data out of the file, to know what mimetype it is.
00937 
00938   if ( !KProtocolInfo::supportsReading( m_strURL ) )
00939   {
00940     kdError(7010) << "#### NO SUPPORT FOR READING!" << endl;
00941     m_bFault = true;
00942     m_bFinished = true;
00943     m_timer.start( 0, true );
00944     return;
00945   }
00946   kdDebug(7010) << this << " Scanning file " << m_strURL.url() << endl;
00947 
00948   KIO::TransferJob *job = KIO::get( m_strURL, false /*reload*/, m_bProgressInfo );
00949   job->setWindow (d->m_window);
00950   connect(job, SIGNAL( result(KIO::Job *)),
00951           this, SLOT( slotScanFinished(KIO::Job *)));
00952   connect(job, SIGNAL( mimetype(KIO::Job *, const QString &)),
00953           this, SLOT( slotScanMimeType(KIO::Job *, const QString &)));
00954   m_job = job;
00955   kdDebug(7010) << " Job " << job << " is about getting from " << m_strURL.url() << endl;
00956 }
00957 
00958 void KRun::slotTimeout()
00959 {
00960   kdDebug(7010) << this << " slotTimeout called" << endl;
00961   if ( m_bInit )
00962   {
00963     m_bInit = false;
00964     init();
00965     return;
00966   }
00967 
00968   if ( m_bFault ) {
00969       emit error();
00970   }
00971   if ( m_bFinished ) {
00972       emit finished();
00973   }
00974   else
00975   {
00976   if ( m_bScanFile )
00977   {
00978     m_bScanFile = false;
00979     scanFile();
00980     return;
00981   }
00982   else if ( m_bIsDirectory )
00983   {
00984     m_bIsDirectory = false;
00985     foundMimeType( "inode/directory" );
00986     return;
00987   }
00988   }
00989 
00990   if ( m_bAutoDelete )
00991   {
00992     delete this;
00993     return;
00994   }
00995 }
00996 
00997 void KRun::slotStatResult( KIO::Job * job )
00998 {
00999   m_job = 0L;
01000   if (job->error())
01001   {
01002     d->m_showingError = true;
01003     kdError(7010) << this << " ERROR " << job->error() << " " << job->errorString() << endl;
01004     job->showErrorDialog();
01005     //kdDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us" << endl;
01006     d->m_showingError = false;
01007 
01008     m_bFault = true;
01009     m_bFinished = true;
01010 
01011     // will emit the error and autodelete this
01012     m_timer.start( 0, true );
01013 
01014   } else {
01015 
01016     kdDebug(7010) << "Finished" << endl;
01017     if(!dynamic_cast<KIO::StatJob*>(job))
01018         kdFatal() << "job is a " << typeid(*job).name() << " should be a StatJob" << endl;
01019 
01020     KIO::UDSEntry entry = ((KIO::StatJob*)job)->statResult();
01021     KIO::UDSEntry::ConstIterator it = entry.begin();
01022     for( ; it != entry.end(); it++ ) {
01023         if ( (*it).m_uds == KIO::UDS_FILE_TYPE )
01024         {
01025             if ( S_ISDIR( (mode_t)((*it).m_long) ) )
01026                 m_bIsDirectory = true; // it's a dir
01027             else
01028                 m_bScanFile = true; // it's a file
01029         }
01030         else if ( (*it).m_uds == KIO::UDS_MIME_TYPE ) // mimetype already known? (e.g. print:/manager)
01031         {
01032             foundMimeType( (*it).m_str );
01033             m_bFinished = true;
01034         }
01035     }
01036     // We should have found something
01037     assert ( m_bScanFile || m_bIsDirectory );
01038 
01039     // Start the timer. Once we get the timer event this
01040     // protocol server is back in the pool and we can reuse it.
01041     // This gives better performance than starting a new slave
01042     m_timer.start( 0, true );
01043   }
01044 }
01045 
01046 void KRun::slotScanMimeType( KIO::Job *, const QString &mimetype )
01047 {
01048   if ( mimetype.isEmpty() )
01049     kdWarning(7010) << "KRun::slotScanFinished : MimetypeJob didn't find a mimetype! Probably a kioslave bug." << endl;
01050   foundMimeType( mimetype );
01051   m_job = 0;
01052 }
01053 
01054 void KRun::slotScanFinished( KIO::Job *job )
01055 {
01056   m_job = 0;
01057   if (job->error())
01058   {
01059     d->m_showingError = true;
01060     kdError(7010) << this << " ERROR (stat) : " << job->error() << " " << job->errorString() << endl;
01061     job->showErrorDialog();
01062     //kdDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us" << endl;
01063     d->m_showingError = false;
01064 
01065     m_bFault = true;
01066     m_bFinished = true;
01067 
01068     // will emit the error and autodelete this
01069     m_timer.start( 0, true );
01070   }
01071 }
01072 
01073 void KRun::foundMimeType( const QString& type )
01074 {
01075   kdDebug(7010) << "Resulting mime type is " << type << endl;
01076 
01077 /*
01078   // Automatically unzip stuff
01079 
01080   // Disabled since the new KIO doesn't have filters yet.
01081 
01082   if ( type == "application/x-gzip"  ||
01083        type == "application/x-bzip"  ||
01084        type == "application/x-bzip2"  )
01085   {
01086     KURL::List lst = KURL::split( m_strURL );
01087     if ( lst.isEmpty() )
01088     {
01089       QString tmp = i18n( "Malformed URL" );
01090       tmp += "\n";
01091       tmp += m_strURL.url();
01092       KMessageBoxWrapper::error( 0L, tmp );
01093       return;
01094     }
01095 
01096     if ( type == "application/x-gzip" )
01097       lst.prepend( KURL( "gzip:/decompress" ) );
01098     else if ( type == "application/x-bzip" )
01099       lst.prepend( KURL( "bzip:/decompress" ) );
01100     else if ( type == "application/x-bzip2" )
01101       lst.prepend( KURL( "bzip2:/decompress" ) );
01102     else if ( type == "application/x-tar" )
01103       lst.prepend( KURL( "tar:/" ) );
01104 
01105     // Move the HTML style reference to the leftmost URL
01106     KURL::List::Iterator it = lst.begin();
01107     ++it;
01108     (*lst.begin()).setRef( (*it).ref() );
01109     (*it).setRef( QString::null );
01110 
01111     // Create the new URL
01112     m_strURL = KURL::join( lst );
01113 
01114     kdDebug(7010) << "Now trying with " << debugString(m_strURL.url()) << endl;
01115 
01116     killJob();
01117 
01118     // We don't know if this is a file or a directory. Let's test this first.
01119     // (For instance a tar.gz is a directory contained inside a file)
01120     // It may be a directory or a file, let's stat
01121     KIO::StatJob *job = KIO::stat( m_strURL, m_bProgressInfo );
01122     connect( job, SIGNAL( result( KIO::Job * ) ),
01123              this, SLOT( slotStatResult( KIO::Job * ) ) );
01124     m_job = job;
01125 
01126     return;
01127   }
01128 */
01129   if (m_job && m_job->inherits("KIO::TransferJob"))
01130   {
01131      KIO::TransferJob *job = static_cast<KIO::TransferJob *>(m_job);
01132      job->putOnHold();
01133      KIO::Scheduler::publishSlaveOnHold();
01134      m_job = 0;
01135   }
01136 
01137   Q_ASSERT( !m_bFinished );
01138 
01139   // Suport for preferred service setting, see setPreferredService
01140   if ( !d->m_preferredService.isEmpty() ) {
01141       kdDebug(7010) << "Attempting to open with preferred service: " << d->m_preferredService << endl;
01142       KService::Ptr serv = KService::serviceByDesktopName( d->m_preferredService );
01143       if ( serv && serv->hasServiceType( type ) )
01144       {
01145           KURL::List lst;
01146           lst.append( m_strURL );
01147           m_bFinished = KRun::run( *serv, lst );
01152       }
01153   }
01154 
01155   if (!m_bFinished && KRun::runURL( m_strURL, type, false, d->m_runExecutables )){
01156     m_bFinished = true;
01157   }
01158   else{
01159     m_bFinished = true;
01160      m_bFault = true;
01161   }
01162 
01163   m_timer.start( 0, true );
01164 }
01165 
01166 void KRun::killJob()
01167 {
01168   if ( m_job )
01169   {
01170     kdDebug(7010) << "KRun::killJob run=" << this << " m_job=" << m_job << endl;
01171     m_job->kill();
01172     m_job = 0L;
01173   }
01174 }
01175 
01176 void KRun::abort()
01177 {
01178   kdDebug(7010) << "KRun::abort " << this << " m_showingError=" << d->m_showingError << endl;
01179   killJob();
01180   // If we're showing an error message box, the rest will be done
01181   // after closing the msgbox -> don't autodelete nor emit signals now.
01182   if ( d->m_showingError )
01183     return;
01184   m_bFault = true;
01185   m_bFinished = true;
01186   m_bInit = false;
01187   m_bScanFile = false;
01188 
01189   // will emit the error and autodelete this
01190   m_timer.start( 0, true );
01191 }
01192 
01193 void KRun::setEnableExternalBrowser(bool b)
01194 {
01195    if (b)
01196       d->m_externalBrowser = KConfigGroup(KGlobal::config(), "General").readEntry("BrowserApplication");
01197    else
01198       d->m_externalBrowser = QString::null;
01199 }
01200 
01201 void KRun::setPreferredService( const QString& desktopEntryName )
01202 {
01203     d->m_preferredService = desktopEntryName;
01204 }
01205 
01206 void KRun::setRunExecutables(bool b)
01207 {
01208     d->m_runExecutables = b;
01209 }
01210 
01211 bool KRun::isExecutable( const QString& serviceType )
01212 {
01213     return ( serviceType == "application/x-desktop" ||
01214              serviceType == "application/x-executable" ||
01215              serviceType == "application/x-msdos-program" ||
01216              serviceType == "application/x-shellscript" );
01217 }
01218 
01219 /****************/
01220 
01221 pid_t
01222 KProcessRunner::run(KProcess * p, const QString & binName)
01223 {
01224   return (new KProcessRunner(p, binName))->pid();
01225 }
01226 
01227 #ifdef Q_WS_X11
01228 pid_t
01229 KProcessRunner::run(KProcess * p, const QString & binName, const KStartupInfoId& id )
01230 {
01231   return (new KProcessRunner(p, binName, id))->pid();
01232 }
01233 #endif
01234 
01235 KProcessRunner::KProcessRunner(KProcess * p, const QString & _binName )
01236   : QObject(),
01237     process_(p),
01238     binName( _binName )
01239 {
01240   QObject::connect(
01241       process_, SIGNAL(processExited(KProcess *)),
01242       this,     SLOT(slotProcessExited(KProcess *)));
01243 
01244   process_->start();
01245   if ( !process_->pid() )
01246       slotProcessExited( process_ );
01247 }
01248 
01249 #ifdef Q_WS_X11
01250 KProcessRunner::KProcessRunner(KProcess * p, const QString & _binName, const KStartupInfoId& id )
01251   : QObject(),
01252     process_(p),
01253     binName( _binName ),
01254     id_( id )
01255 {
01256   QObject::connect(
01257       process_, SIGNAL(processExited(KProcess *)),
01258       this,     SLOT(slotProcessExited(KProcess *)));
01259 
01260   process_->start();
01261   if ( !process_->pid() )
01262       slotProcessExited( process_ );
01263 }
01264 #endif
01265 
01266 KProcessRunner::~KProcessRunner()
01267 {
01268   delete process_;
01269 }
01270 
01271   pid_t
01272 KProcessRunner::pid() const
01273 {
01274   return process_->pid();
01275 }
01276 
01277   void
01278 KProcessRunner::slotProcessExited(KProcess * p)
01279 {
01280   if (p != process_)
01281     return; // Eh ?
01282 
01283   kdDebug(7010) << "slotProcessExited " << binName << endl;
01284   kdDebug(7010) << "normalExit " << process_->normalExit() << endl;
01285   kdDebug(7010) << "exitStatus " << process_->exitStatus() << endl;
01286   bool showErr = process_->normalExit()
01287                  && ( process_->exitStatus() == 127 || process_->exitStatus() == 1 );
01288   if ( !binName.isEmpty() && ( showErr || process_->pid() == 0 ) )
01289   {
01290     // Often we get 1 (zsh, csh) or 127 (ksh, bash) because the binary doesn't exist.
01291     // We can't just rely on that, but it's a good hint.
01292     // Before assuming its really so, we'll try to find the binName
01293     // relatively to current directory,  and then in the PATH.
01294     if ( !QFile( binName ).exists() && KStandardDirs::findExe( binName ).isEmpty() )
01295     {
01296       kapp->ref();
01297       KMessageBox::sorry( 0L, i18n("Could not find the program '%1'").arg( binName ) );
01298       kapp->deref();
01299     }
01300   }
01301 #ifdef Q_WS_X11
01302   if( !id_.none())
01303   {
01304       KStartupInfoData data;
01305       data.addPid( pid()); // announce this pid for the startup notification has finished
01306       data.setHostname();
01307       KStartupInfo::sendFinish( id_, data );
01308   }
01309 #endif
01310   deleteLater();
01311 }
01312 
01313 void KRun::virtual_hook( int, void* )
01314 { /*BASE::virtual_hook( id, data );*/ }
01315 
01316 #include "krun.moc"
KDE Logo
This file is part of the documentation for kio Library Version 3.4.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Jul 20 12:38:23 2006 by doxygen 1.4.4 written by Dimitri van Heesch, © 1997-2003