kmail

vacation.cpp

Go to the documentation of this file.
00001 /*  -*- c++ -*-
00002     vacation.cpp
00003 
00004     KMail, the KDE mail client.
00005     Copyright (c) 2002 Marc Mutz <mutz@kde.org>
00006 
00007     This program is free software; you can redistribute it and/or
00008     modify it under the terms of the GNU General Public License,
00009     version 2.0, as published by the Free Software Foundation.
00010     You should have received a copy of the GNU General Public License
00011     along with this program; if not, write to the Free Software Foundation,
00012     Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US
00013 */
00014 
00015 #ifdef HAVE_CONFIG_H
00016 #include <config.h>
00017 #endif
00018 
00019 #include "vacation.h"
00020 #include <limits.h>
00021 
00022 #include "vacationdialog.h"
00023 #include "sievejob.h"
00024 using KMail::SieveJob;
00025 #include "kmkernel.h"
00026 #include "kmmainwidget.h"
00027 #include "accountmanager.h"
00028 using KMail::AccountManager;
00029 #include "kmacctimap.h"
00030 #include "kmmessage.h"
00031 #include "globalsettings.h"
00032 #include <libkpimidentities/identitymanager.h>
00033 #include <libkpimidentities/identity.h>
00034 
00035 #include <kmime_header_parsing.h>
00036 using KMime::Types::AddrSpecList;
00037 
00038 #include <ksieve/parser.h>
00039 #include <ksieve/scriptbuilder.h>
00040 #include <ksieve/error.h>
00041 
00042 #include <klocale.h>
00043 #include <kmessagebox.h>
00044 #include <kdebug.h>
00045 
00046 #include <qdatetime.h>
00047 
00048 #include <cassert>
00049 #include <vector>
00050 #include <map>
00051 #include <set>
00052 
00053 namespace KSieveExt {
00054 
00055   class MultiScriptBuilder : public KSieve::ScriptBuilder {
00056     std::vector<KSieve::ScriptBuilder*> mBuilders;
00057   public:
00058     MultiScriptBuilder() : KSieve::ScriptBuilder() {}
00059     MultiScriptBuilder( KSieve::ScriptBuilder * sb1 )
00060       : KSieve::ScriptBuilder(), mBuilders( 1 )
00061     {
00062       mBuilders[0] = sb1;
00063       assert( sb1 );
00064     }
00065     MultiScriptBuilder( KSieve::ScriptBuilder * sb1,
00066                         KSieve::ScriptBuilder * sb2 )
00067       : KSieve::ScriptBuilder(), mBuilders( 2 )
00068     {
00069       mBuilders[0] = sb1;
00070       mBuilders[1] = sb2;
00071       assert( sb1 ); assert( sb2 );
00072     }
00073     MultiScriptBuilder( KSieve::ScriptBuilder * sb1,
00074                         KSieve::ScriptBuilder * sb2,
00075                         KSieve::ScriptBuilder * sb3 )
00076       : KSieve::ScriptBuilder(), mBuilders( 3 )
00077     {
00078       mBuilders[0] = sb1;
00079       mBuilders[1] = sb2;
00080       mBuilders[2] = sb3;
00081       assert( sb1 ); assert( sb2 ); assert( sb3 );
00082     }
00083     ~MultiScriptBuilder() {}
00084   private:
00085 #ifdef FOREACH
00086 #undef FOREACH
00087 #endif
00088 #define FOREACH for ( std::vector<KSieve::ScriptBuilder*>::const_iterator it = mBuilders.begin(), end = mBuilders.end() ; it != end ; ++it ) (*it)->
00089     void commandStart( const QString & identifier ) { FOREACH commandStart( identifier ); }
00090     void commandEnd() { FOREACH commandEnd(); }
00091     void testStart( const QString & identifier ) { FOREACH testStart( identifier ); }
00092     void testEnd() { FOREACH testEnd(); }
00093     void testListStart() { FOREACH testListStart(); }
00094     void testListEnd() { FOREACH testListEnd(); }
00095     void blockStart() { FOREACH blockStart(); }
00096     void blockEnd() { FOREACH blockEnd(); }
00097     void hashComment( const QString & comment ) { FOREACH hashComment( comment ); }
00098     void bracketComment( const QString & comment ) { FOREACH bracketComment( comment ); }
00099     void lineFeed() { FOREACH lineFeed(); }
00100     void error( const KSieve::Error & e ) { FOREACH error( e ); }
00101     void finished() { FOREACH finished(); }
00102     void taggedArgument( const QString & tag ) { FOREACH taggedArgument( tag ); }
00103     void stringArgument( const QString & string, bool multiline, const QString & fixme ) { FOREACH stringArgument( string, multiline, fixme ); }
00104     void numberArgument( unsigned long number, char quantifier ) { FOREACH numberArgument( number, quantifier ); }
00105     void stringListArgumentStart() { FOREACH stringListArgumentStart(); }
00106     void stringListEntry( const QString & string, bool multiline, const QString & fixme) { FOREACH stringListEntry( string, multiline, fixme ); }
00107     void stringListArgumentEnd() { FOREACH stringListArgumentEnd(); }
00108 #undef FOREACH
00109   };
00110 
00111 }
00112 
00113 namespace {
00114 
00115   class GenericInformationExtractor : public KSieve::ScriptBuilder {
00116   public:
00117     enum BuilderMethod {
00118       Any,
00119       TaggedArgument,
00120       StringArgument,
00121       NumberArgument,
00122       CommandStart,
00123       CommandEnd,
00124       TestStart,
00125       TestEnd,
00126       TestListStart,
00127       TestListEnd,
00128       BlockStart,
00129       BlockEnd,
00130       StringListArgumentStart,
00131       StringListEntry,
00132       StringListArgumentEnd
00133     };
00134 
00135     struct StateNode {
00136       // expectation:
00137       int depth;
00138       BuilderMethod method;
00139       const char * string;
00140       // actions:
00141       int if_found;
00142       int if_not_found;
00143       const char * save_tag;
00144     };
00145 
00146     const std::vector<StateNode> mNodes;
00147     std::map<QString,QString> mResults;
00148     std::set<unsigned int> mRecursionGuard;
00149     unsigned int mState;
00150     int mNestingDepth;
00151 
00152   public:
00153     GenericInformationExtractor( const std::vector<StateNode> & nodes )
00154       : KSieve::ScriptBuilder(), mNodes( nodes ), mState( 0 ), mNestingDepth( 0 ) {}
00155 
00156     const std::map<QString,QString> & results() const { return mResults; }
00157 
00158   private:
00159     void process( BuilderMethod method, const QString & string=QString::null ) {
00160       doProcess( method, string );
00161       mRecursionGuard.clear();
00162     }
00163     void doProcess( BuilderMethod method, const QString & string ) {
00164       mRecursionGuard.insert( mState );
00165       bool found = true;
00166       const StateNode & expected = mNodes[mState];
00167       if ( expected.depth != -1 && mNestingDepth != expected.depth )
00168         found = false;
00169       if ( expected.method != Any && method != expected.method )
00170         found = false;
00171       if ( const char * str = expected.string )
00172         if ( string.lower() != QString::fromUtf8( str ).lower() )
00173           found = false;
00174       kdDebug(5006) << ( found ? "found:     " : "not found: " )
00175                     << mState << " -> "
00176                     << ( found ? expected.if_found : expected.if_not_found ) << endl;
00177       mState = found ? expected.if_found : expected.if_not_found ;
00178       assert( mState < mNodes.size() );
00179       if ( found )
00180         if ( const char * save_tag = expected.save_tag )
00181           mResults[save_tag] = string;
00182       if ( !found && !mRecursionGuard.count( mState ) ) {
00183         doProcess( method, string );
00184       }
00185     }
00186     void commandStart( const QString & identifier ) { kdDebug(5006) << k_funcinfo << endl; process( CommandStart, identifier ); }
00187     void commandEnd() { kdDebug(5006) << k_funcinfo << endl; process( CommandEnd ); }
00188     void testStart( const QString & identifier ) { kdDebug(5006) << k_funcinfo << endl; process( TestStart, identifier ); }
00189     void testEnd() { kdDebug(5006) << k_funcinfo << endl; process( TestEnd ); }
00190     void testListStart() { kdDebug(5006) << k_funcinfo << endl; process( TestListStart ); }
00191     void testListEnd() { kdDebug(5006) << k_funcinfo << endl; process( TestListEnd ); }
00192     void blockStart() { kdDebug(5006) << k_funcinfo << endl; process( BlockStart ); ++mNestingDepth; }
00193     void blockEnd() { kdDebug(5006) << k_funcinfo << endl; --mNestingDepth; process( BlockEnd ); }
00194     void hashComment( const QString & ) { kdDebug(5006) << k_funcinfo << endl; }
00195     void bracketComment( const QString & ) { kdDebug(5006) << k_funcinfo << endl; }
00196     void lineFeed() { kdDebug(5006) << k_funcinfo << endl; }
00197     void error( const KSieve::Error & ) {
00198       kdDebug(5006) << k_funcinfo << endl;
00199       mState = 0;
00200     }
00201     void finished() { kdDebug(5006) << k_funcinfo << endl; }
00202 
00203     void taggedArgument( const QString & tag ) { kdDebug(5006) << k_funcinfo << endl; process( TaggedArgument, tag ); }
00204     void stringArgument( const QString & string, bool, const QString & ) { kdDebug(5006) << k_funcinfo << endl; process( StringArgument, string ); }
00205     void numberArgument( unsigned long number, char ) { kdDebug(5006) << k_funcinfo << endl; process( NumberArgument, QString::number( number ) ); }
00206     void stringListArgumentStart() { kdDebug(5006) << k_funcinfo << endl; process( StringListArgumentStart ); }
00207     void stringListEntry( const QString & string, bool, const QString & ) { kdDebug(5006) << k_funcinfo << endl; process( StringListEntry, string ); }
00208     void stringListArgumentEnd() { kdDebug(5006) << k_funcinfo << endl; process( StringListArgumentEnd ); }
00209   };
00210 
00211   typedef GenericInformationExtractor GIE;
00212   static const GenericInformationExtractor::StateNode spamNodes[] = {
00213     { 0, GIE::CommandStart, "if",  1, 0, 0 },              // 0
00214     { 0,   GIE::TestStart, "header", 2, 0, 0 },            // 1
00215     { 0,     GIE::TaggedArgument, "contains", 3, 0, 0 },   // 2
00216 
00217     // accept both string and string-list:
00218     { 0,     GIE::StringArgument, "x-spam-flag", 9, 4, "x-spam-flag" },    // 3
00219     { 0,     GIE::StringListArgumentStart, 0, 5, 0, 0 },                   // 4
00220     { 0,       GIE::StringListEntry, "x-spam-flag", 6, 7, "x-spam-flag" }, // 5
00221     { 0,       GIE::StringListEntry, 0, 6, 8, 0 },                         // 6
00222     { 0,     GIE::StringListArgumentEnd, 0, 0, 5, 0 },                     // 7
00223     { 0,     GIE::StringListArgumentEnd, 0, 9, 0, 0 },                     // 8
00224 
00225     // accept both string and string-list:
00226     { 0,     GIE::StringArgument, "yes", 15, 10, "spam-flag-yes" },    // 9
00227     { 0,     GIE::StringListArgumentStart, 0, 11, 0, 0 },              // 10
00228     { 0,       GIE::StringListEntry, "yes", 12, 13, "spam-flag-yes" }, // 11
00229     { 0,       GIE::StringListEntry, 0, 12, 14, 0 },                   // 12
00230     { 0,     GIE::StringListArgumentEnd, 0, 0, 11, 0 },                // 13
00231     { 0,     GIE::StringListArgumentEnd, 0, 15, 0, 0 },                // 14
00232 
00233     { 0,   GIE::TestEnd, 0, 16, 0, 0 }, // 15
00234 
00235     // block of command, find "stop", take nested if's into account:
00236     { 0,   GIE::BlockStart, 0, 17, 0, 0 },                // 16
00237     { 1,     GIE::CommandStart, "stop", 20, 19, "stop" }, // 17
00238     { -1,    GIE::Any, 0, 17, 0, 0 },                     // 18
00239     { 0,   GIE::BlockEnd, 0, 0, 18, 0 },                  // 19
00240 
00241     { -1, GIE::Any, 0, 20, 20, 0 }, // 20 end state
00242   };
00243   static const unsigned int numSpamNodes = sizeof spamNodes / sizeof *spamNodes ;
00244 
00245   class SpamDataExtractor : public GenericInformationExtractor {
00246   public:
00247     SpamDataExtractor()
00248       : GenericInformationExtractor( std::vector<StateNode>( spamNodes, spamNodes + numSpamNodes ) )
00249     {
00250 
00251     }
00252 
00253     bool found() const {
00254       return mResults.count( "x-spam-flag" ) &&
00255         mResults.count( "spam-flag-yes" ) &&
00256         mResults.count( "stop" ) ;
00257     }
00258   };
00259 
00260   // to understand this table, study the output of
00261   // libksieve/tests/parsertest
00262   //   'if not address :domain :contains ["from"] ["mydomain.org"] { keep; stop; }'
00263   static const GenericInformationExtractor::StateNode domainNodes[] = {
00264     { 0, GIE::CommandStart, "if", 1, 0, 0 },       // 0
00265     { 0,   GIE::TestStart, "not", 2, 0, 0, },      // 1
00266     { 0,     GIE::TestStart, "address", 3, 0, 0 }, // 2
00267 
00268     // :domain and :contains in arbitrary order:
00269     { 0,       GIE::TaggedArgument, "domain", 4, 5, 0 },     // 3
00270     { 0,       GIE::TaggedArgument, "contains", 7, 0, 0 },   // 4
00271     { 0,       GIE::TaggedArgument, "contains", 6, 0, 0 },   // 5
00272     { 0,       GIE::TaggedArgument, "domain", 7, 0, 0 },     // 6
00273 
00274     // accept both string and string-list:
00275     { 0,       GIE::StringArgument, "from", 13, 8, "from" },     // 7
00276     { 0,       GIE::StringListArgumentStart, 0, 9, 0, 0 },       // 8
00277     { 0,         GIE::StringListEntry, "from", 10, 11, "from" }, // 9
00278     { 0,         GIE::StringListEntry, 0, 10, 12, 0 },           // 10
00279     { 0,       GIE::StringListArgumentEnd, 0, 0, 9, 0 },         // 11
00280     { 0,       GIE::StringListArgumentEnd, 0, 13, 0, 0 },        // 12
00281 
00282     // string: save, string-list: save last
00283     { 0,       GIE::StringArgument, 0, 17, 14, "domainName" },    // 13
00284     { 0,       GIE::StringListArgumentStart, 0, 15, 0, 0 },       // 14
00285     { 0,         GIE::StringListEntry, 0, 15, 16, "domainName" }, // 15
00286     { 0,       GIE::StringListArgumentEnd, 0, 17, 0, 0 },         // 16
00287 
00288     { 0,     GIE::TestEnd, 0, 18, 0, 0 },  // 17
00289     { 0,   GIE::TestEnd, 0, 19, 0, 0 },    // 18
00290 
00291     // block of commands, find "stop", take nested if's into account:
00292     { 0,   GIE::BlockStart, 0, 20, 0, 0 },                 // 19
00293     { 1,     GIE::CommandStart, "stop", 23, 22, "stop" },  // 20
00294     { -1,    GIE::Any, 0, 20, 0, 0 },                      // 21
00295     { 0,   GIE::BlockEnd, 0, 0, 21, 0 },                   // 22
00296 
00297     { -1, GIE::Any, 0, 23, 23, 0 }  // 23 end state
00298   };
00299   static const unsigned int numDomainNodes = sizeof domainNodes / sizeof *domainNodes ;
00300 
00301   class DomainRestrictionDataExtractor : public GenericInformationExtractor {
00302   public:
00303     DomainRestrictionDataExtractor()
00304       : GenericInformationExtractor( std::vector<StateNode>( domainNodes, domainNodes+numDomainNodes ) )
00305     {
00306 
00307     }
00308 
00309     QString domainName() /*not const, since map::op[] isn't const*/ {
00310       return mResults.count( "stop" ) && mResults.count( "from" )
00311         ? mResults["domainName"] : QString::null ;
00312     }
00313   };
00314 
00315   class VacationDataExtractor : public KSieve::ScriptBuilder {
00316     enum Context {
00317       None = 0,
00318       // command itself:
00319       VacationCommand,
00320       // tagged args:
00321       Days, Addresses
00322     };
00323   public:
00324     VacationDataExtractor()
00325       : KSieve::ScriptBuilder(),
00326     mContext( None ), mNotificationInterval( 0 )
00327     {
00328       kdDebug(5006) << "VacationDataExtractor instantiated" << endl;
00329     }
00330     virtual ~VacationDataExtractor() {}
00331 
00332     int notificationInterval() const { return mNotificationInterval; }
00333     const QString & messageText() const { return mMessageText; }
00334     const QStringList & aliases() const { return mAliases; }
00335 
00336   private:
00337     void commandStart( const QString & identifier ) {
00338       kdDebug( 5006 ) << "VacationDataExtractor::commandStart( \"" << identifier << "\" )" << endl;
00339       if ( identifier != "vacation" )
00340     return;
00341       reset();
00342       mContext = VacationCommand;
00343     }
00344 
00345     void commandEnd() {
00346       kdDebug( 5006 ) << "VacationDataExtractor::commandEnd()" << endl;
00347       mContext = None;
00348     }
00349 
00350     void testStart( const QString & ) {}
00351     void testEnd() {}
00352     void testListStart() {}
00353     void testListEnd() {}
00354     void blockStart() {}
00355     void blockEnd() {}
00356     void hashComment( const QString & ) {}
00357     void bracketComment( const QString & ) {}
00358     void lineFeed() {}
00359     void error( const KSieve::Error & e ) {
00360       kdDebug( 5006 ) << "VacationDataExtractor::error() ### "
00361               << e.asString() << " @ " << e.line() << "," << e.column()
00362               << endl;
00363     }
00364     void finished() {}
00365 
00366     void taggedArgument( const QString & tag ) {
00367       kdDebug( 5006 ) << "VacationDataExtractor::taggedArgument( \"" << tag << "\" )" << endl;
00368       if ( mContext != VacationCommand )
00369     return;
00370       if ( tag == "days" )
00371     mContext = Days;
00372       else if ( tag == "addresses" )
00373     mContext = Addresses;
00374     }
00375 
00376     void stringArgument( const QString & string, bool, const QString & ) {
00377       kdDebug( 5006 ) << "VacationDataExtractor::stringArgument( \"" << string << "\" )" << endl;
00378       if ( mContext == Addresses ) {
00379     mAliases.push_back( string );
00380     mContext = VacationCommand;
00381       } else if ( mContext == VacationCommand ) {
00382     mMessageText = string;
00383     mContext = VacationCommand;
00384       }
00385     }
00386 
00387     void numberArgument( unsigned long number, char ) {
00388       kdDebug( 5006 ) << "VacationDataExtractor::numberArgument( \"" << number << "\" )" << endl;
00389       if ( mContext != Days )
00390     return;
00391       if ( number > INT_MAX )
00392     mNotificationInterval = INT_MAX;
00393       else
00394     mNotificationInterval = number;
00395       mContext = VacationCommand;
00396     }
00397 
00398     void stringListArgumentStart() {}
00399     void stringListEntry( const QString & string, bool, const QString & ) {
00400       kdDebug( 5006 ) << "VacationDataExtractor::stringListEntry( \"" << string << "\" )" << endl;
00401       if ( mContext != Addresses )
00402     return;
00403       mAliases.push_back( string );
00404     }
00405     void stringListArgumentEnd() {
00406       kdDebug( 5006 ) << "VacationDataExtractor::stringListArgumentEnd()" << endl;
00407       if ( mContext != Addresses )
00408     return;
00409       mContext = VacationCommand;
00410     }
00411 
00412   private:
00413     Context mContext;
00414     int mNotificationInterval;
00415     QString mMessageText;
00416     QStringList mAliases;
00417 
00418     void reset() {
00419       kdDebug(5006) << "VacationDataExtractor::reset()" << endl;
00420       mContext = None;
00421       mNotificationInterval = 0;
00422       mAliases.clear();
00423       mMessageText = QString::null;
00424     }
00425   };
00426 
00427 }
00428 
00429 namespace KMail {
00430 
00431   Vacation::Vacation( QObject * parent, bool checkOnly, const char * name )
00432     : QObject( parent, name ), mSieveJob( 0 ), mDialog( 0 ), mWasActive( false ), mCheckOnly( checkOnly )
00433   {
00434     mUrl = findURL();
00435     kdDebug(5006) << "Vacation: found url \"" << mUrl.prettyURL() << "\"" << endl;
00436     if ( mUrl.isEmpty() ) // nothing to do...
00437       return;
00438     mSieveJob = SieveJob::get( mUrl, !checkOnly );
00439     connect( mSieveJob, SIGNAL(gotScript(KMail::SieveJob*,bool,const QString&,bool)),
00440          SLOT(slotGetResult(KMail::SieveJob*,bool,const QString&,bool)) );
00441   }
00442 
00443   Vacation::~Vacation() {
00444     if ( mSieveJob ) mSieveJob->kill(); mSieveJob = 0;
00445     delete mDialog; mDialog = 0;
00446     kdDebug(5006) << "~Vacation()" << endl;
00447   }
00448 
00449   static inline QString dotstuff( QString s ) {
00450     if ( s.startsWith( "." ) )
00451       return '.' + s.replace( "\n.", "\n.." );
00452     else
00453       return s.replace( "\n.", "\n.." );
00454   }
00455 
00456   QString Vacation::composeScript( const QString & messageText,
00457                    int notificationInterval,
00458                    const AddrSpecList & addrSpecs,
00459                                    bool sendForSpam, const QString & domain )
00460   {
00461     QString addressesArgument;
00462     QStringList aliases;
00463     if ( !addrSpecs.empty() ) {
00464       addressesArgument += ":addresses [ ";
00465       QStringList sl;
00466       for ( AddrSpecList::const_iterator it = addrSpecs.begin() ; it != addrSpecs.end() ; ++it ) {
00467     sl.push_back( '"' + (*it).asString().replace( '\\', "\\\\" ).replace( '"', "\\\"" ) + '"' );
00468     aliases.push_back( (*it).asString() );
00469       }
00470       addressesArgument += sl.join( ", " ) + " ] ";
00471     }
00472     QString script = QString::fromLatin1("require \"vacation\";\n\n" );
00473     if ( !sendForSpam )
00474       script += QString::fromLatin1( "if header :contains \"X-Spam-Flag\" \"YES\""
00475                                      " { keep; stop; }\n" ); // FIXME?
00476 
00477     if ( !domain.isEmpty() ) // FIXME
00478       script += QString::fromLatin1( "if not address :domain :contains \"from\" \"%1\" { keep; stop; }\n" ).arg( domain );
00479 
00480     script += "vacation ";
00481     script += addressesArgument;
00482     if ( notificationInterval > 0 )
00483       script += QString::fromLatin1(":days %1 ").arg( notificationInterval );
00484     script += QString::fromLatin1("text:\n");
00485     script += dotstuff( messageText.isEmpty() ? defaultMessageText() : messageText );
00486     script += QString::fromLatin1( "\n.\n;\n" );
00487     return script;
00488   }
00489 
00490   static KURL findUrlForAccount( const KMail::ImapAccountBase * a ) {
00491     assert( a );
00492     const SieveConfig sieve = a->sieveConfig();
00493     if ( !sieve.managesieveSupported() )
00494       return KURL();
00495     if ( sieve.reuseConfig() ) {
00496       // assemble Sieve url from the settings of the account:
00497       KURL u;
00498       u.setProtocol( "sieve" );
00499       u.setHost( a->host() );
00500       u.setUser( a->login() );
00501       u.setPass( a->passwd() );
00502       u.setPort( sieve.port() );
00503       u.setQuery( "x-mech=" + (a->auth() == "*" ? "PLAIN" : a->auth()) ); //translate IMAP LOGIN to PLAIN
00504       u.setFileName( sieve.vacationFileName() );
00505       return u;
00506     } else {
00507       KURL u = sieve.alternateURL();
00508       u.setFileName( sieve.vacationFileName() );
00509       return u;
00510     }
00511   }
00512 
00513   KURL Vacation::findURL() const {
00514     AccountManager * am = kmkernel->acctMgr();
00515     assert( am );
00516     for ( KMAccount * a = am->first() ; a ; a = am->next() )
00517       if ( KMail::ImapAccountBase * iab = dynamic_cast<KMail::ImapAccountBase*>( a ) ) {
00518         KURL u = findUrlForAccount( iab );
00519     if ( !u.isEmpty() )
00520       return u;
00521       }
00522     return KURL();
00523   }
00524 
00525   bool Vacation::parseScript( const QString & script, QString & messageText,
00526                   int & notificationInterval, QStringList & aliases,
00527                               bool & sendForSpam, QString & domainName ) {
00528     if ( script.stripWhiteSpace().isEmpty() ) {
00529       messageText = defaultMessageText();
00530       notificationInterval = defaultNotificationInterval();
00531       aliases = defaultMailAliases();
00532       sendForSpam = defaultSendForSpam();
00533       domainName = defaultDomainName();
00534       return true;
00535     }
00536 
00537     // The stripWhiteSpace() call below prevents parsing errors. The
00538     // slave somehow omits the last \n, which results in a lone \r at
00539     // the end, leading to a parse error.
00540     const QCString scriptUTF8 = script.stripWhiteSpace().utf8();
00541     kdDebug(5006) << "scriptUtf8 = \"" + scriptUTF8 + "\"" << endl;
00542     KSieve::Parser parser( scriptUTF8.begin(),
00543                scriptUTF8.begin() + scriptUTF8.length() );
00544     VacationDataExtractor vdx;
00545     SpamDataExtractor sdx;
00546     DomainRestrictionDataExtractor drdx;
00547     KSieveExt::MultiScriptBuilder tsb( &vdx, &sdx, &drdx );
00548     parser.setScriptBuilder( &tsb );
00549     if ( !parser.parse() )
00550       return false;
00551     messageText = vdx.messageText().stripWhiteSpace();
00552     notificationInterval = vdx.notificationInterval();
00553     aliases = vdx.aliases();
00554     if ( !GlobalSettings::allowOutOfOfficeUploadButNoSettings() ) {
00555       sendForSpam = !sdx.found();
00556       domainName = drdx.domainName();
00557     }
00558     return true;
00559   }
00560 
00561   QString Vacation::defaultMessageText() {
00562     return i18n("I am out of office till %1.\n"
00563         "\n"
00564         "In urgent cases, please contact Mrs. <vacation replacement>\n"
00565         "\n"
00566         "email: <email address of vacation replacement>\n"
00567         "phone: +49 711 1111 11\n"
00568         "fax.:  +49 711 1111 12\n"
00569         "\n"
00570         "Yours sincerely,\n"
00571         "-- <enter your name and email address here>\n")
00572       .arg( KGlobal::locale()->formatDate( QDate::currentDate().addDays( 1 ) ) );
00573   }
00574 
00575   int Vacation::defaultNotificationInterval() {
00576     return 7; // days
00577   }
00578 
00579   QStringList Vacation::defaultMailAliases() {
00580     QStringList sl;
00581     for ( KPIM::IdentityManager::ConstIterator it = kmkernel->identityManager()->begin() ;
00582       it != kmkernel->identityManager()->end() ; ++it )
00583       if ( !(*it).emailAddr().isEmpty() )
00584     sl.push_back( (*it).emailAddr() );
00585     return sl;
00586   }
00587 
00588   bool Vacation::defaultSendForSpam() {
00589     return GlobalSettings::outOfOfficeReactToSpam();
00590   }
00591 
00592   QString Vacation::defaultDomainName() {
00593     return GlobalSettings::outOfOfficeDomain();
00594   }
00595 
00596   void Vacation::slotGetResult( SieveJob * job, bool success,
00597                 const QString & script, bool active ) {
00598     kdDebug(5006) << "Vacation::slotGetResult( ??, " << success
00599           << ", ?, " << active << " )" << endl
00600           << "script:" << endl
00601           << script << endl;
00602     mSieveJob = 0; // job deletes itself after returning from this slot!
00603 
00604     if ( !mCheckOnly && mUrl.protocol() == "sieve" && !job->sieveCapabilities().isEmpty() &&
00605      !job->sieveCapabilities().contains("vacation") ) {
00606       KMessageBox::sorry( 0, i18n("Your server did not list \"vacation\" in "
00607                   "its list of supported Sieve extensions;\n"
00608                   "without it, KMail cannot install out-of-"
00609                   "office replies for you.\n"
00610                   "Please contact you system administrator.") );
00611       emit result( false );
00612       return;
00613     }
00614 
00615     if ( !mDialog && !mCheckOnly )
00616       mDialog = new VacationDialog( i18n("Configure \"Out of Office\" Replies"), 0, 0, false );
00617 
00618     QString messageText = defaultMessageText();
00619     int notificationInterval = defaultNotificationInterval();
00620     QStringList aliases = defaultMailAliases();
00621     bool sendForSpam = defaultSendForSpam();
00622     QString domainName = defaultDomainName();
00623     if ( !success ) active = false; // default to inactive
00624 
00625     if ( !mCheckOnly && ( !success || !parseScript( script, messageText, notificationInterval, aliases, sendForSpam, domainName ) ) )
00626       KMessageBox::information( 0, i18n("Someone (probably you) changed the "
00627                     "vacation script on the server.\n"
00628                     "KMail is no longer able to determine "
00629                     "the parameters for the autoreplies.\n"
00630                     "Default values will be used." ) );
00631 
00632     mWasActive = active;
00633     if ( mDialog ) {
00634       mDialog->setActivateVacation( active );
00635       mDialog->setMessageText( messageText );
00636       mDialog->setNotificationInterval( notificationInterval );
00637       mDialog->setMailAliases( aliases.join(", ") );
00638       mDialog->setSendForSpam( sendForSpam );
00639       mDialog->setDomainName( domainName );
00640       mDialog->enableDomainAndSendForSpam( !GlobalSettings::allowOutOfOfficeUploadButNoSettings() );
00641 
00642       connect( mDialog, SIGNAL(okClicked()), SLOT(slotDialogOk()) );
00643       connect( mDialog, SIGNAL(cancelClicked()), SLOT(slotDialogCancel()) );
00644       connect( mDialog, SIGNAL(defaultClicked()), SLOT(slotDialogDefaults()) );
00645 
00646       mDialog->show();
00647     }
00648 
00649     emit scriptActive( mWasActive );
00650     if ( mCheckOnly && mWasActive ) {
00651       if ( KMessageBox::questionYesNo( 0, i18n( "There is still an active out-of-office reply configured.\n"
00652                                         "Do you want to edit it?"), i18n("Out-of-office reply still active"),
00653                                         KGuiItem( i18n( "Edit"), "edit" ), KGuiItem( i18n("Ignore"), "button_cancel" ) )
00654            == KMessageBox::Yes ) {
00655         kmkernel->getKMMainWidget()->slotEditVacation();
00656       }
00657     }
00658   }
00659 
00660   void Vacation::slotDialogDefaults() {
00661     if ( !mDialog )
00662       return;
00663     mDialog->setActivateVacation( true );
00664     mDialog->setMessageText( defaultMessageText() );
00665     mDialog->setNotificationInterval( defaultNotificationInterval() );
00666     mDialog->setMailAliases( defaultMailAliases().join(", ") );
00667     mDialog->setSendForSpam( defaultSendForSpam() );
00668     mDialog->setDomainName( defaultDomainName() );
00669   }
00670 
00671   void Vacation::slotDialogOk() {
00672     kdDebug(5006) << "Vacation::slotDialogOk()" << endl;
00673     // compose a new script:
00674     const QString script = composeScript( mDialog->messageText(),
00675                     mDialog->notificationInterval(),
00676                     mDialog->mailAliases(),
00677                                     mDialog->sendForSpam(),
00678                                     mDialog->domainName() );
00679     const bool active = mDialog->activateVacation();
00680     emit scriptActive( active );
00681 
00682     kdDebug(5006) << "script:" << endl << script << endl;
00683 
00684     // and commit the dialog's settings to the server:
00685     mSieveJob = SieveJob::put( mUrl, script, active, mWasActive );
00686     connect( mSieveJob, SIGNAL(gotScript(KMail::SieveJob*,bool,const QString&,bool)),
00687          active
00688          ? SLOT(slotPutActiveResult(KMail::SieveJob*,bool))
00689          : SLOT(slotPutInactiveResult(KMail::SieveJob*,bool)) );
00690 
00691     // destroy the dialog:
00692     mDialog->delayedDestruct();
00693     mDialog = 0;
00694   }
00695 
00696   void Vacation::slotDialogCancel() {
00697     kdDebug(5006) << "Vacation::slotDialogCancel()" << endl;
00698     mDialog->delayedDestruct();
00699     mDialog = 0;
00700     emit result( false );
00701   }
00702 
00703   void Vacation::slotPutActiveResult( SieveJob * job, bool success ) {
00704     handlePutResult( job, success, true );
00705   }
00706 
00707   void Vacation::slotPutInactiveResult( SieveJob * job, bool success ) {
00708     handlePutResult( job, success, false );
00709   }
00710 
00711   void Vacation::handlePutResult( SieveJob *, bool success, bool activated ) {
00712     if ( success )
00713       KMessageBox::information( 0, activated
00714                 ? i18n("Sieve script installed successfully on the server.\n"
00715                        "Out of Office reply is now active.")
00716                 : i18n("Sieve script installed successfully on the server.\n"
00717                        "Out of Office reply has been deactivated.") );
00718 
00719     kdDebug(5006) << "Vacation::handlePutResult( ???, " << success << ", ? )"
00720           << endl;
00721     mSieveJob = 0; // job deletes itself after returning from this slot!
00722     emit result( success );
00723     emit scriptActive( activated );
00724   }
00725 
00726 
00727 } // namespace KMail
00728 
00729 #include "vacation.moc"