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