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