/* -*- c-basic-offset: 2 -*- * kmail: KDE mail client * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include <config.h> #include <tqfileinfo.h> #include <tqregexp.h> #include "kmfoldermbox.h" #include "folderstorage.h" #include "kmfolder.h" #include "kmkernel.h" #include "kmmsgdict.h" #include "undostack.h" #include "kcursorsaver.h" #include "jobscheduler.h" #include "compactionjob.h" #include "util.h" #include <kdebug.h> #include <klocale.h> #include <kmessagebox.h> #include <knotifyclient.h> #include <kprocess.h> #include <kconfig.h> #include <ctype.h> #include <stdio.h> #include <errno.h> #include <assert.h> #include <ctype.h> #include <unistd.h> #ifdef HAVE_FCNTL_H #include <fcntl.h> #endif #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/file.h> #include "broadcaststatus.h" using KPIM::BroadcastStatus; #ifndef MAX_LINE #define MAX_LINE 4096 #endif #ifndef INIT_MSGS #define INIT_MSGS 8 #endif // Regular expression to find the line that seperates messages in a mail // folder: #define MSG_SEPERATOR_START "From " #define MSG_SEPERATOR_START_LEN (sizeof(MSG_SEPERATOR_START) - 1) #define MSG_SEPERATOR_REGEX "^From .*[0-9][0-9]:[0-9][0-9]" //----------------------------------------------------------------------------- KMFolderMbox::KMFolderMbox(KMFolder* folder, const char* name) : KMFolderIndex(folder, name) { mStream = 0; mFilesLocked = false; mReadOnly = false; mLockType = lock_none; } //----------------------------------------------------------------------------- KMFolderMbox::~KMFolderMbox() { if (mOpenCount>0) close("~kmfoldermbox", true); if (kmkernel->undoStack()) kmkernel->undoStack()->folderDestroyed( folder() ); } //----------------------------------------------------------------------------- int KMFolderMbox::open(const char *owner) { Q_UNUSED( owner ); int rc = 0; mOpenCount++; kmkernel->jobScheduler()->notifyOpeningFolder( folder() ); if (mOpenCount > 1) return 0; // already open assert(!folder()->name().isEmpty()); mFilesLocked = false; mStream = fopen(TQFile::encodeName(location()), "r+"); // messages file if (!mStream) { KNotifyClient::event( 0, "warning", i18n("Cannot open file \"%1\":\n%2").tqarg(location()).tqarg(strerror(errno))); kdDebug(5006) << "Cannot open folder `" << location() << "': " << strerror(errno) << endl; mOpenCount = 0; return errno; } lock(); if (!folder()->path().isEmpty()) { KMFolderIndex::IndextqStatus index_status = indextqStatus(); // test if index file exists and is up-to-date if (KMFolderIndex::IndexOk != index_status) { // only show a warning if the index file exists, otherwise it can be // silently regenerated if (KMFolderIndex::IndexTooOld == index_status) { TQString msg = i18n("<qt><p>The index of folder '%2' seems " "to be out of date. To prevent message " "corruption the index will be " "regenerated. As a result deleted " "messages might reappear and status " "flags might be lost.</p>" "<p>Please read the corresponding entry " "in the <a href=\"%1\">FAQ section of the manual " "of KMail</a> for " "information about how to prevent this " "problem from happening again.</p></qt>") .tqarg("help:/kmail/faq.html#faq-index-regeneration") .tqarg(name()); // When KMail is starting up we have to show a non-blocking message // box so that the initialization can continue. We don't show a // queued message box when KMail isn't starting up because queued // message boxes don't have a "Don't ask again" checkbox. if (kmkernel->startingUp()) { KConfigGroup configGroup( KMKernel::config(), "Notification Messages" ); bool showMessage = configGroup.readBoolEntry( "showIndexRegenerationMessage", true ); if (showMessage) KMessageBox::queuedMessageBox( 0, KMessageBox::Information, msg, i18n("Index Out of Date"), KMessageBox::AllowLink ); } else { KCursorSaver idle(KBusyPtr::idle()); KMessageBox::information( 0, msg, i18n("Index Out of Date"), "showIndexRegenerationMessage", KMessageBox::AllowLink ); } } TQString str; mIndexStream = 0; str = i18n("Folder `%1' changed. Recreating index.") .tqarg(name()); emit statusMsg(str); } else { mIndexStream = fopen(TQFile::encodeName(indexLocation()), "r+"); // index file if ( mIndexStream ) { fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC); updateIndexStreamPtr(); } } if (!mIndexStream) rc = createIndexFromContents(); else if (!readIndex()) rc = createIndexFromContents(); } else { mAutoCreateIndex = false; rc = createIndexFromContents(); } mChanged = false; fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC); if (mIndexStream) fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC); return rc; } //---------------------------------------------------------------------------- int KMFolderMbox::canAccess() { assert(!folder()->name().isEmpty()); if (access(TQFile::encodeName(location()), R_OK | W_OK) != 0) { kdDebug(5006) << "KMFolderMbox::access call to access function failed" << endl; return 1; } return 0; } //----------------------------------------------------------------------------- int KMFolderMbox::create() { int rc; int old_umask; assert(!folder()->name().isEmpty()); assert(mOpenCount == 0); kdDebug(5006) << "Creating folder " << name() << endl; if (access(TQFile::encodeName(location()), F_OK) == 0) { kdDebug(5006) << "KMFolderMbox::create call to access function failed." << endl; kdDebug(5006) << "File:: " << endl; kdDebug(5006) << "Error " << endl; return EEXIST; } old_umask = umask(077); mStream = fopen(TQFile::encodeName(location()), "w+"); //sven; open RW umask(old_umask); if (!mStream) return errno; fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC); if (!folder()->path().isEmpty()) { old_umask = umask(077); mIndexStream = fopen(TQFile::encodeName(indexLocation()), "w+"); //sven; open RW updateIndexStreamPtr(true); umask(old_umask); if (!mIndexStream) return errno; fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC); } else { mAutoCreateIndex = false; } mOpenCount++; mChanged = false; rc = writeIndex(); if (!rc) lock(); return rc; } //----------------------------------------------------------------------------- void KMFolderMbox::reallyDoClose(const char* owner) { Q_UNUSED( owner ); if (mAutoCreateIndex) { if (KMFolderIndex::IndexOk != indextqStatus()) { kdDebug(5006) << "Critical error: " << location() << " has been modified by an external application while KMail was running." << endl; // exit(1); backed out due to broken nfs } updateIndex(); writeConfig(); } if (!noContent()) { if (mStream) unlock(); mMsgList.clear(true); if (mStream) fclose(mStream); if (mIndexStream) { fclose(mIndexStream); updateIndexStreamPtr(true); } } mOpenCount = 0; mStream = 0; mIndexStream = 0; mFilesLocked = false; mUnreadMsgs = -1; mMsgList.reset(INIT_MSGS); } //----------------------------------------------------------------------------- void KMFolderMbox::sync() { if (mOpenCount > 0) if (!mStream || fsync(fileno(mStream)) || !mIndexStream || fsync(fileno(mIndexStream))) { kmkernel->emergencyExit( i18n("Could not sync index file <b>%1</b>: %2").tqarg( indexLocation() ).tqarg(errno ? TQString::fromLocal8Bit(strerror(errno)) : i18n("Internal error. Please copy down the details and report a bug."))); } } //----------------------------------------------------------------------------- int KMFolderMbox::lock() { int rc; struct flock fl; fl.l_type=F_WRLCK; fl.l_whence=0; fl.l_start=0; fl.l_len=0; fl.l_pid=-1; TQCString cmd_str; assert(mStream != 0); mFilesLocked = false; mReadOnly = false; switch( mLockType ) { case FCNTL: rc = fcntl(fileno(mStream), F_SETLKW, &fl); if (rc < 0) { kdDebug(5006) << "Cannot lock folder `" << location() << "': " << strerror(errno) << " (" << errno << ")" << endl; mReadOnly = true; return errno; } if (mIndexStream) { rc = fcntl(fileno(mIndexStream), F_SETLK, &fl); if (rc < 0) { kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " << strerror(errno) << " (" << errno << ")" << endl; rc = errno; fl.l_type = F_UNLCK; /*rc =*/ fcntl(fileno(mIndexStream), F_SETLK, &fl); mReadOnly = true; return rc; } } break; case procmail_lockfile: cmd_str = "lockfile -l20 -r5 "; if (!mProcmailLockFileName.isEmpty()) cmd_str += TQFile::encodeName(KProcess::quote(mProcmailLockFileName)); else cmd_str += TQFile::encodeName(KProcess::quote(location() + ".lock")); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; mReadOnly = true; return rc; } if( mIndexStream ) { cmd_str = "lockfile -l20 -r5 " + TQFile::encodeName(KProcess::quote(indexLocation() + ".lock")); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; mReadOnly = true; return rc; } } break; case mutt_dotlock: cmd_str = "mutt_dotlock " + TQFile::encodeName(KProcess::quote(location())); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; mReadOnly = true; return rc; } if( mIndexStream ) { cmd_str = "mutt_dotlock " + TQFile::encodeName(KProcess::quote(indexLocation())); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; mReadOnly = true; return rc; } } break; case mutt_dotlock_privileged: cmd_str = "mutt_dotlock -p " + TQFile::encodeName(KProcess::quote(location())); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; mReadOnly = true; return rc; } if( mIndexStream ) { cmd_str = "mutt_dotlock -p " + TQFile::encodeName(KProcess::quote(indexLocation())); rc = system( cmd_str.data() ); if( rc != 0 ) { kdDebug(5006) << "Cannot lock index of folder `" << location() << "': " << strerror(rc) << " (" << rc << ")" << endl; mReadOnly = true; return rc; } } break; case lock_none: default: break; } mFilesLocked = true; return 0; } //------------------------------------------------------------- FolderJob* KMFolderMbox::doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder, TQString, const AttachmentStrategy* ) const { MboxJob *job = new MboxJob( msg, jt, folder ); job->setParent( this ); return job; } //------------------------------------------------------------- FolderJob* KMFolderMbox::doCreateJob( TQPtrList<KMMessage>& msgList, const TQString& sets, FolderJob::JobType jt, KMFolder *folder ) const { MboxJob *job = new MboxJob( msgList, sets, jt, folder ); job->setParent( this ); return job; } //----------------------------------------------------------------------------- int KMFolderMbox::unlock() { int rc; struct flock fl; fl.l_type=F_UNLCK; fl.l_whence=0; fl.l_start=0; fl.l_len=0; TQCString cmd_str; assert(mStream != 0); mFilesLocked = false; switch( mLockType ) { case FCNTL: if (mIndexStream) fcntl(fileno(mIndexStream), F_SETLK, &fl); fcntl(fileno(mStream), F_SETLK, &fl); rc = errno; break; case procmail_lockfile: cmd_str = "rm -f "; if (!mProcmailLockFileName.isEmpty()) cmd_str += TQFile::encodeName(KProcess::quote(mProcmailLockFileName)); else cmd_str += TQFile::encodeName(KProcess::quote(location() + ".lock")); rc = system( cmd_str.data() ); if( mIndexStream ) { cmd_str = "rm -f " + TQFile::encodeName(KProcess::quote(indexLocation() + ".lock")); rc = system( cmd_str.data() ); } break; case mutt_dotlock: cmd_str = "mutt_dotlock -u " + TQFile::encodeName(KProcess::quote(location())); rc = system( cmd_str.data() ); if( mIndexStream ) { cmd_str = "mutt_dotlock -u " + TQFile::encodeName(KProcess::quote(indexLocation())); rc = system( cmd_str.data() ); } break; case mutt_dotlock_privileged: cmd_str = "mutt_dotlock -p -u " + TQFile::encodeName(KProcess::quote(location())); rc = system( cmd_str.data() ); if( mIndexStream ) { cmd_str = "mutt_dotlock -p -u " + TQFile::encodeName(KProcess::quote(indexLocation())); rc = system( cmd_str.data() ); } break; case lock_none: default: rc = 0; break; } return rc; } //----------------------------------------------------------------------------- KMFolderIndex::IndextqStatus KMFolderMbox::indextqStatus() { if ( !mCompactable ) return KMFolderIndex::IndexCorrupt; TQFileInfo contInfo(location()); TQFileInfo indInfo(indexLocation()); if (!contInfo.exists()) return KMFolderIndex::IndexOk; if (!indInfo.exists()) return KMFolderIndex::IndexMissing; // Check whether the mbox file is more than 5 seconds newer than the index // file. The 5 seconds are added to reduce the number of false alerts due // to slightly out of sync clocks of the NFS server and the local machine. return ( contInfo.lastModified() > indInfo.lastModified().addSecs(5) ) ? KMFolderIndex::IndexTooOld : KMFolderIndex::IndexOk; } //----------------------------------------------------------------------------- int KMFolderMbox::createIndexFromContents() { char line[MAX_LINE]; char status[8], xstatus[8]; TQCString subjStr, dateStr, fromStr, toStr, xmarkStr, *lastStr=0; TQCString replyToIdStr, replyToAuxIdStr, referencesStr, msgIdStr; TQCString sizeServerStr, uidStr; TQCString contentTypeStr, charset; bool atEof = false; bool inHeader = true; KMMsgInfo* mi; TQString msgStr; TQRegExp regexp(MSG_SEPERATOR_REGEX); int i, num, numtqStatus; short needtqStatus; assert(mStream != 0); rewind(mStream); mMsgList.clear(); num = -1; numtqStatus= 11; off_t offs = 0; size_t size = 0; dateStr = ""; fromStr = ""; toStr = ""; subjStr = ""; *status = '\0'; *xstatus = '\0'; xmarkStr = ""; replyToIdStr = ""; replyToAuxIdStr = ""; referencesStr = ""; msgIdStr = ""; needtqStatus = 3; size_t sizeServer = 0; ulong uid = 0; while (!atEof) { off_t pos = ftell(mStream); if (!fgets(line, MAX_LINE, mStream)) atEof = true; if (atEof || (memcmp(line, MSG_SEPERATOR_START, MSG_SEPERATOR_START_LEN)==0 && regexp.search(line) >= 0)) { size = pos - offs; pos = ftell(mStream); if (num >= 0) { if (numtqStatus <= 0) { msgStr = i18n("Creating index file: one message done", "Creating index file: %n messages done", num); emit statusMsg(msgStr); numtqStatus = 10; } if (size > 0) { msgIdStr = msgIdStr.stripWhiteSpace(); if( !msgIdStr.isEmpty() ) { int rightAngle; rightAngle = msgIdStr.find( '>' ); if( rightAngle != -1 ) msgIdStr.truncate( rightAngle + 1 ); } replyToIdStr = replyToIdStr.stripWhiteSpace(); if( !replyToIdStr.isEmpty() ) { int rightAngle; rightAngle = replyToIdStr.find( '>' ); if( rightAngle != -1 ) replyToIdStr.truncate( rightAngle + 1 ); } referencesStr = referencesStr.stripWhiteSpace(); if( !referencesStr.isEmpty() ) { int leftAngle, rightAngle; leftAngle = referencesStr.findRev( '<' ); if( ( leftAngle != -1 ) && ( replyToIdStr.isEmpty() || ( replyToIdStr[0] != '<' ) ) ) { // use the last reference, instead of missing In-Reply-To replyToIdStr = referencesStr.mid( leftAngle ); } // find second last reference leftAngle = referencesStr.findRev( '<', leftAngle - 1 ); if( leftAngle != -1 ) referencesStr = referencesStr.mid( leftAngle ); rightAngle = referencesStr.findRev( '>' ); if( rightAngle != -1 ) referencesStr.truncate( rightAngle + 1 ); // Store the second to last reference in the replyToAuxIdStr // It is a good candidate for threading the message below if the // message In-Reply-To points to is not kept in this folder, // but e.g. in an Outbox replyToAuxIdStr = referencesStr; rightAngle = referencesStr.find( '>' ); if( rightAngle != -1 ) replyToAuxIdStr.truncate( rightAngle + 1 ); } contentTypeStr = contentTypeStr.stripWhiteSpace(); charset = ""; if ( !contentTypeStr.isEmpty() ) { int cidx = contentTypeStr.find( "charset=" ); if ( cidx != -1 ) { charset = contentTypeStr.mid( cidx + 8 ); if ( !charset.isEmpty() && ( charset[0] == '"' ) ) { charset = charset.mid( 1 ); } cidx = 0; while ( (unsigned int) cidx < charset.length() ) { if ( charset[cidx] == '"' || ( !isalnum(charset[cidx]) && charset[cidx] != '-' && charset[cidx] != '_' ) ) break; ++cidx; } charset.truncate( cidx ); // kdDebug() << "KMFolderMaildir::readFileHeaderIntern() charset found: " << // charset << " from " << contentTypeStr << endl; } } mi = new KMMsgInfo(folder()); mi->init( subjStr.stripWhiteSpace(), fromStr.stripWhiteSpace(), toStr.stripWhiteSpace(), 0, KMMsgStatusNew, xmarkStr.stripWhiteSpace(), replyToIdStr, replyToAuxIdStr, msgIdStr, KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown, KMMsgMDNStateUnknown, charset, offs, size, sizeServer, uid ); mi->setqStatus(status, xstatus); mi->setDate( dateStr.stripWhiteSpace().data() ); mi->setDirty(false); mMsgList.append(mi, mExportsSernums ); *status = '\0'; *xstatus = '\0'; needtqStatus = 3; xmarkStr = ""; replyToIdStr = ""; replyToAuxIdStr = ""; referencesStr = ""; msgIdStr = ""; dateStr = ""; fromStr = ""; subjStr = ""; sizeServer = 0; uid = 0; } else num--,numtqStatus++; } offs = ftell(mStream); num++; numtqStatus--; inHeader = true; continue; } // Is this a long header line? if (inHeader && (line[0]=='\t' || line[0]==' ')) { i = 0; while (line [i]=='\t' || line [i]==' ') i++; if (line [i] < ' ' && line [i]>0) inHeader = false; else if (lastStr) *lastStr += line + i; } else lastStr = 0; if (inHeader && (line [0]=='\n' || line [0]=='\r')) inHeader = false; if (!inHeader) continue; /* -sanders Make all messages read when auto-recreating index */ /* Reverted, as it breaks reading the sent mail status, for example. -till */ if ((needtqStatus & 1) && strncasecmp(line, "Status:", 7) == 0) { for(i=0; i<4 && line[i+8] > ' '; i++) status[i] = line[i+8]; status[i] = '\0'; needtqStatus &= ~1; } else if ((needtqStatus & 2) && strncasecmp(line, "X-Status:", 9)==0) { for(i=0; i<4 && line[i+10] > ' '; i++) xstatus[i] = line[i+10]; xstatus[i] = '\0'; needtqStatus &= ~2; } else if (strncasecmp(line,"X-KMail-Mark:",13)==0) xmarkStr = TQCString(line+13); else if (strncasecmp(line,"In-Reply-To:",12)==0) { replyToIdStr = TQCString(line+12); lastStr = &replyToIdStr; } else if (strncasecmp(line,"References:",11)==0) { referencesStr = TQCString(line+11); lastStr = &referencesStr; } else if (strncasecmp(line,"Message-Id:",11)==0) { msgIdStr = TQCString(line+11); lastStr = &msgIdStr; } else if (strncasecmp(line,"Date:",5)==0) { dateStr = TQCString(line+5); lastStr = &dateStr; } else if (strncasecmp(line,"From:", 5)==0) { fromStr = TQCString(line+5); lastStr = &fromStr; } else if (strncasecmp(line,"To:", 3)==0) { toStr = TQCString(line+3); lastStr = &toStr; } else if (strncasecmp(line,"Subject:",8)==0) { subjStr = TQCString(line+8); lastStr = &subjStr; } else if (strncasecmp(line,"X-Length:",9)==0) { sizeServerStr = TQCString(line+9); sizeServer = sizeServerStr.toULong(); lastStr = &sizeServerStr; } else if (strncasecmp(line,"X-UID:",6)==0) { uidStr = TQCString(line+6); uid = uidStr.toULong(); lastStr = &uidStr; } else if (strncasecmp(line, "Content-Type:", 13) == 0) { contentTypeStr = TQCString(line+13); lastStr = &contentTypeStr; } } if (mAutoCreateIndex) { emit statusMsg(i18n("Writing index file")); writeIndex(); } else mHeaderOffset = 0; correctUnreadMsgsCount(); if (kmkernel->outboxFolder() == folder() && count() > 0) KMessageBox::queuedMessageBox(0, KMessageBox::Information, i18n("Your outbox contains messages which were " "most-likely not created by KMail;\nplease remove them from there if you " "do not want KMail to send them.")); invalidateFolder(); return 0; } //----------------------------------------------------------------------------- KMMessage* KMFolderMbox::readMsg(int idx) { KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; assert(mi!=0 && !mi->isMessage()); assert(mStream != 0); KMMessage *msg = new KMMessage(*mi); msg->setMsgInfo( mi ); // remember the KMMsgInfo object to that we can restore it when the KMMessage object is no longer needed mMsgList.set(idx,&msg->toMsgBase()); // done now so that the serial number can be computed msg->fromDwString(getDwString(idx)); return msg; } #define STRDIM(x) (sizeof(x)/sizeof(*x)-1) // performs (\n|^)>{n}From_ -> \1>{n-1}From_ conversion static size_t unescapeFrom( char* str, size_t strLen ) { if ( !str ) return 0; if ( strLen <= STRDIM(">From ") ) return strLen; // yes, *d++ = *s++ is a no-op as long as d == s (until after the // first >From_), but writes are cheap compared to reads and the // data is already in the cache from the read, so special-casing // might even be slower... const char * s = str; char * d = str; const char * const e = str + strLen - STRDIM(">From "); while ( s < e ) { if ( *s == '\n' && *(s+1) == '>' ) { // we can do the lookahead, since e is 6 chars from the end! *d++ = *s++; // == '\n' *d++ = *s++; // == '>' while ( s < e && *s == '>' ) *d++ = *s++; if ( tqstrncmp( s, "From ", STRDIM("From ") ) == 0 ) --d; } *d++ = *s++; // yes, s might be e here, but e is not the end :-) } // copy the rest: while ( s < str + strLen ) *d++ = *s++; if ( d < s ) // only NUL-terminate if it's shorter *d = 0; return d - str; } //static TQByteArray KMFolderMbox::escapeFrom( const DwString & str ) { const unsigned int strLen = str.length(); if ( strLen <= STRDIM("From ") ) return KMail::Util::ByteArray( str ); // worst case: \nFrom_\nFrom_\nFrom_... => grows to 7/6 TQByteArray result( int( strLen + 5 ) / 6 * 7 + 1 ); const char * s = str.data(); const char * const e = s + strLen - STRDIM("From "); char * d = result.data(); bool onlyAnglesAfterLF = false; // dont' match ^From_ while ( s < e ) { switch ( *s ) { case '\n': onlyAnglesAfterLF = true; break; case '>': break; case 'F': if ( onlyAnglesAfterLF && tqstrncmp( s+1, "rom ", STRDIM("rom ") ) == 0 ) *d++ = '>'; // fall through default: onlyAnglesAfterLF = false; break; } *d++ = *s++; } while ( s < str.data() + strLen ) *d++ = *s++; result.truncate( d - result.data() ); return result; } #undef STRDIM //----------------------------------------------------------------------------- DwString KMFolderMbox::getDwString(int idx) { KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx]; assert(mi!=0); assert(mStream != 0); size_t msgSize = mi->msgSize(); char* msgText = new char[ msgSize + 1 ]; fseek(mStream, mi->folderOffset(), SEEK_SET); fread(msgText, msgSize, 1, mStream); msgText[msgSize] = '\0'; size_t newMsgSize = unescapeFrom( msgText, msgSize ); newMsgSize = KMail::Util::crlf2lf( msgText, newMsgSize ); DwString msgStr; // the DwString takes possession of msgText, so we must not delete msgText msgStr.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize ); return msgStr; } //----------------------------------------------------------------------------- int KMFolderMbox::addMsg( KMMessage* aMsg, int* aIndex_ret ) { if (!canAddMsgNow(aMsg, aIndex_ret)) return 0; TQByteArray msgText; char endStr[3]; int idx = -1, rc; KMFolder* msgParent; bool editing = false; int growth = 0; KMFolderOpener openThis(folder(), "mboxaddMsg"); rc = openThis.openResult(); if (rc) { kdDebug(5006) << "KMFolderMbox::addMsg-open: " << rc << " of folder: " << label() << endl; return rc; } // take message out of the folder it is currently in, if any msgParent = aMsg->parent(); if (msgParent) { if ( msgParent== folder() ) { if (kmkernel->folderIsDraftOrOutbox( folder() )) //special case for Edit message. { kdDebug(5006) << "Editing message in outbox or drafts" << endl; editing = true; } else return 0; } idx = msgParent->find(aMsg); msgParent->getMsg( idx ); } if (folderType() != KMFolderTypeImap) { /* TQFile fileD0( "testdat_xx-kmfoldermbox-0" ); if( fileD0.open( IO_WriteOnly ) ) { TQDataStream ds( &fileD0 ); ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() ); fileD0.close(); // If data is 0 we just create a zero length file. } */ aMsg->seStatusFields(); /* TQFile fileD1( "testdat_xx-kmfoldermbox-1" ); if( fileD1.open( IO_WriteOnly ) ) { TQDataStream ds( &fileD1 ); ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() ); fileD1.close(); // If data is 0 we just create a zero length file. } */ if (aMsg->headerField("Content-Type").isEmpty()) // This might be added by aMsg->removeHeaderField("Content-Type"); // the line above } msgText = escapeFrom( aMsg->asDwString() ); size_t len = msgText.size(); assert(mStream != 0); clearerr(mStream); if (len <= 0) { kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl; return 0; } // Make sure the file is large enough to check for an end // character fseek(mStream, 0, SEEK_END); off_t revert = ftell(mStream); if (ftell(mStream) >= 2) { // write message to folder file fseek(mStream, -2, SEEK_END); fread(endStr, 1, 2, mStream); // ensure separating empty line if (ftell(mStream) > 0 && endStr[0]!='\n') { ++growth; if (endStr[1]!='\n') { //printf ("****endStr[1]=%c\n", endStr[1]); fwrite("\n\n", 1, 2, mStream); ++growth; } else fwrite("\n", 1, 1, mStream); } } fseek(mStream,0,SEEK_END); // this is needed on solaris and others int error = ferror(mStream); if (error) return error; TQCString messageSeparator( aMsg->mboxMessageSeparator() ); fwrite( messageSeparator.data(), messageSeparator.length(), 1, mStream ); off_t offs = ftell(mStream); fwrite(msgText.data(), len, 1, mStream); if (msgText[(int)len-1]!='\n') fwrite("\n\n", 1, 2, mStream); fflush(mStream); size_t size = ftell(mStream) - offs; error = ferror(mStream); if (error) { kdDebug(5006) << "Error: Could not add message to folder: " << strerror(errno) << endl; if (ftell(mStream) > revert) { kdDebug(5006) << "Undoing changes" << endl; truncate( TQFile::encodeName(location()), revert ); } kmkernel->emergencyExit( i18n("Could not add message to folder: ") + TQString::fromLocal8Bit(strerror(errno))); /* This code is not 100% reliable bool busy = kmkernel->kbp()->isBusy(); if (busy) kmkernel->kbp()->idle(); KMessageBox::sorry(0, i18n("Unable to add message to folder.\n" "(No space left on tqdevice or insufficient quota?)\n" "Free space and sufficient quota are required to continue safely.")); if (busy) kmkernel->kbp()->busy(); kmkernel->kbp()->idle(); */ return error; } if (msgParent) { if (idx >= 0) msgParent->take(idx); } // if (mAccount) aMsg->removeHeaderField("X-UID"); if (aMsg->isUnread() || aMsg->isNew() || (folder() == kmkernel->outboxFolder())) { if (mUnreadMsgs == -1) mUnreadMsgs = 1; else ++mUnreadMsgs; if ( !mQuiet ) emit numUnreadMsgsChanged( folder() ); } ++mTotalMsgs; mSize = -1; if ( aMsg->attachmentState() == KMMsgAttachmentUnknown && aMsg->readyToShow() ) { aMsg->updateAttachmentState(); } if ( aMsg->invitationState() == KMMsgInvitationUnknown && aMsg->readyToShow() ) { aMsg->updateInvitationState(); } // store information about the position in the folder file in the message aMsg->setParent(folder()); aMsg->setFolderOffset(offs); aMsg->setMsgSize(size); idx = mMsgList.append(&aMsg->toMsgBase(), mExportsSernums ); if ( aMsg->getMsgSerNum() <= 0 ) aMsg->setMsgSerNum(); else replaceMsgSerNum( aMsg->getMsgSerNum(), &aMsg->toMsgBase(), idx ); // change the length of the previous message to encompass white space added if ((idx > 0) && (growth > 0)) { // don't grow if a deleted message claims space at the end of the file if ((ulong)revert == mMsgList[idx - 1]->folderOffset() + mMsgList[idx - 1]->msgSize() ) mMsgList[idx - 1]->setMsgSize( mMsgList[idx - 1]->msgSize() + growth ); } // write index entry if desired if (mAutoCreateIndex) { assert(mIndexStream != 0); clearerr(mIndexStream); fseek(mIndexStream, 0, SEEK_END); revert = ftell(mIndexStream); KMMsgBase * mb = &aMsg->toMsgBase(); int len; const uchar *buffer = mb->asIndexString(len); fwrite(&len,sizeof(len), 1, mIndexStream); mb->setIndexOffset( ftell(mIndexStream) ); mb->setIndexLength( len ); if(fwrite(buffer, len, 1, mIndexStream) != 1) kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl; fflush(mIndexStream); error = ferror(mIndexStream); if ( mExportsSernums ) error |= appendToFolderIdsFile( idx ); if (error) { kdWarning(5006) << "Error: Could not add message to folder (No space left on tqdevice?)" << endl; if (ftell(mIndexStream) > revert) { kdWarning(5006) << "Undoing changes" << endl; truncate( TQFile::encodeName(indexLocation()), revert ); } if ( errno ) kmkernel->emergencyExit( i18n("Could not add message to folder:") + TQString::fromLocal8Bit(strerror(errno))); else kmkernel->emergencyExit( i18n("Could not add message to folder (No space left on tqdevice?)") ); /* This code may not be 100% reliable bool busy = kmkernel->kbp()->isBusy(); if (busy) kmkernel->kbp()->idle(); KMessageBox::sorry(0, i18n("Unable to add message to folder.\n" "(No space left on tqdevice or insufficient quota?)\n" "Free space and sufficient quota are required to continue safely.")); if (busy) kmkernel->kbp()->busy(); */ return error; } } if (aIndex_ret) *aIndex_ret = idx; emitMsgAddedSignals(idx); // All streams have been flushed without errors if we arrive here // Return success! // (Don't return status of stream, it may have been closed already.) return 0; } int KMFolderMbox::compact( unsigned int startIndex, int nbMessages, FILE* tmpfile, off_t& offs, bool& done ) { int rc = 0; TQCString mtext; unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() : TQMIN( mMsgList.count(), startIndex + nbMessages ); //kdDebug(5006) << "KMFolderMbox: compacting from " << startIndex << " to " << stopIndex << endl; for(unsigned int idx = startIndex; idx < stopIndex; ++idx) { KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at(idx); size_t msize = mi->msgSize(); if (mtext.size() < msize + 2) mtext.resize(msize+2); off_t folder_offset = mi->folderOffset(); //now we need to find the separator! grr... for(off_t i = folder_offset-25; true; i -= 20) { off_t chunk_offset = i <= 0 ? 0 : i; if(fseek(mStream, chunk_offset, SEEK_SET) == -1) { rc = errno; break; } if (mtext.size() < 20) mtext.resize(20); fread(mtext.data(), 20, 1, mStream); if(i <= 0) { //woops we've reached the top of the file, last try.. if ( mtext.contains( "from ", false ) ) { if (mtext.size() < (size_t)folder_offset) mtext.resize(folder_offset); if(fseek(mStream, chunk_offset, SEEK_SET) == -1 || !fread(mtext.data(), folder_offset, 1, mStream) || !fwrite(mtext.data(), folder_offset, 1, tmpfile)) { rc = errno; break; } offs += folder_offset; } else { rc = 666; } break; } else { int last_crlf = -1; for(int i2 = 0; i2 < 20; i2++) { if(*(mtext.data()+i2) == '\n') last_crlf = i2; } if(last_crlf != -1) { int size = folder_offset - (i + last_crlf+1); if ((int)mtext.size() < size) mtext.resize(size); if(fseek(mStream, i + last_crlf+1, SEEK_SET) == -1 || !fread(mtext.data(), size, 1, mStream) || !fwrite(mtext.data(), size, 1, tmpfile)) { rc = errno; break; } offs += size; break; } } } if (rc) break; //now actually write the message if(fseek(mStream, folder_offset, SEEK_SET) == -1 || !fread(mtext.data(), msize, 1, mStream) || !fwrite(mtext.data(), msize, 1, tmpfile)) { rc = errno; break; } mi->setFolderOffset(offs); offs += msize; } done = ( !rc && stopIndex == mMsgList.count() ); // finished without errors return rc; } //----------------------------------------------------------------------------- int KMFolderMbox::compact( bool silent ) { // This is called only when the user explicitely requests compaction, // so we don't check needsCompact. KMail::MboxCompactionJob* job = new KMail::MboxCompactionJob( folder(), true /*immediate*/ ); int rc = job->executeNow( silent ); // Note that job autodeletes itself. // If this is the current folder, the changed signal will ultimately call // KMHeaders::setFolderInfotqStatus which will override the message, so save/restore it TQString statusMsg = BroadcastStatus::instance()->statusMsg(); emit changed(); BroadcastStatus::instance()->seStatusMsg( statusMsg ); return rc; } //----------------------------------------------------------------------------- void KMFolderMbox::setLockType( LockType ltype ) { mLockType = ltype; } //----------------------------------------------------------------------------- void KMFolderMbox::setProcmailLockFileName( const TQString &fname ) { mProcmailLockFileName = fname; } //----------------------------------------------------------------------------- int KMFolderMbox::removeContents() { int rc = 0; rc = unlink(TQFile::encodeName(location())); return rc; } //----------------------------------------------------------------------------- int KMFolderMbox::expungeContents() { int rc = 0; if (truncate(TQFile::encodeName(location()), 0)) rc = errno; return rc; } //----------------------------------------------------------------------------- /*virtual*/ TQ_INT64 KMFolderMbox::doFolderSize() const { TQFileInfo info( location() ); return (TQ_INT64)(info.size()); } //----------------------------------------------------------------------------- #include "kmfoldermbox.moc"