kmail

vacation.cpp
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 <tqdatetime.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 TQString & identifier ) { FOREACH commandStart( identifier ); }
00090     void commandEnd() { FOREACH commandEnd(); }
00091     void testStart( const TQString & 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 TQString & comment ) { FOREACH hashComment( comment ); }
00098     void bracketComment( const TQString & 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 TQString & tag ) { FOREACH taggedArgument( tag ); }
00103     void stringArgument( const TQString & string, bool multiline, const TQString & 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 TQString & string, bool multiline, const TQString & 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<TQString,TQString> 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<TQString,TQString> & results() const { return mResults; }
00157 
00158   private:
00159     void process( BuilderMethod method, const TQString & string=TQString() ) {
00160       doProcess( method, string );
00161       mRecursionGuard.clear();
00162     }
00163     void doProcess( BuilderMethod method, const TQString & 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() != TQString::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 TQString & identifier ) { kdDebug(5006) << k_funcinfo << endl; process( CommandStart, identifier ); }
00187     void commandEnd() { kdDebug(5006) << k_funcinfo << endl; process( CommandEnd ); }
00188     void testStart( const TQString & 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 TQString & ) { kdDebug(5006) << k_funcinfo << endl; }
00195     void bracketComment( const TQString & ) { 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 TQString & tag ) { kdDebug(5006) << k_funcinfo << endl; process( TaggedArgument, tag ); }
00204     void stringArgument( const TQString & string, bool, const TQString & ) { kdDebug(5006) << k_funcinfo << endl; process( StringArgument, string ); }
00205     void numberArgument( unsigned long number, char ) { kdDebug(5006) << k_funcinfo << endl; process( NumberArgument, TQString::number( number ) ); }
00206     void stringListArgumentStart() { kdDebug(5006) << k_funcinfo << endl; process( StringListArgumentStart ); }
00207     void stringListEntry( const TQString & string, bool, const TQString & ) { 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     TQString domainName() /*not const, since map::op[] isn't const*/ {
00310       return mResults.count( "stop" ) && mResults.count( "from" )
00311         ? mResults["domainName"] : TQString() ;
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 TQString & messageText() const { return mMessageText; }
00334     const TQStringList & aliases() const { return mAliases; }
00335 
00336   private:
00337     void commandStart( const TQString & 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 TQString & ) {}
00351     void testEnd() {}
00352     void testListStart() {}
00353     void testListEnd() {}
00354     void blockStart() {}
00355     void blockEnd() {}
00356     void hashComment( const TQString & ) {}
00357     void bracketComment( const TQString & ) {}
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 TQString & 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 TQString & string, bool, const TQString & ) {
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 TQString & string, bool, const TQString & ) {
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     TQString mMessageText;
00416     TQStringList mAliases;
00417 
00418     void reset() {
00419       kdDebug(5006) << "VacationDataExtractor::reset()" << endl;
00420       mContext = None;
00421       mNotificationInterval = 0;
00422       mAliases.clear();
00423       mMessageText = TQString();
00424     }
00425   };
00426 
00427 }
00428 
00429 namespace KMail {
00430 
00431   Vacation::Vacation( TQObject * parent, bool checkOnly, const char * name )
00432     : TQObject( 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, TQT_SIGNAL(gotScript(KMail::SieveJob*,bool,const TQString&,bool)),
00440          TQT_SLOT(slotGetResult(KMail::SieveJob*,bool,const TQString&,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 TQString dotstuff( TQString s ) {
00450     if ( s.startsWith( "." ) )
00451       return '.' + s.replace( "\n.", "\n.." );
00452     else
00453       return s.replace( "\n.", "\n.." );
00454   }
00455 
00456   TQString Vacation::composeScript( const TQString & messageText,
00457                    int notificationInterval,
00458                    const AddrSpecList & addrSpecs,
00459                                    bool sendForSpam, const TQString & domain )
00460   {
00461     TQString addressesArgument;
00462     TQStringList aliases;
00463     if ( !addrSpecs.empty() ) {
00464       addressesArgument += ":addresses [ ";
00465       TQStringList 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     TQString script = TQString::fromLatin1("require \"vacation\";\n\n" );
00473     if ( !sendForSpam )
00474       script += TQString::fromLatin1( "if header :contains \"X-Spam-Flag\" \"YES\""
00475                                      " { keep; stop; }\n" ); // FIXME?
00476 
00477     if ( !domain.isEmpty() ) // FIXME
00478       script += TQString::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 += TQString::fromLatin1(":days %1 ").arg( notificationInterval );
00484     script += TQString::fromLatin1("text:\n");
00485     script += dotstuff( messageText.isEmpty() ? defaultMessageText() : messageText );
00486     script += TQString::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.addQueryItem( "x-mech", a->auth() == "*" ? "PLAIN" : a->auth() ); //translate IMAP LOGIN to PLAIN
00504       if ( !a->useSSL() && !a->useTLS() )
00505           u.addQueryItem( "x-allow-unencrypted", "true" );
00506       u.setFileName( sieve.vacationFileName() );
00507       return u;
00508     } else {
00509       KURL u = sieve.alternateURL();
00510       if ( u.protocol().lower() == "sieve" && !a->useSSL() && !a->useTLS() && u.queryItem("x-allow-unencrypted").isEmpty() )
00511           u.addQueryItem( "x-allow-unencrypted", "true" );
00512       u.setFileName( sieve.vacationFileName() );
00513       return u;
00514     }
00515   }
00516 
00517   KURL Vacation::findURL() const {
00518     AccountManager * am = kmkernel->acctMgr();
00519     assert( am );
00520     for ( KMAccount * a = am->first() ; a ; a = am->next() )
00521       if ( KMail::ImapAccountBase * iab = dynamic_cast<KMail::ImapAccountBase*>( a ) ) {
00522         KURL u = findUrlForAccount( iab );
00523     if ( !u.isEmpty() )
00524       return u;
00525       }
00526     return KURL();
00527   }
00528 
00529   bool Vacation::parseScript( const TQString & script, TQString & messageText,
00530                   int & notificationInterval, TQStringList & aliases,
00531                               bool & sendForSpam, TQString & domainName ) {
00532     if ( script.stripWhiteSpace().isEmpty() ) {
00533       messageText = defaultMessageText();
00534       notificationInterval = defaultNotificationInterval();
00535       aliases = defaultMailAliases();
00536       sendForSpam = defaultSendForSpam();
00537       domainName = defaultDomainName();
00538       return true;
00539     }
00540 
00541     // The stripWhiteSpace() call below prevents parsing errors. The
00542     // slave somehow omits the last \n, which results in a lone \r at
00543     // the end, leading to a parse error.
00544     const TQCString scriptUTF8 = script.stripWhiteSpace().utf8();
00545     kdDebug(5006) << "scriptUtf8 = \"" + scriptUTF8 + "\"" << endl;
00546     KSieve::Parser parser( scriptUTF8.begin(),
00547                scriptUTF8.begin() + scriptUTF8.length() );
00548     VacationDataExtractor vdx;
00549     SpamDataExtractor sdx;
00550     DomainRestrictionDataExtractor drdx;
00551     KSieveExt::MultiScriptBuilder tsb( &vdx, &sdx, &drdx );
00552     parser.setScriptBuilder( &tsb );
00553     if ( !parser.parse() )
00554       return false;
00555     messageText = vdx.messageText().stripWhiteSpace();
00556     notificationInterval = vdx.notificationInterval();
00557     aliases = vdx.aliases();
00558     if ( !GlobalSettings::allowOutOfOfficeUploadButNoSettings() ) {
00559       sendForSpam = !sdx.found();
00560       domainName = drdx.domainName();
00561     }
00562     return true;
00563   }
00564 
00565   TQString Vacation::defaultMessageText() {
00566     return i18n("I am out of office till %1.\n"
00567         "\n"
00568         "In urgent cases, please contact Mrs. <vacation replacement>\n"
00569         "\n"
00570         "email: <email address of vacation replacement>\n"
00571         "phone: +49 711 1111 11\n"
00572         "fax.:  +49 711 1111 12\n"
00573         "\n"
00574         "Yours sincerely,\n"
00575         "-- <enter your name and email address here>\n")
00576       .arg( KGlobal::locale()->formatDate( TQDate::currentDate().addDays( 1 ) ) );
00577   }
00578 
00579   int Vacation::defaultNotificationInterval() {
00580     return 7; // days
00581   }
00582 
00583   TQStringList Vacation::defaultMailAliases() {
00584     TQStringList sl;
00585     for ( KPIM::IdentityManager::ConstIterator it = kmkernel->identityManager()->begin() ;
00586       it != kmkernel->identityManager()->end() ; ++it ) {
00587       if ( !(*it).primaryEmailAddress().isEmpty() )
00588     sl.push_back( (*it).primaryEmailAddress() );
00589       sl += (*it).emailAliases();
00590     }
00591     return sl;
00592   }
00593 
00594   bool Vacation::defaultSendForSpam() {
00595     return GlobalSettings::outOfOfficeReactToSpam();
00596   }
00597 
00598   TQString Vacation::defaultDomainName() {
00599     return GlobalSettings::outOfOfficeDomain();
00600   }
00601 
00602   void Vacation::slotGetResult( SieveJob * job, bool success,
00603                 const TQString & script, bool active ) {
00604     kdDebug(5006) << "Vacation::slotGetResult( ??, " << success
00605           << ", ?, " << active << " )" << endl
00606           << "script:" << endl
00607           << script << endl;
00608     mSieveJob = 0; // job deletes itself after returning from this slot!
00609 
00610     if ( !mCheckOnly && mUrl.protocol() == "sieve" && !job->sieveCapabilities().isEmpty() &&
00611      !job->sieveCapabilities().contains("vacation") ) {
00612       KMessageBox::sorry( 0, i18n("Your server did not list \"vacation\" in "
00613                   "its list of supported Sieve extensions;\n"
00614                   "without it, KMail cannot install out-of-"
00615                   "office replies for you.\n"
00616                   "Please contact you system administrator.") );
00617       emit result( false );
00618       return;
00619     }
00620 
00621     if ( !mDialog && !mCheckOnly )
00622       mDialog = new VacationDialog( i18n("Configure \"Out of Office\" Replies"), 0, 0, false );
00623 
00624     TQString messageText = defaultMessageText();
00625     int notificationInterval = defaultNotificationInterval();
00626     TQStringList aliases = defaultMailAliases();
00627     bool sendForSpam = defaultSendForSpam();
00628     TQString domainName = defaultDomainName();
00629     if ( !success ) active = false; // default to inactive
00630 
00631     if ( !mCheckOnly && ( !success || !parseScript( script, messageText, notificationInterval, aliases, sendForSpam, domainName ) ) )
00632       KMessageBox::information( 0, i18n("Someone (probably you) changed the "
00633                     "vacation script on the server.\n"
00634                     "KMail is no longer able to determine "
00635                     "the parameters for the autoreplies.\n"
00636                     "Default values will be used." ) );
00637 
00638     mWasActive = active;
00639     if ( mDialog ) {
00640       mDialog->setActivateVacation( active );
00641       mDialog->setMessageText( messageText );
00642       mDialog->setNotificationInterval( notificationInterval );
00643       mDialog->setMailAliases( aliases.join(", ") );
00644       mDialog->setSendForSpam( sendForSpam );
00645       mDialog->setDomainName( domainName );
00646       mDialog->enableDomainAndSendForSpam( !GlobalSettings::allowOutOfOfficeUploadButNoSettings() );
00647 
00648       connect( mDialog, TQT_SIGNAL(okClicked()), TQT_SLOT(slotDialogOk()) );
00649       connect( mDialog, TQT_SIGNAL(cancelClicked()), TQT_SLOT(slotDialogCancel()) );
00650       connect( mDialog, TQT_SIGNAL(defaultClicked()), TQT_SLOT(slotDialogDefaults()) );
00651 
00652       mDialog->show();
00653     }
00654 
00655     emit scriptActive( mWasActive );
00656     if ( mCheckOnly && mWasActive ) {
00657       if ( KMessageBox::questionYesNo( 0, i18n( "There is still an active out-of-office reply configured.\n"
00658                                         "Do you want to edit it?"), i18n("Out-of-office reply still active"),
00659                                         KGuiItem( i18n( "Edit"), "edit" ), KGuiItem( i18n("Ignore"), "button_cancel" ) )
00660            == KMessageBox::Yes ) {
00661         kmkernel->getKMMainWidget()->slotEditVacation();
00662       }
00663     }
00664   }
00665 
00666   void Vacation::slotDialogDefaults() {
00667     if ( !mDialog )
00668       return;
00669     mDialog->setActivateVacation( true );
00670     mDialog->setMessageText( defaultMessageText() );
00671     mDialog->setNotificationInterval( defaultNotificationInterval() );
00672     mDialog->setMailAliases( defaultMailAliases().join(", ") );
00673     mDialog->setSendForSpam( defaultSendForSpam() );
00674     mDialog->setDomainName( defaultDomainName() );
00675     mDialog->setDomainCheck( false );
00676   }
00677 
00678   void Vacation::slotDialogOk() {
00679     kdDebug(5006) << "Vacation::slotDialogOk()" << endl;
00680     // compose a new script:
00681     const TQString script = composeScript( mDialog->messageText(),
00682                     mDialog->notificationInterval(),
00683                     mDialog->mailAliases(),
00684                                     mDialog->sendForSpam(),
00685                                     mDialog->domainName() );
00686     const bool active = mDialog->activateVacation();
00687     emit scriptActive( active );
00688 
00689     kdDebug(5006) << "script:" << endl << script << endl;
00690 
00691     // and commit the dialog's settings to the server:
00692     mSieveJob = SieveJob::put( mUrl, script, active, mWasActive );
00693     connect( mSieveJob, TQT_SIGNAL(gotScript(KMail::SieveJob*,bool,const TQString&,bool)),
00694          active
00695          ? TQT_SLOT(slotPutActiveResult(KMail::SieveJob*,bool))
00696          : TQT_SLOT(slotPutInactiveResult(KMail::SieveJob*,bool)) );
00697 
00698     // destroy the dialog:
00699     mDialog->delayedDestruct();
00700     mDialog = 0;
00701   }
00702 
00703   void Vacation::slotDialogCancel() {
00704     kdDebug(5006) << "Vacation::slotDialogCancel()" << endl;
00705     mDialog->delayedDestruct();
00706     mDialog = 0;
00707     emit result( false );
00708   }
00709 
00710   void Vacation::slotPutActiveResult( SieveJob * job, bool success ) {
00711     handlePutResult( job, success, true );
00712   }
00713 
00714   void Vacation::slotPutInactiveResult( SieveJob * job, bool success ) {
00715     handlePutResult( job, success, false );
00716   }
00717 
00718   void Vacation::handlePutResult( SieveJob *, bool success, bool activated ) {
00719     if ( success )
00720       KMessageBox::information( 0, activated
00721                 ? i18n("Sieve script installed successfully on the server.\n"
00722                        "Out of Office reply is now active.")
00723                 : i18n("Sieve script installed successfully on the server.\n"
00724                        "Out of Office reply has been deactivated.") );
00725 
00726     kdDebug(5006) << "Vacation::handlePutResult( ???, " << success << ", ? )"
00727           << endl;
00728     mSieveJob = 0; // job deletes itself after returning from this slot!
00729     emit result( success );
00730     emit scriptActive( activated );
00731   }
00732 
00733 
00734 } // namespace KMail
00735 
00736 #include "vacation.moc"