From aa6f2664078920097db372b8c74e46530b7dac11 Mon Sep 17 00:00:00 2001 From: Michael Haeckel Date: Sun, 6 May 2001 19:14:32 +0000 Subject: [PATCH] Reimplement filter app. Patch by Marc Mutz svn path=/trunk/kdenetwork/kmail/; revision=95487 --- kmfilteraction.cpp | 239 +++++++++++++++++++++++++++------------------ kmfilteraction.h | 21 +++- kmfilterdlg.cpp | 7 +- 3 files changed, 164 insertions(+), 103 deletions(-) diff --git a/kmfilteraction.cpp b/kmfilteraction.cpp index d5fefec0f..241d471cf 100644 --- a/kmfilteraction.cpp +++ b/kmfilteraction.cpp @@ -22,9 +22,12 @@ #include #include #include +#include #include #include +#include +#include // QT Template Library, needed for qHeapSort #include #include @@ -306,7 +309,55 @@ void KMFilterActionWithCommand::clearParamWidget( QWidget* paramWidget ) const KMFilterActionWithString::clearParamWidget( paramWidget ); } +QString KMFilterActionWithCommand::substituteCommandLineArgsFor( KMMessage *aMsg, QList & aTempFileList ) const +{ + QString result = mParameter; + QValueList argList; + QRegExp r( "%[0-9]+" ); + int start=-1, len=0; + bool OK = FALSE; + + // search for '%n' + while ( ( start = r.match( result, start + 1, &len ) ) > 0 ) { + // and save the encountered 'n' in a list. + int n = result.mid( start + 1, len - 1 ).toInt( &OK ); + if ( OK ) + argList.append( n ); + } + + // sort the list of n's + qHeapSort( argList ); + + // and use QString::arg to substitute filenames for the %n's. + int lastSeen = -1; + QString tempFileName; + KMMessagePart msgPart; + QValueList::Iterator it; + for ( it = argList.begin() ; it != argList.end() ; ++it ) { + // setup temp files with check for duplicate %n's + if ( (*it) != lastSeen ) { + KTempFile *tf = new KTempFile(); + if ( tf->status() != 0 ) { + delete tf; + kdDebug() << "KMFilterActionWithCommand: Could not create temp file!" << endl; + return QString::null; + } + tf->setAutoDelete(TRUE); + aTempFileList.append( tf ); + tempFileName = tf->name(); + aMsg->bodyPart( (*it), &msgPart ); + kByteArrayToFile( msgPart.bodyDecodedBinary(), tempFileName, + false, false, false ); + tf->close(); + } + // QString( "%0 and %1 and %1" ).arg( 0 ).arg( 1 ) + // returns "0 and 1 and %1", so we must call .arg as + // many times as there are %n's, regardless of their multiplicity. + result = result.arg( tempFileName ); + } + return result; +} //============================================================================= @@ -562,72 +613,54 @@ KMFilterAction::ReturnCode KMFilterActionRedirect::process(KMMessage* aMsg, bool class KMFilterActionExec : public KMFilterActionWithCommand { public: - KMFilterActionExec(const char* aName, const QString aLabel ); + KMFilterActionExec(); virtual ReturnCode process(KMMessage* msg, bool& stopIt) const; static KMFilterAction* newAction(void); - static void dummySigHandler(int); }; KMFilterAction* KMFilterActionExec::newAction(void) { - return (new KMFilterActionExec(0,0)); // ### -} - -KMFilterActionExec::KMFilterActionExec(const char* aName, const QString aLabel ) - : KMFilterActionWithCommand( aName ? aName : "execute" , aLabel ? aLabel : i18n("execute command") ) -{ + return (new KMFilterActionExec()); } -void KMFilterActionExec::dummySigHandler(int) +KMFilterActionExec::KMFilterActionExec() + : KMFilterActionWithCommand( "execute", i18n("execute command") ) { } KMFilterAction::ReturnCode KMFilterActionExec::process(KMMessage *aMsg, bool& /*stop*/) const { - // should use K{,Shell}Process.... - void (*oldSigHandler)(int); - int rc; - if (mParameter.isEmpty()) return ErrorButGoOn; - QString fullCmd = mParameter + " "; + if ( mParameter.isEmpty() ) + return ErrorButGoOn; + QList atmList; - KMMessagePart msgPart; - int i, nr; - while (fullCmd.contains("%")) - { - // this code seems broken for commands that - // specify e.g. %2 before %1. (mmutz) - i = fullCmd.find("%") + 1; - nr = fullCmd.mid(i, fullCmd.find(" ", i) - i).toInt(); - aMsg->bodyPart(nr, &msgPart); - KTempFile *atmTempFile = new KTempFile(); - atmList.append( atmTempFile ); - atmTempFile->setAutoDelete( true ); - kByteArrayToFile(msgPart.bodyDecodedBinary(), atmTempFile->name(), - false, false, false); - fullCmd = fullCmd.arg( atmTempFile->name() ); - } - oldSigHandler = signal(SIGALRM, &KMFilterActionExec::dummySigHandler); - alarm(30); - rc = system(fullCmd.left(fullCmd.length() - 1 )); - alarm(0); - signal(SIGALRM, oldSigHandler); - if (rc & 255) + atmList.setAutoDelete(TRUE); + + assert( aMsg ); + + QString commandLine = substituteCommandLineArgsFor( aMsg, atmList ); + + if ( commandLine.isEmpty() ) return ErrorButGoOn; - else - return GoOn; -} -// support suspended until proted to KProcess. -#define KMFILTERACTIONEXTFILTER_IS_BROKEN + KShellProcess shProc; + shProc << commandLine; + + if ( !shProc.start( KProcess::Block, KProcess::NoCommunication ) ) + return ErrorButGoOn; -#ifndef KMFILTERACTIONEXTFILTER_IS_BROKEN + if ( shProc.normalExit() && shProc.exitStatus() == 0 ) + return GoOn; + else + return ErrorButGoOn; +} //============================================================================= // KMFilterActionExtFilter - use external filter app // External message filter: executes a shell command with message // on stdin; altered message is expected on stdout. //============================================================================= -class KMFilterActionExtFilter: public KMFilterActionExec +class KMFilterActionExtFilter: public KMFilterActionWithCommand { public: KMFilterActionExtFilter(); @@ -641,67 +674,87 @@ KMFilterAction* KMFilterActionExtFilter::newAction(void) } KMFilterActionExtFilter::KMFilterActionExtFilter() - : KMFilterActionExec( "filter app", i18n("use external filter app") ) + : KMFilterActionWithCommand( "filter app", i18n("pipe through") ) { } -KMFilterAction::ReturnCode KMFilterActionExtFilter::process(KMMessage* aMsg, bool& stop) const +KMFilterAction::ReturnCode KMFilterActionExtFilter::process(KMMessage* aMsg, bool& ) const { - int len; - ReturnCode rc=Ok; - QString msgText, origCmd; - char buf[8192]; - FILE *fh; - bool ok = TRUE; - KTempFile inFile(locateLocal("tmp", "kmail-filter"), "in"); - KTempFile outFile(locateLocal("tmp", "kmail-filter"), "out"); + if ( mParameter.isEmpty() ) + return ErrorButGoOn; + + /////////////// + KTempFile * inFile = new KTempFile; + KTempFile * outFile = new KTempFile; + inFile->setAutoDelete(TRUE); + outFile->setAutoDelete(TRUE); + outFile->close(); + + QList atmList; + atmList.setAutoDelete(TRUE); + atmList.append( inFile ); + atmList.append( outFile ); + + assert( aMsg ); + + QString commandLine = substituteCommandLineArgsFor( aMsg , atmList ); + if ( commandLine.isEmpty() ) + return ErrorButGoOn; - if (mParameter.isEmpty()) return ErrorButGoOn; + // The parentheses force the creation of a subshell + // in which the user-specifed command is executed. + // This is to really catch all output of the command as well + // as to avoid clashes of our redirection with the ones + // the user may have specified. In the long run, we + // shouldn't be using tempfiles at all for this class, due + // to security aspects. (mmutz) + commandLine = QString( "(%1) <%2 >%3" ).arg( commandLine ).arg( inFile->name() ).arg( outFile->name() ); // write message to file - fh = inFile.fstream(); - if (fh) - { + QString msgText; + FILE *fh; + bool ok = TRUE; + + fh = inFile->fstream(); + if (fh) { msgText = aMsg->asString(); if (!fwrite(msgText, msgText.length(), 1, fh)) ok = FALSE; - inFile.close(); - } - else ok = FALSE; - - outFile.close(); - if (ok) - { - // execute filter - origCmd = mParameter; - mParameter += " <" + inFile.name() + " >" + outFile.name(); - rc = KMFilterActionExec::process(aMsg, stop); - mParameter = origCmd; - - // read altered message - fh = fopen(outFile.name(), "r"); - if (fh) - { - msgText = ""; - while (1) - { - len = fread(buf, 1, 1023, fh); - if (len <= 0) break; - buf[len] = 0; - msgText += buf; - } - outFile.close(); - if (!msgText.isEmpty()) aMsg->fromString(msgText); - } - else ok = FALSE; - } + inFile->close(); + } else ok = FALSE; - inFile.unlink(); - outFile.unlink(); + if ( !ok ) + return ErrorButGoOn; - return rc; -} + KShellProcess shProc; + shProc << commandLine; -#endif + if ( !shProc.start( KProcess::Block, KProcess::NoCommunication ) ) + return ErrorButGoOn; + + if ( !shProc.normalExit() || shProc.exitStatus() != 0 ) + return ErrorButGoOn; + + // read altered message + int len; + char buf[8192]; + + fh = fopen(outFile->name(), "r"); + if (fh) { + msgText = ""; + while (1) { + len = fread(buf, 1, 1023, fh); + if (len <= 0) break; + buf[len] = 0; + msgText += buf; + } + outFile->close(); + if ( !msgText.isEmpty() ) + aMsg->fromString( msgText ); + } else + return ErrorButGoOn; + + return GoOn; +} //============================================================================= @@ -721,9 +774,7 @@ void KMFilterActionDict::init(void) insert( KMFilterActionBounce::newAction ); #endif insert( KMFilterActionExec::newAction ); -#ifndef KMFILTERACTIONEXTFILTER_IS_BROKEN insert( KMFilterActionExtFilter::newAction ); -#endif // Register custom filter actions below this line. } // The int in the QDict constructor (23) must be a prime diff --git a/kmfilteraction.h b/kmfilteraction.h index 0fd114297..b81ccb746 100644 --- a/kmfilteraction.h +++ b/kmfilteraction.h @@ -19,7 +19,7 @@ class KMMessage; class QWidget; class KMFolder; class QStringList; - +class KTempFile; //========================================================= @@ -401,17 +401,22 @@ public: /** Abstract base class for KMail's filter actions that need a command - line as parameter, e.g. 'forward to'. Can create a @ref QListBox - (are there better widgtes in the depth of the kdelibs?) as + line as parameter, e.g. 'forward to'. Can create a @ref QLineEdit + (are there better widgets in the depths of the kdelibs?) as parameter widget. A subclass of this must provide at least implementations for the following methods: @li virtual @ref KMFilterAction::ReturnCodes @ref KMFilterAction::process @li static @ref KMFilterAction::newAction + The implementation of @ref KMFilterAction::process should take the + command line specified in mParameter, make all required + modifications and stream the resulting command line into @p + mProcess. Then you can start the command with @p mProcess.start(). + @short Abstract base class for filter actions with a command line as parameter. @author Marc Mutz , based upon work by Stefan Taferner - @see KMFilterActionWithString KMFilterAction KMFilter + @see KMFilterActionWithString KMFilterAction KMFilter KShellProcess */ class KMFilterActionWithCommand : public KMFilterActionWithString @@ -437,6 +442,14 @@ public: /** The filter action shall clear it's parameter widget's contents. */ virtual void clearParamWidget(QWidget* paramWidget) const; + + /** Substitutes various placeholders for data from the message + resp. for filenames containing that data. Currently, only %n is + supported, where n in an integer >= 0. %n gets substituted for + the name of a tempfile holding the n'th message part, with n=0 + meaning the body of the message. */ + virtual QString substituteCommandLineArgsFor( KMMessage *aMsg, QList & aTempFileList ) const; + }; diff --git a/kmfilterdlg.cpp b/kmfilterdlg.cpp index db50bedde..d7a1e1128 100644 --- a/kmfilterdlg.cpp +++ b/kmfilterdlg.cpp @@ -205,14 +205,13 @@ void KMFilterListBox::slotUpdateFilterName() void KMFilterListBox::slotApplyFilterChanges() { - setEnabled( FALSE ); - // unselect all filters: mListBox->selectAll( FALSE ); // maybe QListBox doesn't emit selected(-1) on unselect, // so we make sure the edit widgets receive an equivalent: emit resetWidgets(); mIdxSelItem = -1; + enableControls(); // by now all edit widgets should have written back // their widget's data into our filter list. @@ -235,10 +234,8 @@ void KMFilterListBox::slotApplyFilterChanges() delete f; } - // allow usage of the filters agin. + // allow usage of the filters again. fm->endUpdate(); - setEnabled( TRUE ); - fm->writeConfig(); }