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