kmail

kmfoldermbox.cpp

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