kmail

kmfoldermbox.cpp

00001 /* -*- c-basic-offset: 2 -*-
00002  * kmail: KDE mail client
00003  * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
00004  *
00005  * This program is free software; you can redistribute it and/or modify
00006  * it under the terms of the GNU General Public License as published by
00007  * the Free Software Foundation; either version 2 of the License, or
00008  * (at your option) any later version.
00009  *
00010  * This program is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00013  * GNU General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU General Public License
00016  * along with this program; if not, write to the Free Software
00017  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00018  *
00019  */
00020 #include <config.h>
00021 #include <tqfileinfo.h>
00022 #include <tqregexp.h>
00023 
00024 #include "kmfoldermbox.h"
00025 #include "folderstorage.h"
00026 #include "kmfolder.h"
00027 #include "kmkernel.h"
00028 #include "kmmsgdict.h"
00029 #include "undostack.h"
00030 #include "kcursorsaver.h"
00031 #include "jobscheduler.h"
00032 #include "compactionjob.h"
00033 #include "util.h"
00034 
00035 #include <kdebug.h>
00036 #include <tdelocale.h>
00037 #include <tdemessagebox.h>
00038 #include <knotifyclient.h>
00039 #include <kprocess.h>
00040 #include <tdeconfig.h>
00041 
00042 #include <ctype.h>
00043 #include <stdio.h>
00044 #include <errno.h>
00045 #include <assert.h>
00046 #include <ctype.h>
00047 #include <unistd.h>
00048 
00049 #ifdef HAVE_FCNTL_H
00050 #include <fcntl.h>
00051 #endif
00052 
00053 #include <stdlib.h>
00054 #include <sys/types.h>
00055 #include <sys/stat.h>
00056 #include <sys/file.h>
00057 #include "broadcaststatus.h"
00058 using KPIM::BroadcastStatus;
00059 
00060 #ifndef MAX_LINE
00061 #define MAX_LINE 4096
00062 #endif
00063 #ifndef INIT_MSGS
00064 #define INIT_MSGS 8
00065 #endif
00066 
00067 // Regular expression to find the line that seperates messages in a mail
00068 // folder:
00069 #define MSG_SEPERATOR_START "From "
00070 #define MSG_SEPERATOR_START_LEN (sizeof(MSG_SEPERATOR_START) - 1)
00071 #define MSG_SEPERATOR_REGEX "^From .*[0-9][0-9]:[0-9][0-9]"
00072 
00073 
00074 //-----------------------------------------------------------------------------
00075 KMFolderMbox::KMFolderMbox(KMFolder* folder, const char* name)
00076   : KMFolderIndex(folder, name)
00077 {
00078   mStream         = 0;
00079   mFilesLocked    = false;
00080   mReadOnly       = false;
00081   mLockType       = lock_none;
00082 }
00083 
00084 
00085 //-----------------------------------------------------------------------------
00086 KMFolderMbox::~KMFolderMbox()
00087 {
00088   if (mOpenCount>0)
00089     close("~kmfoldermbox", true);
00090   if (kmkernel->undoStack())
00091     kmkernel->undoStack()->folderDestroyed( folder() );
00092 }
00093 
00094 //-----------------------------------------------------------------------------
00095 int KMFolderMbox::open(const char *owner)
00096 {
00097   Q_UNUSED( owner );
00098   int rc = 0;
00099 
00100   mOpenCount++;
00101   kmkernel->jobScheduler()->notifyOpeningFolder( folder() );
00102 
00103   if (mOpenCount > 1) return 0;  // already open
00104 
00105   assert(!folder()->name().isEmpty());
00106 
00107   mFilesLocked = false;
00108   mStream = fopen(TQFile::encodeName(location()), "r+"); // messages file
00109   if (!mStream)
00110   {
00111     KNotifyClient::event( 0, "warning",
00112     i18n("Cannot open file \"%1\":\n%2").arg(location()).arg(strerror(errno)));
00113     kdDebug(5006) << "Cannot open folder `" << location() << "': " << strerror(errno) << endl;
00114     mOpenCount = 0;
00115     return errno;
00116   }
00117 
00118   lock();
00119 
00120   if (!folder()->path().isEmpty())
00121   {
00122      KMFolderIndex::IndexStatus index_status = indexStatus();
00123      // test if index file exists and is up-to-date
00124      if (KMFolderIndex::IndexOk != index_status)
00125      {
00126        // only show a warning if the index file exists, otherwise it can be
00127        // silently regenerated
00128        if (KMFolderIndex::IndexTooOld == index_status) {
00129         TQString msg = i18n("<qt><p>The index of folder '%2' seems "
00130                            "to be out of date. To prevent message "
00131                            "corruption the index will be "
00132                            "regenerated. As a result deleted "
00133                            "messages might reappear and status "
00134                            "flags might be lost.</p>"
00135                            "<p>Please read the corresponding entry "
00136                            "in the <a href=\"%1\">FAQ section of the manual "
00137                            "of KMail</a> for "
00138                            "information about how to prevent this "
00139                            "problem from happening again.</p></qt>")
00140                       .arg("help:/kmail/faq.html#faq-index-regeneration")
00141                       .arg(name());
00142         // When KMail is starting up we have to show a non-blocking message
00143         // box so that the initialization can continue. We don't show a
00144         // queued message box when KMail isn't starting up because queued
00145         // message boxes don't have a "Don't ask again" checkbox.
00146         if (kmkernel->startingUp())
00147         {
00148           TDEConfigGroup configGroup( KMKernel::config(), "Notification Messages" );
00149           bool showMessage =
00150             configGroup.readBoolEntry( "showIndexRegenerationMessage", true );
00151           if (showMessage)
00152             KMessageBox::queuedMessageBox( 0, KMessageBox::Information,
00153                                            msg, i18n("Index Out of Date"),
00154                                            KMessageBox::AllowLink );
00155         }
00156         else
00157         {
00158             KCursorSaver idle(KBusyPtr::idle());
00159             KMessageBox::information( 0, msg, i18n("Index Out of Date"),
00160                                       "showIndexRegenerationMessage",
00161                                       KMessageBox::AllowLink );
00162         }
00163        }
00164        TQString str;
00165        mIndexStream = 0;
00166        str = i18n("Folder `%1' changed. Recreating index.")
00167              .arg(name());
00168        emit statusMsg(str);
00169      } else {
00170        mIndexStream = fopen(TQFile::encodeName(indexLocation()), "r+"); // index file
00171        if ( mIndexStream ) {
00172          fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
00173          updateIndexStreamPtr();
00174        }
00175      }
00176 
00177      if (!mIndexStream)
00178        rc = createIndexFromContents();
00179      else
00180        if (!readIndex())
00181          rc = createIndexFromContents();
00182   }
00183   else
00184   {
00185     mAutoCreateIndex = false;
00186     rc = createIndexFromContents();
00187   }
00188 
00189   mChanged = false;
00190 
00191   fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC);
00192   if (mIndexStream)
00193      fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
00194 
00195   return rc;
00196 }
00197 
00198 //----------------------------------------------------------------------------
00199 int KMFolderMbox::canAccess()
00200 {
00201   assert(!folder()->name().isEmpty());
00202 
00203   if (access(TQFile::encodeName(location()), R_OK | W_OK) != 0) {
00204     kdDebug(5006) << "KMFolderMbox::access call to access function failed" << endl;
00205       return 1;
00206   }
00207   return 0;
00208 }
00209 
00210 //-----------------------------------------------------------------------------
00211 int KMFolderMbox::create()
00212 {
00213   int rc;
00214   int old_umask;
00215 
00216   assert(!folder()->name().isEmpty());
00217   assert(mOpenCount == 0);
00218 
00219   kdDebug(5006) << "Creating folder " << name() << endl;
00220   if (access(TQFile::encodeName(location()), F_OK) == 0) {
00221     kdDebug(5006) << "KMFolderMbox::create call to access function failed." << endl;
00222     kdDebug(5006) << "File:: " << endl;
00223     kdDebug(5006) << "Error " << endl;
00224     return EEXIST;
00225   }
00226 
00227   old_umask = umask(077);
00228   mStream = fopen(TQFile::encodeName(location()), "w+"); //sven; open RW
00229   umask(old_umask);
00230 
00231   if (!mStream) return errno;
00232 
00233   fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC);
00234 
00235   if (!folder()->path().isEmpty())
00236   {
00237     old_umask = umask(077);
00238     mIndexStream = fopen(TQFile::encodeName(indexLocation()), "w+"); //sven; open RW
00239     updateIndexStreamPtr(true);
00240     umask(old_umask);
00241 
00242     if (!mIndexStream) return errno;
00243     fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
00244   }
00245   else
00246   {
00247     mAutoCreateIndex = false;
00248   }
00249 
00250   mOpenCount++;
00251   mChanged = false;
00252 
00253   rc = writeIndex();
00254   if (!rc) lock();
00255   return rc;
00256 }
00257 
00258 
00259 //-----------------------------------------------------------------------------
00260 void KMFolderMbox::reallyDoClose(const char* owner)
00261 {
00262   Q_UNUSED( owner );
00263   if (mAutoCreateIndex)
00264   {
00265       if (KMFolderIndex::IndexOk != indexStatus()) {
00266           kdDebug(5006) << "Critical error: " << location() <<
00267               " has been modified by an external application while KMail was running." << endl;
00268           //      exit(1); backed out due to broken nfs
00269       }
00270 
00271       updateIndex();
00272       writeConfig();
00273   }
00274 
00275   if (!noContent()) {
00276     if (mStream) unlock();
00277     mMsgList.clear(true);
00278 
00279     if (mStream) fclose(mStream);
00280     if (mIndexStream) {
00281       fclose(mIndexStream);
00282       updateIndexStreamPtr(true);
00283     }
00284   }
00285   mOpenCount   = 0;
00286   mStream      = 0;
00287   mIndexStream = 0;
00288   mFilesLocked = false;
00289   mUnreadMsgs  = -1;
00290 
00291   mMsgList.reset(INIT_MSGS);
00292 }
00293 
00294 //-----------------------------------------------------------------------------
00295 void KMFolderMbox::sync()
00296 {
00297   if (mOpenCount > 0)
00298     if (!mStream || fsync(fileno(mStream)) ||
00299         !mIndexStream || fsync(fileno(mIndexStream))) {
00300     kmkernel->emergencyExit( i18n("Could not sync index file <b>%1</b>: %2").arg( indexLocation() ).arg(errno ? TQString::fromLocal8Bit(strerror(errno)) : i18n("Internal error. Please copy down the details and report a bug.")));
00301     }
00302 }
00303 
00304 //-----------------------------------------------------------------------------
00305 int KMFolderMbox::lock()
00306 {
00307   int rc;
00308   struct flock fl;
00309   fl.l_type=F_WRLCK;
00310   fl.l_whence=0;
00311   fl.l_start=0;
00312   fl.l_len=0;
00313   fl.l_pid=-1;
00314   TQCString cmd_str;
00315   assert(mStream != 0);
00316   mFilesLocked = false;
00317   mReadOnly = false;
00318 
00319   switch( mLockType )
00320   {
00321     case FCNTL:
00322       rc = fcntl(fileno(mStream), F_SETLKW, &fl);
00323 
00324       if (rc < 0)
00325       {
00326         kdDebug(5006) << "Cannot lock folder `" << location() << "': "
00327                   << strerror(errno) << " (" << errno << ")" << endl;
00328         mReadOnly = true;
00329         return errno;
00330       }
00331 
00332       if (mIndexStream)
00333       {
00334         rc = fcntl(fileno(mIndexStream), F_SETLK, &fl);
00335 
00336         if (rc < 0)
00337         {
00338           kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
00339                     << strerror(errno) << " (" << errno << ")" << endl;
00340           rc = errno;
00341           fl.l_type = F_UNLCK;
00342           /*rc =*/ fcntl(fileno(mIndexStream), F_SETLK, &fl);
00343           mReadOnly = true;
00344           return rc;
00345         }
00346       }
00347       break;
00348 
00349     case procmail_lockfile:
00350       cmd_str = "lockfile -l20 -r5 ";
00351       if (!mProcmailLockFileName.isEmpty())
00352         cmd_str += TQFile::encodeName(TDEProcess::quote(mProcmailLockFileName));
00353       else
00354         cmd_str += TQFile::encodeName(TDEProcess::quote(location() + ".lock"));
00355 
00356       rc = system( cmd_str.data() );
00357       if( rc != 0 )
00358       {
00359         kdDebug(5006) << "Cannot lock folder `" << location() << "': "
00360                   << strerror(rc) << " (" << rc << ")" << endl;
00361         mReadOnly = true;
00362         return rc;
00363       }
00364       if( mIndexStream )
00365       {
00366         cmd_str = "lockfile -l20 -r5 " + TQFile::encodeName(TDEProcess::quote(indexLocation() + ".lock"));
00367         rc = system( cmd_str.data() );
00368         if( rc != 0 )
00369         {
00370           kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
00371                     << strerror(rc) << " (" << rc << ")" << endl;
00372           mReadOnly = true;
00373           return rc;
00374         }
00375       }
00376       break;
00377 
00378     case mutt_dotlock:
00379       cmd_str = "mutt_dotlock " + TQFile::encodeName(TDEProcess::quote(location()));
00380       rc = system( cmd_str.data() );
00381       if( rc != 0 )
00382       {
00383         kdDebug(5006) << "Cannot lock folder `" << location() << "': "
00384                   << strerror(rc) << " (" << rc << ")" << endl;
00385         mReadOnly = true;
00386         return rc;
00387       }
00388       if( mIndexStream )
00389       {
00390         cmd_str = "mutt_dotlock " + TQFile::encodeName(TDEProcess::quote(indexLocation()));
00391         rc = system( cmd_str.data() );
00392         if( rc != 0 )
00393         {
00394           kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
00395                     << strerror(rc) << " (" << rc << ")" << endl;
00396           mReadOnly = true;
00397           return rc;
00398         }
00399       }
00400       break;
00401 
00402     case mutt_dotlock_privileged:
00403       cmd_str = "mutt_dotlock -p " + TQFile::encodeName(TDEProcess::quote(location()));
00404       rc = system( cmd_str.data() );
00405       if( rc != 0 )
00406       {
00407         kdDebug(5006) << "Cannot lock folder `" << location() << "': "
00408                   << strerror(rc) << " (" << rc << ")" << endl;
00409         mReadOnly = true;
00410         return rc;
00411       }
00412       if( mIndexStream )
00413       {
00414         cmd_str = "mutt_dotlock -p " + TQFile::encodeName(TDEProcess::quote(indexLocation()));
00415         rc = system( cmd_str.data() );
00416         if( rc != 0 )
00417         {
00418           kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
00419                     << strerror(rc) << " (" << rc << ")" << endl;
00420           mReadOnly = true;
00421           return rc;
00422         }
00423       }
00424       break;
00425 
00426     case lock_none:
00427     default:
00428       break;
00429   }
00430 
00431 
00432   mFilesLocked = true;
00433   return 0;
00434 }
00435 
00436 //-------------------------------------------------------------
00437 FolderJob*
00438 KMFolderMbox::doCreateJob( KMMessage *msg, FolderJob::JobType jt,
00439                            KMFolder *folder, TQString, const AttachmentStrategy* ) const
00440 {
00441   MboxJob *job = new MboxJob( msg, jt, folder );
00442   job->setParent( this );
00443   return job;
00444 }
00445 
00446 //-------------------------------------------------------------
00447 FolderJob*
00448 KMFolderMbox::doCreateJob( TQPtrList<KMMessage>& msgList, const TQString& sets,
00449                            FolderJob::JobType jt, KMFolder *folder ) const
00450 {
00451   MboxJob *job = new MboxJob( msgList, sets, jt, folder );
00452   job->setParent( this );
00453   return job;
00454 }
00455 
00456 //-----------------------------------------------------------------------------
00457 int KMFolderMbox::unlock()
00458 {
00459   int rc;
00460   struct flock fl;
00461   fl.l_type=F_UNLCK;
00462   fl.l_whence=0;
00463   fl.l_start=0;
00464   fl.l_len=0;
00465   TQCString cmd_str;
00466 
00467   assert(mStream != 0);
00468   mFilesLocked = false;
00469 
00470   switch( mLockType )
00471   {
00472     case FCNTL:
00473       if (mIndexStream) fcntl(fileno(mIndexStream), F_SETLK, &fl);
00474       fcntl(fileno(mStream), F_SETLK, &fl);
00475       rc = errno;
00476       break;
00477 
00478     case procmail_lockfile:
00479       cmd_str = "rm -f ";
00480       if (!mProcmailLockFileName.isEmpty())
00481         cmd_str += TQFile::encodeName(TDEProcess::quote(mProcmailLockFileName));
00482       else
00483         cmd_str += TQFile::encodeName(TDEProcess::quote(location() + ".lock"));
00484 
00485       rc = system( cmd_str.data() );
00486       if( mIndexStream )
00487       {
00488         cmd_str = "rm -f " + TQFile::encodeName(TDEProcess::quote(indexLocation() + ".lock"));
00489         rc = system( cmd_str.data() );
00490       }
00491       break;
00492 
00493     case mutt_dotlock:
00494       cmd_str = "mutt_dotlock -u " + TQFile::encodeName(TDEProcess::quote(location()));
00495       rc = system( cmd_str.data() );
00496       if( mIndexStream )
00497       {
00498         cmd_str = "mutt_dotlock -u " + TQFile::encodeName(TDEProcess::quote(indexLocation()));
00499         rc = system( cmd_str.data() );
00500       }
00501       break;
00502 
00503     case mutt_dotlock_privileged:
00504       cmd_str = "mutt_dotlock -p -u " + TQFile::encodeName(TDEProcess::quote(location()));
00505       rc = system( cmd_str.data() );
00506       if( mIndexStream )
00507       {
00508         cmd_str = "mutt_dotlock -p -u " + TQFile::encodeName(TDEProcess::quote(indexLocation()));
00509         rc = system( cmd_str.data() );
00510       }
00511       break;
00512 
00513     case lock_none:
00514     default:
00515       rc = 0;
00516       break;
00517   }
00518 
00519   return rc;
00520 }
00521 
00522 
00523 //-----------------------------------------------------------------------------
00524 KMFolderIndex::IndexStatus KMFolderMbox::indexStatus()
00525 {
00526   if ( !mCompactable )
00527     return KMFolderIndex::IndexCorrupt;
00528 
00529   TQFileInfo contInfo(location());
00530   TQFileInfo indInfo(indexLocation());
00531 
00532   if (!contInfo.exists()) return KMFolderIndex::IndexOk;
00533   if (!indInfo.exists()) return KMFolderIndex::IndexMissing;
00534 
00535   // Check whether the mbox file is more than 5 seconds newer than the index
00536   // file. The 5 seconds are added to reduce the number of false alerts due
00537   // to slightly out of sync clocks of the NFS server and the local machine.
00538   return ( contInfo.lastModified() > indInfo.lastModified().addSecs(5) )
00539       ? KMFolderIndex::IndexTooOld
00540       : KMFolderIndex::IndexOk;
00541 }
00542 
00543 
00544 //-----------------------------------------------------------------------------
00545 int KMFolderMbox::createIndexFromContents()
00546 {
00547   char line[MAX_LINE];
00548   char status[8], xstatus[8];
00549   TQCString subjStr, dateStr, fromStr, toStr, xmarkStr, *lastStr=0;
00550   TQCString replyToIdStr, replyToAuxIdStr, referencesStr, msgIdStr;
00551   TQCString sizeServerStr, uidStr;
00552   TQCString contentTypeStr, charset;
00553   bool atEof = false;
00554   bool inHeader = true;
00555   KMMsgInfo* mi;
00556   TQString msgStr;
00557   TQRegExp regexp(MSG_SEPERATOR_REGEX);
00558   int i, num, numStatus;
00559   short needStatus;
00560 
00561   assert(mStream != 0);
00562   rewind(mStream);
00563 
00564   mMsgList.clear();
00565 
00566   num     = -1;
00567   numStatus= 11;
00568   off_t offs = 0;
00569   size_t size = 0;
00570   dateStr = "";
00571   fromStr = "";
00572   toStr = "";
00573   subjStr = "";
00574   *status = '\0';
00575   *xstatus = '\0';
00576   xmarkStr = "";
00577   replyToIdStr = "";
00578   replyToAuxIdStr = "";
00579   referencesStr = "";
00580   msgIdStr = "";
00581   needStatus = 3;
00582   size_t sizeServer = 0;
00583   ulong uid = 0;
00584 
00585 
00586   while (!atEof)
00587   {
00588     off_t pos = ftell(mStream);
00589     if (!fgets(line, MAX_LINE, mStream)) atEof = true;
00590 
00591     if (atEof ||
00592         (memcmp(line, MSG_SEPERATOR_START, MSG_SEPERATOR_START_LEN)==0 &&
00593          regexp.search(line) >= 0))
00594     {
00595       size = pos - offs;
00596       pos = ftell(mStream);
00597 
00598       if (num >= 0)
00599       {
00600         if (numStatus <= 0)
00601         {
00602           msgStr = i18n("Creating index file: one message done", "Creating index file: %n messages done", num);
00603           emit statusMsg(msgStr);
00604           numStatus = 10;
00605         }
00606 
00607         if (size > 0)
00608         {
00609           msgIdStr = msgIdStr.stripWhiteSpace();
00610           if( !msgIdStr.isEmpty() ) {
00611             int rightAngle;
00612             rightAngle = msgIdStr.find( '>' );
00613             if( rightAngle != -1 )
00614               msgIdStr.truncate( rightAngle + 1 );
00615           }
00616 
00617           replyToIdStr = replyToIdStr.stripWhiteSpace();
00618           if( !replyToIdStr.isEmpty() ) {
00619             int rightAngle;
00620             rightAngle = replyToIdStr.find( '>' );
00621             if( rightAngle != -1 )
00622               replyToIdStr.truncate( rightAngle + 1 );
00623           }
00624 
00625           referencesStr = referencesStr.stripWhiteSpace();
00626           if( !referencesStr.isEmpty() ) {
00627             int leftAngle, rightAngle;
00628             leftAngle = referencesStr.findRev( '<' );
00629             if( ( leftAngle != -1 )
00630                 && ( replyToIdStr.isEmpty() || ( replyToIdStr[0] != '<' ) ) ) {
00631               // use the last reference, instead of missing In-Reply-To
00632               replyToIdStr = referencesStr.mid( leftAngle );
00633             }
00634 
00635             // find second last reference
00636             leftAngle = referencesStr.findRev( '<', leftAngle - 1 );
00637             if( leftAngle != -1 )
00638               referencesStr = referencesStr.mid( leftAngle );
00639             rightAngle = referencesStr.findRev( '>' );
00640             if( rightAngle != -1 )
00641               referencesStr.truncate( rightAngle + 1 );
00642 
00643             // Store the second to last reference in the replyToAuxIdStr
00644             // It is a good candidate for threading the message below if the
00645             // message In-Reply-To points to is not kept in this folder,
00646             // but e.g. in an Outbox
00647             replyToAuxIdStr = referencesStr;
00648             rightAngle = referencesStr.find( '>' );
00649             if( rightAngle != -1 )
00650               replyToAuxIdStr.truncate( rightAngle + 1 );
00651           }
00652 
00653           contentTypeStr = contentTypeStr.stripWhiteSpace();
00654           charset = "";
00655           if ( !contentTypeStr.isEmpty() )
00656           {
00657             int cidx = contentTypeStr.find( "charset=" );
00658             if ( cidx != -1 ) {
00659               charset = contentTypeStr.mid( cidx + 8 );
00660               if ( !charset.isEmpty() && ( charset[0] == '"' ) ) {
00661                 charset = charset.mid( 1 );
00662               }
00663               cidx = 0;
00664               while ( (unsigned int) cidx < charset.length() ) {
00665                 if ( charset[cidx] == '"' || ( !isalnum(charset[cidx]) &&
00666                     charset[cidx] != '-' && charset[cidx] != '_' ) )
00667                   break;
00668                 ++cidx;
00669               }
00670               charset.truncate( cidx );
00671               // kdDebug() << "KMFolderMaildir::readFileHeaderIntern() charset found: " <<
00672               //              charset << " from " << contentTypeStr << endl;
00673             }
00674           }
00675 
00676           mi = new KMMsgInfo(folder());
00677           mi->init( subjStr.stripWhiteSpace(),
00678                     fromStr.stripWhiteSpace(),
00679                     toStr.stripWhiteSpace(),
00680                     0, KMMsgStatusNew,
00681                     xmarkStr.stripWhiteSpace(),
00682                     replyToIdStr, replyToAuxIdStr, msgIdStr,
00683                     KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown,
00684                     KMMsgMDNStateUnknown, charset, offs, size, sizeServer, uid );
00685           mi->setStatus(status, xstatus);
00686           mi->setDate( dateStr.stripWhiteSpace().data() );
00687           mi->setDirty(false);
00688           mMsgList.append(mi, mExportsSernums );
00689 
00690           *status = '\0';
00691           *xstatus = '\0';
00692           needStatus = 3;
00693           xmarkStr = "";
00694           replyToIdStr = "";
00695           replyToAuxIdStr = "";
00696           referencesStr = "";
00697           msgIdStr = "";
00698           dateStr = "";
00699           fromStr = "";
00700           subjStr = "";
00701           sizeServer = 0;
00702           uid = 0;
00703         }
00704         else num--,numStatus++;
00705       }
00706 
00707       offs = ftell(mStream);
00708       num++;
00709       numStatus--;
00710       inHeader = true;
00711       continue;
00712     }
00713     // Is this a long header line?
00714     if (inHeader && (line[0]=='\t' || line[0]==' '))
00715     {
00716       i = 0;
00717       while (line [i]=='\t' || line [i]==' ') i++;
00718       if (line [i] < ' ' && line [i]>0) inHeader = false;
00719       else if (lastStr) *lastStr += line + i;
00720     }
00721     else lastStr = 0;
00722 
00723     if (inHeader && (line [0]=='\n' || line [0]=='\r'))
00724       inHeader = false;
00725     if (!inHeader) continue;
00726 
00727     /* -sanders Make all messages read when auto-recreating index */
00728     /* Reverted, as it breaks reading the sent mail status, for example.
00729        -till */
00730     if ((needStatus & 1) && strncasecmp(line, "Status:", 7) == 0)
00731     {
00732       for(i=0; i<4 && line[i+8] > ' '; i++)
00733         status[i] = line[i+8];
00734       status[i] = '\0';
00735       needStatus &= ~1;
00736     }
00737     else if ((needStatus & 2) && strncasecmp(line, "X-Status:", 9)==0)
00738     {
00739       for(i=0; i<4 && line[i+10] > ' '; i++)
00740         xstatus[i] = line[i+10];
00741       xstatus[i] = '\0';
00742       needStatus &= ~2;
00743     }
00744     else if (strncasecmp(line,"X-KMail-Mark:",13)==0)
00745         xmarkStr = TQCString(line+13);
00746     else if (strncasecmp(line,"In-Reply-To:",12)==0) {
00747       replyToIdStr = TQCString(line+12);
00748       lastStr = &replyToIdStr;
00749     }
00750     else if (strncasecmp(line,"References:",11)==0) {
00751       referencesStr = TQCString(line+11);
00752       lastStr = &referencesStr;
00753     }
00754     else if (strncasecmp(line,"Message-Id:",11)==0) {
00755       msgIdStr = TQCString(line+11);
00756       lastStr = &msgIdStr;
00757     }
00758     else if (strncasecmp(line,"Date:",5)==0)
00759     {
00760       dateStr = TQCString(line+5);
00761       lastStr = &dateStr;
00762     }
00763     else if (strncasecmp(line,"From:", 5)==0)
00764     {
00765       fromStr = TQCString(line+5);
00766       lastStr = &fromStr;
00767     }
00768     else if (strncasecmp(line,"To:", 3)==0)
00769     {
00770       toStr = TQCString(line+3);
00771       lastStr = &toStr;
00772     }
00773     else if (strncasecmp(line,"Subject:",8)==0)
00774     {
00775       subjStr = TQCString(line+8);
00776       lastStr = &subjStr;
00777     }
00778     else if (strncasecmp(line,"X-Length:",9)==0)
00779     {
00780       sizeServerStr = TQCString(line+9);
00781       sizeServer = sizeServerStr.toULong();
00782       lastStr = &sizeServerStr;
00783     }
00784     else if (strncasecmp(line,"X-UID:",6)==0)
00785     {
00786       uidStr = TQCString(line+6);
00787       uid = uidStr.toULong();
00788       lastStr = &uidStr;
00789     }
00790     else if (strncasecmp(line, "Content-Type:", 13) == 0)
00791     {
00792       contentTypeStr = TQCString(line+13);
00793       lastStr = &contentTypeStr;
00794     }
00795   }
00796 
00797   if (mAutoCreateIndex)
00798   {
00799     emit statusMsg(i18n("Writing index file"));
00800     writeIndex();
00801   }
00802   else mHeaderOffset = 0;
00803 
00804   correctUnreadMsgsCount();
00805 
00806   if (kmkernel->outboxFolder() == folder() && count() > 0)
00807     KMessageBox::queuedMessageBox(0, KMessageBox::Information,
00808                                   i18n("Your outbox contains messages which were "
00809     "most-likely not created by KMail;\nplease remove them from there if you "
00810     "do not want KMail to send them."));
00811 
00812   invalidateFolder();
00813   return 0;
00814 }
00815 
00816 
00817 //-----------------------------------------------------------------------------
00818 KMMessage* KMFolderMbox::readMsg(int idx)
00819 {
00820   KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
00821 
00822   assert(mi!=0 && !mi->isMessage());
00823   assert(mStream != 0);
00824 
00825   KMMessage *msg = new KMMessage(*mi);
00826   msg->setMsgInfo( mi ); // remember the KMMsgInfo object to that we can restore it when the KMMessage object is no longer needed
00827   mMsgList.set(idx,&msg->toMsgBase()); // done now so that the serial number can be computed
00828   msg->fromDwString(getDwString(idx));
00829   return msg;
00830 }
00831 
00832 
00833 #define STRDIM(x) (sizeof(x)/sizeof(*x)-1)
00834 // performs (\n|^)>{n}From_ -> \1>{n-1}From_ conversion
00835 static size_t unescapeFrom( char* str, size_t strLen ) {
00836   if ( !str )
00837     return 0;
00838   if ( strLen <= STRDIM(">From ") )
00839     return strLen;
00840 
00841   // yes, *d++ = *s++ is a no-op as long as d == s (until after the
00842   // first >From_), but writes are cheap compared to reads and the
00843   // data is already in the cache from the read, so special-casing
00844   // might even be slower...
00845   const char * s = str;
00846   char * d = str;
00847   const char * const e = str + strLen - STRDIM(">From ");
00848 
00849   while ( s < e ) {
00850     if ( *s == '\n' && *(s+1) == '>' ) { // we can do the lookahead, since e is 6 chars from the end!
00851       *d++ = *s++;  // == '\n'
00852       *d++ = *s++;  // == '>'
00853       while ( s < e && *s == '>' )
00854         *d++ = *s++;
00855       if ( tqstrncmp( s, "From ", STRDIM("From ") ) == 0 )
00856         --d;
00857     }
00858     *d++ = *s++; // yes, s might be e here, but e is not the end :-)
00859   }
00860   // copy the rest:
00861   while ( s < str + strLen )
00862     *d++ = *s++;
00863   if ( d < s ) // only NUL-terminate if it's shorter
00864     *d = 0;
00865 
00866   return d - str;
00867 }
00868 
00869 //static
00870 TQByteArray KMFolderMbox::escapeFrom( const DwString & str ) {
00871   const unsigned int strLen = str.length();
00872   if ( strLen <= STRDIM("From ") )
00873     return KMail::Util::ByteArray( str );
00874   // worst case: \nFrom_\nFrom_\nFrom_... => grows to 7/6
00875   TQByteArray result( int( strLen + 5 ) / 6 * 7 + 1 );
00876 
00877   const char * s = str.data();
00878   const char * const e = s + strLen - STRDIM("From ");
00879   char * d = result.data();
00880 
00881   bool onlyAnglesAfterLF = false; // dont' match ^From_
00882   while ( s < e ) {
00883     switch ( *s ) {
00884     case '\n':
00885       onlyAnglesAfterLF = true;
00886       break;
00887     case '>':
00888       break;
00889     case 'F':
00890       if ( onlyAnglesAfterLF && tqstrncmp( s+1, "rom ", STRDIM("rom ") ) == 0 )
00891         *d++ = '>';
00892       // fall through
00893     default:
00894       onlyAnglesAfterLF = false;
00895       break;
00896     }
00897     *d++ = *s++;
00898   }
00899   while ( s < str.data() + strLen )
00900     *d++ = *s++;
00901 
00902   result.truncate( d - result.data() );
00903   return result;
00904 }
00905 
00906 #undef STRDIM
00907 
00908 //-----------------------------------------------------------------------------
00909 DwString KMFolderMbox::getDwString(int idx)
00910 {
00911   KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
00912 
00913   assert(mi!=0);
00914   assert(mStream != 0);
00915 
00916   size_t msgSize = mi->msgSize();
00917   char* msgText = new char[ msgSize + 1 ];
00918 
00919   fseek(mStream, mi->folderOffset(), SEEK_SET);
00920   fread(msgText, msgSize, 1, mStream);
00921   msgText[msgSize] = '\0';
00922 
00923   size_t newMsgSize = unescapeFrom( msgText, msgSize );
00924   newMsgSize = KMail::Util::crlf2lf( msgText, newMsgSize );
00925 
00926   DwString msgStr;
00927   // the DwString takes possession of msgText, so we must not delete msgText
00928   msgStr.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize );
00929   return msgStr;
00930 }
00931 
00932 
00933 //-----------------------------------------------------------------------------
00934 int KMFolderMbox::addMsg( KMMessage* aMsg, int* aIndex_ret )
00935 {
00936   if (!canAddMsgNow(aMsg, aIndex_ret)) return 0;
00937   TQByteArray msgText;
00938   char endStr[3];
00939   int idx = -1, rc;
00940   KMFolder* msgParent;
00941   bool editing = false;
00942   int growth = 0;
00943 
00944   KMFolderOpener openThis(folder(), "mboxaddMsg");
00945   rc = openThis.openResult();
00946   if (rc)
00947   {
00948     kdDebug(5006) << "KMFolderMbox::addMsg-open: " << rc << " of folder: " << label() << endl;
00949     return rc;
00950   }
00951 
00952   // take message out of the folder it is currently in, if any
00953   msgParent = aMsg->parent();
00954   if (msgParent)
00955   {
00956     if ( msgParent== folder() )
00957     {
00958         if (kmkernel->folderIsDraftOrOutbox( folder() ))
00959           //special case for Edit message.
00960           {
00961             kdDebug(5006) << "Editing message in outbox or drafts" << endl;
00962             editing = true;
00963           }
00964         else
00965           return 0;
00966       }
00967 
00968     idx = msgParent->find(aMsg);
00969     msgParent->getMsg( idx );
00970   }
00971 
00972   if (folderType() != KMFolderTypeImap)
00973   {
00974 /*
00975 TQFile fileD0( "testdat_xx-kmfoldermbox-0" );
00976 if( fileD0.open( IO_WriteOnly ) ) {
00977     TQDataStream ds( &fileD0 );
00978     ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
00979     fileD0.close();  // If data is 0 we just create a zero length file.
00980 }
00981 */
00982     aMsg->setStatusFields();
00983 /*
00984 TQFile fileD1( "testdat_xx-kmfoldermbox-1" );
00985 if( fileD1.open( IO_WriteOnly ) ) {
00986     TQDataStream ds( &fileD1 );
00987     ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
00988     fileD1.close();  // If data is 0 we just create a zero length file.
00989 }
00990 */
00991     if (aMsg->headerField("Content-Type").isEmpty())  // This might be added by
00992       aMsg->removeHeaderField("Content-Type");        // the line above
00993   }
00994   msgText = escapeFrom( aMsg->asDwString() );
00995   size_t len = msgText.size();
00996 
00997   assert(mStream != 0);
00998   clearerr(mStream);
00999   if (len <= 0)
01000   {
01001     kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl;
01002     return 0;
01003   }
01004 
01005   // Make sure the file is large enough to check for an end
01006   // character
01007   fseek(mStream, 0, SEEK_END);
01008   off_t revert = ftell(mStream);
01009   if (ftell(mStream) >= 2) {
01010       // write message to folder file
01011       fseek(mStream, -2, SEEK_END);
01012       fread(endStr, 1, 2, mStream); // ensure separating empty line
01013       if (ftell(mStream) > 0 && endStr[0]!='\n') {
01014           ++growth;
01015           if (endStr[1]!='\n') {
01016               //printf ("****endStr[1]=%c\n", endStr[1]);
01017               fwrite("\n\n", 1, 2, mStream);
01018               ++growth;
01019           }
01020           else fwrite("\n", 1, 1, mStream);
01021       }
01022   }
01023   fseek(mStream,0,SEEK_END); // this is needed on solaris and others
01024   int error = ferror(mStream);
01025   if (error)
01026     return error;
01027 
01028   TQCString messageSeparator( aMsg->mboxMessageSeparator() );
01029   fwrite( messageSeparator.data(), messageSeparator.length(), 1, mStream );
01030   off_t offs = ftell(mStream);
01031   fwrite(msgText.data(), len, 1, mStream);
01032   if (msgText[(int)len-1]!='\n') fwrite("\n\n", 1, 2, mStream);
01033   fflush(mStream);
01034   size_t size = ftell(mStream) - offs;
01035 
01036   error = ferror(mStream);
01037   if (error) {
01038     kdDebug(5006) << "Error: Could not add message to folder: " << strerror(errno) << endl;
01039     if (ftell(mStream) > revert) {
01040       kdDebug(5006) << "Undoing changes" << endl;
01041       truncate( TQFile::encodeName(location()), revert );
01042     }
01043     kmkernel->emergencyExit( i18n("Could not add message to folder: ") + TQString::fromLocal8Bit(strerror(errno)));
01044 
01045     /* This code is not 100% reliable
01046     bool busy = kmkernel->kbp()->isBusy();
01047     if (busy) kmkernel->kbp()->idle();
01048     KMessageBox::sorry(0,
01049           i18n("Unable to add message to folder.\n"
01050                "(No space left on device or insufficient quota?)\n"
01051                "Free space and sufficient quota are required to continue safely."));
01052     if (busy) kmkernel->kbp()->busy();
01053     kmkernel->kbp()->idle();
01054     */
01055     return error;
01056   }
01057 
01058   if (msgParent) {
01059     if (idx >= 0) msgParent->take(idx);
01060   }
01061 //  if (mAccount) aMsg->removeHeaderField("X-UID");
01062 
01063   if (aMsg->isUnread() || aMsg->isNew() ||
01064       (folder() == kmkernel->outboxFolder())) {
01065     if (mUnreadMsgs == -1) mUnreadMsgs = 1;
01066     else ++mUnreadMsgs;
01067     if ( !mQuiet )
01068       emit numUnreadMsgsChanged( folder() );
01069   }
01070   ++mTotalMsgs;
01071   mSize = -1;
01072 
01073   if ( aMsg->attachmentState() == KMMsgAttachmentUnknown && aMsg->readyToShow() ) {
01074     aMsg->updateAttachmentState();
01075   }
01076   if ( aMsg->invitationState() == KMMsgInvitationUnknown && aMsg->readyToShow() ) {
01077     aMsg->updateInvitationState();
01078   }
01079 
01080   // store information about the position in the folder file in the message
01081   aMsg->setParent(folder());
01082   aMsg->setFolderOffset(offs);
01083   aMsg->setMsgSize(size);
01084   idx = mMsgList.append(&aMsg->toMsgBase(), mExportsSernums );
01085   if ( aMsg->getMsgSerNum() <= 0 )
01086     aMsg->setMsgSerNum();
01087   else
01088     replaceMsgSerNum( aMsg->getMsgSerNum(), &aMsg->toMsgBase(), idx );
01089 
01090   // change the length of the previous message to encompass white space added
01091   if ((idx > 0) && (growth > 0)) {
01092     // don't grow if a deleted message claims space at the end of the file
01093     if ((ulong)revert == mMsgList[idx - 1]->folderOffset() + mMsgList[idx - 1]->msgSize() )
01094       mMsgList[idx - 1]->setMsgSize( mMsgList[idx - 1]->msgSize() + growth );
01095   }
01096 
01097   // write index entry if desired
01098   if (mAutoCreateIndex)
01099   {
01100     assert(mIndexStream != 0);
01101     clearerr(mIndexStream);
01102     fseek(mIndexStream, 0, SEEK_END);
01103     revert = ftell(mIndexStream);
01104 
01105     KMMsgBase * mb = &aMsg->toMsgBase();
01106     int len;
01107     const uchar *buffer = mb->asIndexString(len);
01108     fwrite(&len,sizeof(len), 1, mIndexStream);
01109     mb->setIndexOffset( ftell(mIndexStream) );
01110     mb->setIndexLength( len );
01111     if(fwrite(buffer, len, 1, mIndexStream) != 1)
01112       kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl;
01113 
01114     fflush(mIndexStream);
01115     error = ferror(mIndexStream);
01116 
01117     if ( mExportsSernums )
01118       error |= appendToFolderIdsFile( idx );
01119 
01120     if (error) {
01121       kdWarning(5006) << "Error: Could not add message to folder (No space left on device?)" << endl;
01122       if (ftell(mIndexStream) > revert) {
01123         kdWarning(5006) << "Undoing changes" << endl;
01124         truncate( TQFile::encodeName(indexLocation()), revert );
01125       }
01126       if ( errno )
01127         kmkernel->emergencyExit( i18n("Could not add message to folder:") + TQString::fromLocal8Bit(strerror(errno)));
01128       else
01129         kmkernel->emergencyExit( i18n("Could not add message to folder (No space left on device?)") );
01130 
01131       /* This code may not be 100% reliable
01132       bool busy = kmkernel->kbp()->isBusy();
01133       if (busy) kmkernel->kbp()->idle();
01134       KMessageBox::sorry(0,
01135         i18n("Unable to add message to folder.\n"
01136              "(No space left on device or insufficient quota?)\n"
01137              "Free space and sufficient quota are required to continue safely."));
01138       if (busy) kmkernel->kbp()->busy();
01139       */
01140       return error;
01141     }
01142   }
01143 
01144   if (aIndex_ret) *aIndex_ret = idx;
01145   emitMsgAddedSignals(idx);
01146 
01147   // All streams have been flushed without errors if we arrive here
01148   // Return success!
01149   // (Don't return status of stream, it may have been closed already.)
01150   return 0;
01151 }
01152 
01153 int KMFolderMbox::compact( unsigned int startIndex, int nbMessages, FILE* tmpfile, off_t& offs, bool& done )
01154 {
01155   int rc = 0;
01156   TQCString mtext;
01157   unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() :
01158                            TQMIN( mMsgList.count(), startIndex + nbMessages );
01159   //kdDebug(5006) << "KMFolderMbox: compacting from " << startIndex << " to " << stopIndex << endl;
01160   for(unsigned int idx = startIndex; idx < stopIndex; ++idx) {
01161     KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at(idx);
01162     size_t msize = mi->msgSize();
01163     if (mtext.size() < msize + 2)
01164       mtext.resize(msize+2);
01165     off_t folder_offset = mi->folderOffset();
01166 
01167     //now we need to find the separator! grr...
01168     for(off_t i = folder_offset-25; true; i -= 20) {
01169       off_t chunk_offset = i <= 0 ? 0 : i;
01170       if(fseek(mStream, chunk_offset, SEEK_SET) == -1) {
01171         rc = errno;
01172         break;
01173       }
01174       if (mtext.size() < 20)
01175         mtext.resize(20);
01176       fread(mtext.data(), 20, 1, mStream);
01177       if(i <= 0) { //woops we've reached the top of the file, last try..
01178         if ( mtext.contains( "from ", false ) ) {
01179           if (mtext.size() < (size_t)folder_offset)
01180               mtext.resize(folder_offset);
01181           if(fseek(mStream, chunk_offset, SEEK_SET) == -1 ||
01182              !fread(mtext.data(), folder_offset, 1, mStream) ||
01183              !fwrite(mtext.data(), folder_offset, 1, tmpfile)) {
01184               rc = errno;
01185               break;
01186           }
01187           offs += folder_offset;
01188         } else {
01189           rc = 666;
01190         }
01191         break;
01192       } else {
01193         int last_crlf = -1;
01194         for(int i2 = 0; i2 < 20; i2++) {
01195           if(*(mtext.data()+i2) == '\n')
01196             last_crlf = i2;
01197         }
01198         if(last_crlf != -1) {
01199           int size = folder_offset - (i + last_crlf+1);
01200           if ((int)mtext.size() < size)
01201               mtext.resize(size);
01202           if(fseek(mStream, i + last_crlf+1, SEEK_SET) == -1 ||
01203              !fread(mtext.data(), size, 1, mStream) ||
01204              !fwrite(mtext.data(), size, 1, tmpfile)) {
01205               rc = errno;
01206               break;
01207           }
01208           offs += size;
01209           break;
01210         }
01211       }
01212     }
01213     if (rc)
01214       break;
01215 
01216     //now actually write the message
01217     if(fseek(mStream, folder_offset, SEEK_SET) == -1 ||
01218        !fread(mtext.data(), msize, 1, mStream) || !fwrite(mtext.data(), msize, 1, tmpfile)) {
01219         rc = errno;
01220         break;
01221     }
01222     mi->setFolderOffset(offs);
01223     offs += msize;
01224   }
01225   done = ( !rc && stopIndex == mMsgList.count() ); // finished without errors
01226   return rc;
01227 }
01228 
01229 //-----------------------------------------------------------------------------
01230 int KMFolderMbox::compact( bool silent )
01231 {
01232   // This is called only when the user explicitely requests compaction,
01233   // so we don't check needsCompact.
01234 
01235   KMail::MboxCompactionJob* job = new KMail::MboxCompactionJob( folder(), true /*immediate*/ );
01236   int rc = job->executeNow( silent );
01237   // Note that job autodeletes itself.
01238 
01239   // If this is the current folder, the changed signal will ultimately call
01240   // KMHeaders::setFolderInfoStatus which will override the message, so save/restore it
01241   TQString statusMsg = BroadcastStatus::instance()->statusMsg();
01242   emit changed();
01243   BroadcastStatus::instance()->setStatusMsg( statusMsg );
01244   return rc;
01245 }
01246 
01247 
01248 //-----------------------------------------------------------------------------
01249 void KMFolderMbox::setLockType( LockType ltype )
01250 {
01251   mLockType = ltype;
01252 }
01253 
01254 //-----------------------------------------------------------------------------
01255 void KMFolderMbox::setProcmailLockFileName( const TQString &fname )
01256 {
01257   mProcmailLockFileName = fname;
01258 }
01259 
01260 //-----------------------------------------------------------------------------
01261 int KMFolderMbox::removeContents()
01262 {
01263   int rc = 0;
01264   rc = unlink(TQFile::encodeName(location()));
01265   return rc;
01266 }
01267 
01268 //-----------------------------------------------------------------------------
01269 int KMFolderMbox::expungeContents()
01270 {
01271   int rc = 0;
01272   if (truncate(TQFile::encodeName(location()), 0))
01273     rc = errno;
01274   return rc;
01275 }
01276 
01277 //-----------------------------------------------------------------------------
01278 /*virtual*/
01279 TQ_INT64 KMFolderMbox::doFolderSize() const
01280 {
01281   TQFileInfo info( location() );
01282   return (TQ_INT64)(info.size());
01283 }
01284 
01285 //-----------------------------------------------------------------------------
01286 #include "kmfoldermbox.moc"