/* -*- c-basic-offset: 2 -*- * kmail: KDE mail client * Copyright (c) 1996-1998 Stefan Taferner * * 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 "kmfoldermbox.h" #include #include #include #include #include #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 #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_FCNTL_H #include #endif #include #include #include #include #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]" #ifdef KMAIL_SQLITE_INDEX #include #endif //----------------------------------------------------------------------------- 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 ) { #ifdef FOLDER_REFCOUNT_DEBUGGING mOwners.append( owner ); kDebug() << endl << "open" << mOpenCount << folder()->name() << mOwners << ", adding:" << owner; // << mOwners << ", adding:" << owner << kBacktrace(); #else Q_UNUSED( owner ); #endif int rc = 0; mOpenCount++; kmkernel->jobScheduler()->notifyOpeningFolder( folder() ); if ( mOpenCount > 1 ) { return 0; // already open } assert( !folder()->name().isEmpty() ); mFilesLocked = false; mStream = KDE_fopen( QFile::encodeName( location() ), "r+" ); // messages file if ( !mStream ) { KMessageBox::sorry( 0, i18n( "Cannot open file \"%1\":\n%2", location(), strerror( errno ) ) ); kDebug() << "Cannot open folder `" << location() <<"':" << strerror(errno); mOpenCount = 0; return errno; } lock(); rc = openInternal( CheckIfIndexTooOld | CreateIndexFromContentsWhenReadIndexFailed ); /* moved to openInternal() if ( !folder()->path().isEmpty() ) { KMFolderIndex::IndexStatus index_status = indexStatus(); bool shouldCreateIndexFromContents = false; // 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 ) { QString msg = i18n("

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.

" "

Please read the corresponding entry " "in the FAQ section of the manual " "of KMail for " "information about how to prevent this " "problem from happening again.

", QString("help:/kmail/faq.html#faq-index-regeneration"), objectName()); // 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.readEntry( "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 ); } } #ifdef KMAIL_SQLITE_INDEX #else mIndexStream = 0; #endif shouldCreateIndexFromContents = true; emit statusMsg( i18n("Folder `%1' changed. Recreating index.", objectName()) ); } else { #ifdef KMAIL_SQLITE_INDEX #else mIndexStream = KDE_fopen( QFile::encodeName( indexLocation() ), "r+" ); // index file if ( mIndexStream ) { # ifndef Q_WS_WIN fcntl( fileno( mIndexStream ), F_SETFD, FD_CLOEXEC ); # endif if ( !updateIndexStreamPtr() ) return 1; } else shouldCreateIndexFromContents = true; #endif } if ( shouldCreateIndexFromContents ) { rc = createIndexFromContents(); } else { if ( !readIndex() ) { rc = createIndexFromContents(); } } } else { mAutoCreateIndex = false; rc = createIndexFromContents(); } mChanged = false;*/ #ifdef KMAIL_SQLITE_INDEX #else # ifndef Q_WS_WIN fcntl( fileno( mStream ), F_SETFD, FD_CLOEXEC ); if ( mIndexStream ) { fcntl( fileno( mIndexStream ), F_SETFD, FD_CLOEXEC ); } # endif #endif return rc; } //---------------------------------------------------------------------------- bool KMFolderMbox::canAccess() const { assert(!folder()->name().isEmpty()); QFileInfo finfo( location() ); if ( !finfo.isReadable() || !finfo.isWritable() ) { kDebug() << "call to access function failed"; return false; } return true; } //----------------------------------------------------------------------------- int KMFolderMbox::create() { int rc; int old_umask; assert(!folder()->name().isEmpty()); assert(mOpenCount == 0); kDebug() << "Creating folder" << objectName(); if (access(QFile::encodeName(location()), F_OK) == 0) { kDebug() << "call to access function failed."; kDebug() << "Error"; return EEXIST; } old_umask = umask(077); mStream = KDE_fopen(QFile::encodeName(location()), "w+"); //sven; open RW umask(old_umask); if (!mStream) return errno; #ifndef Q_WS_WIN fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC); #endif rc = createInternal(); if (!rc) lock(); return rc; } //----------------------------------------------------------------------------- void KMFolderMbox::reallyDoClose() { if ( mAutoCreateIndex ) { if ( KMFolderIndex::IndexOk != indexStatus() ) { kDebug() << "Critical error:" << location() << "has been modified by an external application while KMail was running."; // exit(1); backed out due to broken nfs } updateIndex( true ); writeConfig(); } if ( !noContent() ) { if ( mStream ) { unlock(); } mMsgList.clear( true ); if ( mStream ) { fclose( mStream ); } #ifdef KMAIL_SQLITE_INDEX if ( mIndexDb ) sqlite3_close( mIndexDb ); #else if ( mIndexStream ) { fclose( mIndexStream ); updateIndexStreamPtr( true ); } #endif } #ifdef KMAIL_SQLITE_INDEX mIndexDb = 0; #else mIndexStream = 0; #endif mOpenCount = 0; mStream = 0; mFilesLocked = false; mUnreadMsgs = -1; mMsgList.reset( INIT_MSGS ); } //----------------------------------------------------------------------------- void KMFolderMbox::sync() { #ifdef KMAIL_SQLITE_INDEX #else if (mOpenCount > 0) if (!mStream || fsync(fileno(mStream)) || !mIndexStream || fsync(fileno(mIndexStream))) { kmkernel->emergencyExit( i18n("Could not sync index file %1: %2", indexLocation(), errno ? QString::fromLocal8Bit(strerror(errno)) : i18n("Internal error. Please copy down the details and report a bug."))); } #endif } //----------------------------------------------------------------------------- int KMFolderMbox::lock() { #ifdef Q_WS_WIN # ifdef __GNUC__ # warning TODO implement mbox locking on Windows # else # pragma WARNING( TODO implement mbox locking on Windows ) # endif assert(mStream != 0); mFilesLocked = true; mReadOnly = false; #else 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; QByteArray cmd_str; assert(mStream != 0); mFilesLocked = false; mReadOnly = false; switch( mLockType ) { case FCNTL: rc = fcntl(fileno(mStream), F_SETLKW, &fl); if (rc < 0) { kDebug() << "Cannot lock folder `" << location() << "':" << strerror(errno) << "(" << errno << ")"; mReadOnly = true; return errno; } #ifdef KMAIL_SQLITE_INDEX #else if (mIndexStream) { rc = fcntl(fileno(mIndexStream), F_SETLK, &fl); if (rc < 0) { kDebug() << "Cannot lock index of folder `" << location() << "':" << strerror(errno) << "(" << errno << ")"; rc = errno; fl.l_type = F_UNLCK; /*rc =*/ fcntl(fileno(mIndexStream), F_SETLK, &fl); mReadOnly = true; return rc; } } #endif break; case procmail_lockfile: cmd_str = "lockfile -l20 -r5 "; if (!mProcmailLockFileName.isEmpty()) cmd_str += QFile::encodeName(KShell::quoteArg(mProcmailLockFileName)); else cmd_str += QFile::encodeName(KShell::quoteArg(location() + ".lock")); rc = system( cmd_str.data() ); if( rc != 0 ) { kDebug() << "Cannot lock folder `" << location() << "':" << strerror(rc) << "(" << rc << ")"; mReadOnly = true; return rc; } #ifdef KMAIL_SQLITE_INDEX #else if( mIndexStream ) { cmd_str = "lockfile -l20 -r5 " + QFile::encodeName(KShell::quoteArg(indexLocation() + ".lock")); rc = system( cmd_str.data() ); if( rc != 0 ) { kDebug() << "Cannot lock index of folder `" << location() << "':" << strerror(rc) << "(" << rc << ")"; mReadOnly = true; return rc; } } #endif break; case mutt_dotlock: cmd_str = "mutt_dotlock " + QFile::encodeName(KShell::quoteArg(location())); rc = system( cmd_str.data() ); if( rc != 0 ) { kDebug() << "Cannot lock folder `" << location() << "':" << strerror(rc) << "(" << rc << ")"; mReadOnly = true; return rc; } #ifdef KMAIL_SQLITE_INDEX #else if( mIndexStream ) { cmd_str = "mutt_dotlock " + QFile::encodeName(KShell::quoteArg(indexLocation())); rc = system( cmd_str.data() ); if( rc != 0 ) { kDebug() << "Cannot lock index of folder `" << location() << "':" << strerror(rc) << "(" << rc << ")"; mReadOnly = true; return rc; } } #endif break; case mutt_dotlock_privileged: cmd_str = "mutt_dotlock -p " + QFile::encodeName(KShell::quoteArg(location())); rc = system( cmd_str.data() ); if( rc != 0 ) { kDebug() << "Cannot lock folder `" << location() << "':" << strerror(rc) << "(" << rc << ")"; mReadOnly = true; return rc; } #ifdef KMAIL_SQLITE_INDEX #else if( mIndexStream ) { cmd_str = "mutt_dotlock -p " + QFile::encodeName(KShell::quoteArg(indexLocation())); rc = system( cmd_str.data() ); if( rc != 0 ) { kDebug() << "Cannot lock index of folder `" << location() << "':" << strerror(rc) << "(" << rc << ")"; mReadOnly = true; return rc; } } #endif break; case lock_none: default: break; } mFilesLocked = true; #endif return 0; } //------------------------------------------------------------- FolderJob* KMFolderMbox::doCreateJob( KMMessage *msg, FolderJob::JobType jt, KMFolder *folder, const QString&, const AttachmentStrategy* ) const { MboxJob *job = new MboxJob( msg, jt, folder ); job->setParent( this ); return job; } //------------------------------------------------------------- FolderJob* KMFolderMbox::doCreateJob( QList& msgList, const QString& sets, FolderJob::JobType jt, KMFolder *folder ) const { MboxJob *job = new MboxJob( msgList, sets, jt, folder ); job->setParent( this ); return job; } //----------------------------------------------------------------------------- int KMFolderMbox::unlock() { #ifdef Q_WS_WIN # ifdef __GNUC__ # warning TODO implement mbox unlocking on Windows # else # pragma WARNING( TODO implement mbox unlocking on Windows ) # endif mFilesLocked = false; return 0; #else int rc; struct flock fl; fl.l_type=F_UNLCK; fl.l_whence=0; fl.l_start=0; fl.l_len=0; QByteArray cmd_str; assert(mStream != 0); mFilesLocked = false; switch( mLockType ) { case FCNTL: #ifdef KMAIL_SQLITE_INDEX #else if (mIndexStream) fcntl(fileno(mIndexStream), F_SETLK, &fl); #endif fcntl(fileno(mStream), F_SETLK, &fl); rc = errno; break; case procmail_lockfile: cmd_str = "rm -f "; if (!mProcmailLockFileName.isEmpty()) cmd_str += QFile::encodeName(KShell::quoteArg(mProcmailLockFileName)); else cmd_str += QFile::encodeName(KShell::quoteArg(location() + ".lock")); rc = system( cmd_str.data() ); #ifdef KMAIL_SQLITE_INDEX #else if( mIndexStream ) { cmd_str = "rm -f " + QFile::encodeName(KShell::quoteArg(indexLocation() + ".lock")); rc = system( cmd_str.data() ); } #endif break; case mutt_dotlock: cmd_str = "mutt_dotlock -u " + QFile::encodeName(KShell::quoteArg(location())); rc = system( cmd_str.data() ); #ifdef KMAIL_SQLITE_INDEX #else if( mIndexStream ) { cmd_str = "mutt_dotlock -u " + QFile::encodeName(KShell::quoteArg(indexLocation())); rc = system( cmd_str.data() ); } #endif break; case mutt_dotlock_privileged: cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KShell::quoteArg(location())); rc = system( cmd_str.data() ); #ifdef KMAIL_SQLITE_INDEX #else if( mIndexStream ) { cmd_str = "mutt_dotlock -p -u " + QFile::encodeName(KShell::quoteArg(indexLocation())); rc = system( cmd_str.data() ); } #endif break; case lock_none: default: rc = 0; break; } return rc; #endif } //----------------------------------------------------------------------------- KMFolderIndex::IndexStatus KMFolderMbox::indexStatus() { QFileInfo contInfo(location()); QFileInfo 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]; QByteArray subjStr, dateStr, fromStr, toStr, xmarkStr, *lastStr=0; QByteArray replyToIdStr, replyToAuxIdStr, referencesStr, msgIdStr; QByteArray sizeServerStr, uidStr; QByteArray contentTypeStr, charset; bool atEof = false; bool inHeader = true; KMMsgInfo* mi; QString msgStr; QRegExp regexp(MSG_SEPERATOR_REGEX); int i, num, numStatus; short needStatus; assert(mStream != 0); rewind(mStream); mMsgList.clear(); num = -1; numStatus= 11; off_t offs = 0; size_t size = 0; dateStr = ""; fromStr = ""; toStr = ""; subjStr = ""; *status = '\0'; *xstatus = '\0'; xmarkStr = ""; replyToIdStr = ""; replyToAuxIdStr = ""; referencesStr = ""; msgIdStr = ""; needStatus = 3; size_t sizeServer = 0; ulong uid = 0; while (!atEof) { off_t pos = KDE_ftell(mStream); if (!fgets(line, MAX_LINE, mStream)) atEof = true; if (atEof || (memcmp(line, MSG_SEPERATOR_START, MSG_SEPERATOR_START_LEN)==0 && regexp.indexIn(line) >= 0)) { size = pos - offs; pos = KDE_ftell(mStream); if (num >= 0) { if (numStatus <= 0) { msgStr = i18np("Creating index file: one message done", "Creating index file: %1 messages done", num); emit statusMsg(msgStr); numStatus = 10; } if (size > 0) { msgIdStr = msgIdStr.trimmed(); if( !msgIdStr.isEmpty() ) { int rightAngle; rightAngle = msgIdStr.indexOf( '>' ); if( rightAngle != -1 ) msgIdStr.truncate( rightAngle + 1 ); } replyToIdStr = replyToIdStr.trimmed(); if( !replyToIdStr.isEmpty() ) { int rightAngle; rightAngle = replyToIdStr.indexOf( '>' ); if( rightAngle != -1 ) replyToIdStr.truncate( rightAngle + 1 ); } referencesStr = referencesStr.trimmed(); if( !referencesStr.isEmpty() ) { int leftAngle, rightAngle; leftAngle = referencesStr.lastIndexOf( '<' ); 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.lastIndexOf( '<', leftAngle - 1 ); if( leftAngle != -1 ) referencesStr = referencesStr.mid( leftAngle ); rightAngle = referencesStr.lastIndexOf( '>' ); 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.indexOf( '>' ); if( rightAngle != -1 ) replyToAuxIdStr.truncate( rightAngle + 1 ); } contentTypeStr = contentTypeStr.trimmed(); charset = ""; if ( !contentTypeStr.isEmpty() ) { int cidx = contentTypeStr.indexOf( "charset=" ); if ( cidx != -1 ) { charset = contentTypeStr.mid( cidx + 8 ); if ( !charset.isEmpty() && ( charset[0] == '"' ) ) { charset = charset.mid( 1 ); } cidx = 0; while ( cidx < charset.length() ) { if ( charset[cidx] == '"' || ( !isalnum(charset[cidx]) && charset[cidx] != '-' && charset[cidx] != '_' ) ) { break; } ++cidx; } charset.truncate( cidx ); } } mi = new KMMsgInfo(folder()); mi->init( subjStr.trimmed(), fromStr.trimmed(), toStr.trimmed(), 0, MessageStatus::statusNew(), xmarkStr.trimmed(), replyToIdStr, replyToAuxIdStr, msgIdStr, KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown, KMMsgMDNStateUnknown, charset, offs, size, sizeServer, uid ); mi->setStatus(status, xstatus); mi->setDate( dateStr.trimmed().constData() ); mi->setDirty(false); mMsgList.append(mi, mExportsSernums ); *status = '\0'; *xstatus = '\0'; needStatus = 3; xmarkStr = ""; replyToIdStr = ""; replyToAuxIdStr = ""; referencesStr = ""; msgIdStr = ""; dateStr = ""; fromStr = ""; subjStr = ""; sizeServer = 0; uid = 0; } else num--,numStatus++; } offs = KDE_ftell(mStream); num++; numStatus--; 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 ( ( needStatus & 1) && strncasecmp( line, "Status:", 7 ) == 0 ) { for ( i=0; i<4 && line[i+8] > ' '; ++i ) { status[i] = line[i+8]; } status[i] = '\0'; needStatus &= ~1; } else if ( ( needStatus & 2 ) && strncasecmp( line, "X-Status:", 9 ) == 0 ) { for ( i=0; i<4 && line[i+10] > ' '; ++i ) { xstatus[i] = line[i+10]; } xstatus[i] = '\0'; needStatus &= ~2; } else if ( strncasecmp( line, "X-KMail-Mark:", 13 ) == 0 ) { xmarkStr = QByteArray( line + 13 ); } else if ( strncasecmp( line, "In-Reply-To:", 12 ) == 0 ) { replyToIdStr = QByteArray( line + 12 ); lastStr = &replyToIdStr; } else if ( strncasecmp( line, "References:", 11 ) == 0 ) { referencesStr = QByteArray( line + 11 ); lastStr = &referencesStr; } else if ( strncasecmp( line, "Message-Id:", 11 ) == 0 ) { msgIdStr = QByteArray( line + 11 ); lastStr = &msgIdStr; } else if ( strncasecmp( line, "Date:", 5 ) == 0 ) { dateStr = QByteArray( line + 5 ); lastStr = &dateStr; } else if ( strncasecmp( line, "From:", 5 ) == 0 ) { fromStr = QByteArray( line + 5 ); lastStr = &fromStr; } else if ( strncasecmp( line, "To:", 3 ) == 0 ) { toStr = QByteArray( line + 3 ); lastStr = &toStr; } else if ( strncasecmp( line, "Subject:", 8 ) == 0 ) { subjStr = QByteArray( line + 8 ); lastStr = &subjStr; } else if ( strncasecmp( line, "X-Length:", 9 ) == 0 ) { sizeServerStr = QByteArray( line + 9 ); sizeServer = sizeServerStr.toULong(); lastStr = &sizeServerStr; } else if ( strncasecmp( line, "X-UID:", 6 ) == 0 ) { uidStr = QByteArray( line + 6 ); uid = uidStr.toULong(); lastStr = &uidStr; } else if ( strncasecmp( line, "Content-Type:", 13 ) == 0 ) { contentTypeStr = QByteArray( line + 13 ); lastStr = &contentTypeStr; } } if ( mAutoCreateIndex ) { emit statusMsg( i18n("Writing index file") ); writeIndex(); } else { #ifdef KMAIL_SQLITE_INDEX #else mHeaderOffset = 0; #endif } 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;\n" "please 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); // note that mi is deleted by the line below 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 ( qstrncmp( 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 QByteArray 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 QByteArray result( int( strLen + 5 ) / 6 * 7 + 1, '\0' ); 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 && qstrncmp( 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 ]; KDE_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; } KMFolderOpener openThis( folder(), "mboxaddMsg" ); if ( openThis.openResult() ) { kDebug() << openThis.openResult() << " of folder: " << label(); return openThis.openResult(); } // take message out of the folder it is currently in, if any KMFolder* msgParent = aMsg->parent(); int idx = -1; if ( msgParent ) { if ( msgParent== folder() ) { if ( kmkernel->folderIsDraftOrOutbox( folder() ) ) { //special case for Edit message. kDebug() << "Editing message in outbox or drafts"; } else { return 0; } } idx = msgParent->find( aMsg ); msgParent->getMsg( idx ); } if ( folderType() != KMFolderTypeImap ) { /* QFile fileD0( "testdat_xx-kmfoldermbox-0" ); if( fileD0.open( QIODevice::WriteOnly ) ) { QDataStream ds( &fileD0 ); ds.writeRawData( aMsg->asString(), aMsg->asString().length() ); fileD0.close(); // If data is 0 we just create a zero length file. } */ aMsg->setStatusFields(); /* QFile fileD1( "testdat_xx-kmfoldermbox-1" ); if( fileD1.open( QIODevice::WriteOnly ) ) { QDataStream ds( &fileD1 ); ds.writeRawData( 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 } QByteArray msgText = escapeFrom( aMsg->asDwString() ); size_t len = msgText.size(); assert( mStream != 0 ); clearerr( mStream ); if ( len <= 0 ) { kDebug() << "Message added to folder `" << objectName() << "' contains no data. Ignoring it."; return 0; } // Make sure the file is large enough to check for an end // character KDE_fseek( mStream, 0, SEEK_END ); off_t revert = KDE_ftell( mStream ); int growth = 0; if ( KDE_ftell( mStream ) >= 2 ) { // write message to folder file char endStr[3]; KDE_fseek( mStream, -2, SEEK_END ); fread( endStr, 1, 2, mStream ); // ensure separating empty line if ( KDE_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 ); } } } KDE_fseek( mStream, 0, SEEK_END ); // this is needed on solaris and others int error = ferror( mStream ); if ( error ) return error; QByteArray messageSeparator( aMsg->mboxMessageSeparator() ); fwrite( messageSeparator.data(), messageSeparator.length(), 1, mStream ); off_t offs = KDE_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 = KDE_ftell( mStream ) - offs; error = ferror( mStream ); if ( error ) { kDebug() << "Error: Could not add message to folder:" << strerror(errno); if ( KDE_ftell( mStream ) > revert ) { kDebug() << "Undoing changes"; truncate( QFile::encodeName(location()), revert ); } kmkernel->emergencyExit( i18n("Could not add message to folder: ") + QString::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 device 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->status().isUnread() || aMsg->status().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(); // 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 ) { #ifdef KMAIL_SQLITE_INDEX // reset the db id, in case we have one, we are about to change folders // and can't reuse it there aMsg->setDbId( 0 ); #else assert( mIndexStream != 0 ); clearerr( mIndexStream ); KDE_fseek( mIndexStream, 0, SEEK_END ); revert = KDE_ftell( mIndexStream ); #endif KMMsgBase * mb = &aMsg->toMsgBase(); error = writeMessages( mb, true /*flush*/ ); if ( mExportsSernums ) { error |= appendToFolderIdsFile( idx ); } if (error) { kWarning() <<"Error: Could not add message to folder (No space left on device?)"; #ifdef KMAIL_SQLITE_INDEX #else if ( KDE_ftell( mIndexStream ) > revert ) { kWarning() <<"Undoing changes"; truncate( QFile::encodeName( indexLocation() ), revert ); } #endif if ( errno ) { kmkernel->emergencyExit( i18n("Could not add message to folder: ") + QString::fromLocal8Bit( strerror( errno ) ) ); } else { kmkernel->emergencyExit( i18n("Could not add message to folder (No space left on device?)") ); } /* 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 device 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; QByteArray mtext; unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() : qMin( mMsgList.count(), startIndex + nbMessages ); //kDebug() << "KMFolderMbox: compacting from" << startIndex <<" to" << stopIndex; for ( unsigned int idx = startIndex; idx < stopIndex; ++idx ) { KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at( idx ); size_t msize = mi->msgSize(); if ( (size_t) 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 ( KDE_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.indexOf( "from " ) ) { if ( (off_t) mtext.size() < folder_offset ) { mtext.resize( folder_offset ); } if ( KDE_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 ( KDE_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 ( KDE_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 explicitly 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::setFolderInfoStatus which will override the message, // so save/restore it QString statusMsg = BroadcastStatus::instance()->statusMsg(); emit changed(); BroadcastStatus::instance()->setStatusMsg( statusMsg ); return rc; } //----------------------------------------------------------------------------- void KMFolderMbox::setLockType( LockType ltype ) { mLockType = ltype; } //----------------------------------------------------------------------------- void KMFolderMbox::setProcmailLockFileName( const QString &fname ) { mProcmailLockFileName = fname; } //----------------------------------------------------------------------------- int KMFolderMbox::removeContents() { int rc = 0; rc = unlink(QFile::encodeName(location())); return rc; } //----------------------------------------------------------------------------- int KMFolderMbox::expungeContents() { int rc = 0; if (truncate(QFile::encodeName(location()), 0)) rc = errno; return rc; } //----------------------------------------------------------------------------- /*virtual*/ qint64 KMFolderMbox::doFolderSize() const { QFileInfo info( location() ); return info.size(); } //----------------------------------------------------------------------------- #include "kmfoldermbox.moc"