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