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