kmail

kmfoldermbox.cpp
1/*
2 * kmail: KDE mail client
3 * Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 *
19 */
20#include <config.h>
21#include <tqfileinfo.h>
22#include <tqregexp.h>
23
24#include "kmfoldermbox.h"
25#include "folderstorage.h"
26#include "kmfolder.h"
27#include "kmkernel.h"
28#include "kmmsgdict.h"
29#include "undostack.h"
30#include "kcursorsaver.h"
31#include "jobscheduler.h"
32#include "compactionjob.h"
33#include "util.h"
34
35#include <kdebug.h>
36#include <tdelocale.h>
37#include <tdemessagebox.h>
38#include <knotifyclient.h>
39#include <tdeprocess.h>
40#include <tdeconfig.h>
41
42#include <ctype.h>
43#include <stdio.h>
44#include <errno.h>
45#include <assert.h>
46#include <ctype.h>
47#include <unistd.h>
48
49#include <fcntl.h>
50
51#include <stdlib.h>
52#include <sys/types.h>
53#include <sys/stat.h>
54#include <sys/file.h>
55#include "broadcaststatus.h"
56using KPIM::BroadcastStatus;
57
58#ifndef MAX_LINE
59#define MAX_LINE 4096
60#endif
61#ifndef INIT_MSGS
62#define INIT_MSGS 8
63#endif
64
65// Regular expression to find the line that seperates messages in a mail
66// folder:
67#define MSG_SEPERATOR_START "From "
68#define MSG_SEPERATOR_START_LEN (sizeof(MSG_SEPERATOR_START) - 1)
69#define MSG_SEPERATOR_REGEX "^From .*[0-9][0-9]:[0-9][0-9]"
70
71
72//-----------------------------------------------------------------------------
73KMFolderMbox::KMFolderMbox(KMFolder* folder, const char* name)
74 : KMFolderIndex(folder, name)
75{
76 mStream = 0;
77 mFilesLocked = false;
78 mReadOnly = false;
79 mLockType = lock_none;
80}
81
82
83//-----------------------------------------------------------------------------
84KMFolderMbox::~KMFolderMbox()
85{
86 if (mOpenCount>0)
87 close("~kmfoldermbox", true);
88 if (kmkernel->undoStack())
89 kmkernel->undoStack()->folderDestroyed( folder() );
90}
91
92//-----------------------------------------------------------------------------
93int KMFolderMbox::open(const char *owner)
94{
95 Q_UNUSED( owner );
96 int rc = 0;
97
98 mOpenCount++;
99 kmkernel->jobScheduler()->notifyOpeningFolder( folder() );
100
101 if (mOpenCount > 1) return 0; // already open
102
103 assert(!folder()->name().isEmpty());
104
105 mFilesLocked = false;
106 mStream = fopen(TQFile::encodeName(location()), "r+"); // messages file
107 if (!mStream)
108 {
109 KNotifyClient::event( 0, "warning",
110 i18n("Cannot open file \"%1\":\n%2").arg(location()).arg(strerror(errno)));
111 kdDebug(5006) << "Cannot open folder `" << location() << "': " << strerror(errno) << endl;
112 mOpenCount = 0;
113 return errno;
114 }
115
116 lock();
117
118 if (!folder()->path().isEmpty())
119 {
120 KMFolderIndex::IndexStatus index_status = indexStatus();
121 // test if index file exists and is up-to-date
122 if (KMFolderIndex::IndexOk != index_status)
123 {
124 // only show a warning if the index file exists, otherwise it can be
125 // silently regenerated
126 if (KMFolderIndex::IndexTooOld == index_status) {
127 TQString msg = i18n("<qt><p>The index of folder '%2' seems "
128 "to be out of date. To prevent message "
129 "corruption the index will be "
130 "regenerated. As a result deleted "
131 "messages might reappear and status "
132 "flags might be lost.</p>"
133 "<p>Please read the corresponding entry "
134 "in the <a href=\"%1\">FAQ section of the manual "
135 "of KMail</a> for "
136 "information about how to prevent this "
137 "problem from happening again.</p></qt>")
138 .arg("help:/kmail/faq.html#faq-index-regeneration")
139 .arg(name());
140 // When KMail is starting up we have to show a non-blocking message
141 // box so that the initialization can continue. We don't show a
142 // queued message box when KMail isn't starting up because queued
143 // message boxes don't have a "Don't ask again" checkbox.
144 if (kmkernel->startingUp())
145 {
146 TDEConfigGroup configGroup( KMKernel::config(), "Notification Messages" );
147 bool showMessage =
148 configGroup.readBoolEntry( "showIndexRegenerationMessage", true );
149 if (showMessage)
150 KMessageBox::queuedMessageBox( 0, KMessageBox::Information,
151 msg, i18n("Index Out of Date"),
152 KMessageBox::AllowLink );
153 }
154 else
155 {
156 KCursorSaver idle(KBusyPtr::idle());
157 KMessageBox::information( 0, msg, i18n("Index Out of Date"),
158 "showIndexRegenerationMessage",
159 KMessageBox::AllowLink );
160 }
161 }
162 TQString str;
163 mIndexStream = 0;
164 str = i18n("Folder `%1' changed. Recreating index.")
165 .arg(name());
166 emit statusMsg(str);
167 } else {
168 mIndexStream = fopen(TQFile::encodeName(indexLocation()), "r+"); // index file
169 if ( mIndexStream ) {
170 fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
171 updateIndexStreamPtr();
172 }
173 }
174
175 if (!mIndexStream)
176 rc = createIndexFromContents();
177 else
178 if (!readIndex())
179 rc = createIndexFromContents();
180 }
181 else
182 {
183 mAutoCreateIndex = false;
184 rc = createIndexFromContents();
185 }
186
187 mChanged = false;
188
189 fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC);
190 if (mIndexStream)
191 fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
192
193 return rc;
194}
195
196//----------------------------------------------------------------------------
197int KMFolderMbox::canAccess()
198{
199 assert(!folder()->name().isEmpty());
200
201 if (access(TQFile::encodeName(location()), R_OK | W_OK) != 0) {
202 kdDebug(5006) << "KMFolderMbox::access call to access function failed" << endl;
203 return 1;
204 }
205 return 0;
206}
207
208//-----------------------------------------------------------------------------
209int KMFolderMbox::create()
210{
211 int rc;
212 int old_umask;
213
214 assert(!folder()->name().isEmpty());
215 assert(mOpenCount == 0);
216
217 kdDebug(5006) << "Creating folder " << name() << endl;
218 if (access(TQFile::encodeName(location()), F_OK) == 0) {
219 kdDebug(5006) << "KMFolderMbox::create call to access function failed." << endl;
220 kdDebug(5006) << "File:: " << endl;
221 kdDebug(5006) << "Error " << endl;
222 return EEXIST;
223 }
224
225 old_umask = umask(077);
226 mStream = fopen(TQFile::encodeName(location()), "w+"); //sven; open RW
227 umask(old_umask);
228
229 if (!mStream) return errno;
230
231 fcntl(fileno(mStream), F_SETFD, FD_CLOEXEC);
232
233 if (!folder()->path().isEmpty())
234 {
235 old_umask = umask(077);
236 mIndexStream = fopen(TQFile::encodeName(indexLocation()), "w+"); //sven; open RW
237 updateIndexStreamPtr(true);
238 umask(old_umask);
239
240 if (!mIndexStream) return errno;
241 fcntl(fileno(mIndexStream), F_SETFD, FD_CLOEXEC);
242 }
243 else
244 {
245 mAutoCreateIndex = false;
246 }
247
248 mOpenCount++;
249 mChanged = false;
250
251 rc = writeIndex();
252 if (!rc) lock();
253 return rc;
254}
255
256
257//-----------------------------------------------------------------------------
258void KMFolderMbox::reallyDoClose(const char* owner)
259{
260 Q_UNUSED( owner );
261 if (mAutoCreateIndex)
262 {
263 if (KMFolderIndex::IndexOk != indexStatus()) {
264 kdDebug(5006) << "Critical error: " << location() <<
265 " has been modified by an external application while KMail was running." << endl;
266 // exit(1); backed out due to broken nfs
267 }
268
269 updateIndex();
270 writeConfig();
271 }
272
273 if (!noContent()) {
274 if (mStream) unlock();
275 mMsgList.clear(true);
276
277 if (mStream) fclose(mStream);
278 if (mIndexStream) {
279 fclose(mIndexStream);
280 updateIndexStreamPtr(true);
281 }
282 }
283 mOpenCount = 0;
284 mStream = 0;
285 mIndexStream = 0;
286 mFilesLocked = false;
287 mUnreadMsgs = -1;
288
289 mMsgList.reset(INIT_MSGS);
290}
291
292//-----------------------------------------------------------------------------
293void KMFolderMbox::sync()
294{
295 if (mOpenCount > 0)
296 if (!mStream || fsync(fileno(mStream)) ||
297 !mIndexStream || fsync(fileno(mIndexStream))) {
298 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.")));
299 }
300}
301
302//-----------------------------------------------------------------------------
303int KMFolderMbox::lock()
304{
305 int rc;
306 struct flock fl;
307 fl.l_type=F_WRLCK;
308 fl.l_whence=0;
309 fl.l_start=0;
310 fl.l_len=0;
311 fl.l_pid=-1;
312 TQCString cmd_str;
313 assert(mStream != 0);
314 mFilesLocked = false;
315 mReadOnly = false;
316
317 switch( mLockType )
318 {
319 case FCNTL:
320 rc = fcntl(fileno(mStream), F_SETLKW, &fl);
321
322 if (rc < 0)
323 {
324 kdDebug(5006) << "Cannot lock folder `" << location() << "': "
325 << strerror(errno) << " (" << errno << ")" << endl;
326 mReadOnly = true;
327 return errno;
328 }
329
330 if (mIndexStream)
331 {
332 rc = fcntl(fileno(mIndexStream), F_SETLK, &fl);
333
334 if (rc < 0)
335 {
336 kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
337 << strerror(errno) << " (" << errno << ")" << endl;
338 rc = errno;
339 fl.l_type = F_UNLCK;
340 /*rc =*/ fcntl(fileno(mIndexStream), F_SETLK, &fl);
341 mReadOnly = true;
342 return rc;
343 }
344 }
345 break;
346
347 case procmail_lockfile:
348 cmd_str = "lockfile -l20 -r5 ";
349 if (!mProcmailLockFileName.isEmpty())
350 cmd_str += TQFile::encodeName(TDEProcess::quote(mProcmailLockFileName));
351 else
352 cmd_str += TQFile::encodeName(TDEProcess::quote(location() + ".lock"));
353
354 rc = system( cmd_str.data() );
355 if( rc != 0 )
356 {
357 kdDebug(5006) << "Cannot lock folder `" << location() << "': "
358 << strerror(rc) << " (" << rc << ")" << endl;
359 mReadOnly = true;
360 return rc;
361 }
362 if( mIndexStream )
363 {
364 cmd_str = "lockfile -l20 -r5 " + TQFile::encodeName(TDEProcess::quote(indexLocation() + ".lock"));
365 rc = system( cmd_str.data() );
366 if( rc != 0 )
367 {
368 kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
369 << strerror(rc) << " (" << rc << ")" << endl;
370 mReadOnly = true;
371 return rc;
372 }
373 }
374 break;
375
376 case mutt_dotlock:
377 cmd_str = "mutt_dotlock " + TQFile::encodeName(TDEProcess::quote(location()));
378 rc = system( cmd_str.data() );
379 if( rc != 0 )
380 {
381 kdDebug(5006) << "Cannot lock folder `" << location() << "': "
382 << strerror(rc) << " (" << rc << ")" << endl;
383 mReadOnly = true;
384 return rc;
385 }
386 if( mIndexStream )
387 {
388 cmd_str = "mutt_dotlock " + TQFile::encodeName(TDEProcess::quote(indexLocation()));
389 rc = system( cmd_str.data() );
390 if( rc != 0 )
391 {
392 kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
393 << strerror(rc) << " (" << rc << ")" << endl;
394 mReadOnly = true;
395 return rc;
396 }
397 }
398 break;
399
400 case mutt_dotlock_privileged:
401 cmd_str = "mutt_dotlock -p " + TQFile::encodeName(TDEProcess::quote(location()));
402 rc = system( cmd_str.data() );
403 if( rc != 0 )
404 {
405 kdDebug(5006) << "Cannot lock folder `" << location() << "': "
406 << strerror(rc) << " (" << rc << ")" << endl;
407 mReadOnly = true;
408 return rc;
409 }
410 if( mIndexStream )
411 {
412 cmd_str = "mutt_dotlock -p " + TQFile::encodeName(TDEProcess::quote(indexLocation()));
413 rc = system( cmd_str.data() );
414 if( rc != 0 )
415 {
416 kdDebug(5006) << "Cannot lock index of folder `" << location() << "': "
417 << strerror(rc) << " (" << rc << ")" << endl;
418 mReadOnly = true;
419 return rc;
420 }
421 }
422 break;
423
424 case lock_none:
425 default:
426 break;
427 }
428
429
430 mFilesLocked = true;
431 return 0;
432}
433
434//-------------------------------------------------------------
435FolderJob*
436KMFolderMbox::doCreateJob( KMMessage *msg, FolderJob::JobType jt,
437 KMFolder *folder, TQString, const AttachmentStrategy* ) const
438{
439 MboxJob *job = new MboxJob( msg, jt, folder );
440 job->setParent( this );
441 return job;
442}
443
444//-------------------------------------------------------------
445FolderJob*
446KMFolderMbox::doCreateJob( TQPtrList<KMMessage>& msgList, const TQString& sets,
447 FolderJob::JobType jt, KMFolder *folder ) const
448{
449 MboxJob *job = new MboxJob( msgList, sets, jt, folder );
450 job->setParent( this );
451 return job;
452}
453
454//-----------------------------------------------------------------------------
455int KMFolderMbox::unlock()
456{
457 int rc;
458 struct flock fl;
459 fl.l_type=F_UNLCK;
460 fl.l_whence=0;
461 fl.l_start=0;
462 fl.l_len=0;
463 TQCString cmd_str;
464
465 assert(mStream != 0);
466 mFilesLocked = false;
467
468 switch( mLockType )
469 {
470 case FCNTL:
471 if (mIndexStream) fcntl(fileno(mIndexStream), F_SETLK, &fl);
472 fcntl(fileno(mStream), F_SETLK, &fl);
473 rc = errno;
474 break;
475
476 case procmail_lockfile:
477 cmd_str = "rm -f ";
478 if (!mProcmailLockFileName.isEmpty())
479 cmd_str += TQFile::encodeName(TDEProcess::quote(mProcmailLockFileName));
480 else
481 cmd_str += TQFile::encodeName(TDEProcess::quote(location() + ".lock"));
482
483 rc = system( cmd_str.data() );
484 if( mIndexStream )
485 {
486 cmd_str = "rm -f " + TQFile::encodeName(TDEProcess::quote(indexLocation() + ".lock"));
487 rc = system( cmd_str.data() );
488 }
489 break;
490
491 case mutt_dotlock:
492 cmd_str = "mutt_dotlock -u " + TQFile::encodeName(TDEProcess::quote(location()));
493 rc = system( cmd_str.data() );
494 if( mIndexStream )
495 {
496 cmd_str = "mutt_dotlock -u " + TQFile::encodeName(TDEProcess::quote(indexLocation()));
497 rc = system( cmd_str.data() );
498 }
499 break;
500
501 case mutt_dotlock_privileged:
502 cmd_str = "mutt_dotlock -p -u " + TQFile::encodeName(TDEProcess::quote(location()));
503 rc = system( cmd_str.data() );
504 if( mIndexStream )
505 {
506 cmd_str = "mutt_dotlock -p -u " + TQFile::encodeName(TDEProcess::quote(indexLocation()));
507 rc = system( cmd_str.data() );
508 }
509 break;
510
511 case lock_none:
512 default:
513 rc = 0;
514 break;
515 }
516
517 return rc;
518}
519
520
521//-----------------------------------------------------------------------------
522KMFolderIndex::IndexStatus KMFolderMbox::indexStatus()
523{
524 if ( !mCompactable )
525 return KMFolderIndex::IndexCorrupt;
526
527 TQFileInfo contInfo(location());
528 TQFileInfo indInfo(indexLocation());
529
530 if (!contInfo.exists()) return KMFolderIndex::IndexOk;
531 if (!indInfo.exists()) return KMFolderIndex::IndexMissing;
532
533 // Check whether the mbox file is more than 5 seconds newer than the index
534 // file. The 5 seconds are added to reduce the number of false alerts due
535 // to slightly out of sync clocks of the NFS server and the local machine.
536 return ( contInfo.lastModified() > indInfo.lastModified().addSecs(5) )
537 ? KMFolderIndex::IndexTooOld
538 : KMFolderIndex::IndexOk;
539}
540
541
542//-----------------------------------------------------------------------------
543int KMFolderMbox::createIndexFromContents()
544{
545 char line[MAX_LINE];
546 char status[8], xstatus[8];
547 TQCString subjStr, dateStr, fromStr, toStr, xmarkStr, *lastStr=0;
548 TQCString replyToIdStr, replyToAuxIdStr, referencesStr, msgIdStr;
549 TQCString sizeServerStr, uidStr;
550 TQCString contentTypeStr, charset;
551 bool atEof = false;
552 bool inHeader = true;
553 KMMsgInfo* mi;
554 TQString msgStr;
555 TQRegExp regexp(MSG_SEPERATOR_REGEX);
556 int i, num, numStatus;
557 short needStatus;
558
559 assert(mStream != 0);
560 rewind(mStream);
561
562 mMsgList.clear();
563
564 num = -1;
565 numStatus= 11;
566 off_t offs = 0;
567 size_t size = 0;
568 dateStr = "";
569 fromStr = "";
570 toStr = "";
571 subjStr = "";
572 *status = '\0';
573 *xstatus = '\0';
574 xmarkStr = "";
575 replyToIdStr = "";
576 replyToAuxIdStr = "";
577 referencesStr = "";
578 msgIdStr = "";
579 needStatus = 3;
580 size_t sizeServer = 0;
581 ulong uid = 0;
582
583
584 while (!atEof)
585 {
586 off_t pos = ftell(mStream);
587 if (!fgets(line, MAX_LINE, mStream)) atEof = true;
588
589 if (atEof ||
590 (memcmp(line, MSG_SEPERATOR_START, MSG_SEPERATOR_START_LEN)==0 &&
591 regexp.search(line) >= 0))
592 {
593 size = pos - offs;
594 pos = ftell(mStream);
595
596 if (num >= 0)
597 {
598 if (numStatus <= 0)
599 {
600 msgStr = i18n("Creating index file: one message done", "Creating index file: %n messages done", num);
601 emit statusMsg(msgStr);
602 numStatus = 10;
603 }
604
605 if (size > 0)
606 {
607 msgIdStr = msgIdStr.stripWhiteSpace();
608 if( !msgIdStr.isEmpty() ) {
609 int rightAngle;
610 rightAngle = msgIdStr.find( '>' );
611 if( rightAngle != -1 )
612 msgIdStr.truncate( rightAngle + 1 );
613 }
614
615 replyToIdStr = replyToIdStr.stripWhiteSpace();
616 if( !replyToIdStr.isEmpty() ) {
617 int rightAngle;
618 rightAngle = replyToIdStr.find( '>' );
619 if( rightAngle != -1 )
620 replyToIdStr.truncate( rightAngle + 1 );
621 }
622
623 referencesStr = referencesStr.stripWhiteSpace();
624 if( !referencesStr.isEmpty() ) {
625 int leftAngle, rightAngle;
626 leftAngle = referencesStr.findRev( '<' );
627 if( ( leftAngle != -1 )
628 && ( replyToIdStr.isEmpty() || ( replyToIdStr[0] != '<' ) ) ) {
629 // use the last reference, instead of missing In-Reply-To
630 replyToIdStr = referencesStr.mid( leftAngle );
631 }
632
633 // find second last reference
634 leftAngle = referencesStr.findRev( '<', leftAngle - 1 );
635 if( leftAngle != -1 )
636 referencesStr = referencesStr.mid( leftAngle );
637 rightAngle = referencesStr.findRev( '>' );
638 if( rightAngle != -1 )
639 referencesStr.truncate( rightAngle + 1 );
640
641 // Store the second to last reference in the replyToAuxIdStr
642 // It is a good candidate for threading the message below if the
643 // message In-Reply-To points to is not kept in this folder,
644 // but e.g. in an Outbox
645 replyToAuxIdStr = referencesStr;
646 rightAngle = referencesStr.find( '>' );
647 if( rightAngle != -1 )
648 replyToAuxIdStr.truncate( rightAngle + 1 );
649 }
650
651 contentTypeStr = contentTypeStr.stripWhiteSpace();
652 charset = "";
653 if ( !contentTypeStr.isEmpty() )
654 {
655 int cidx = contentTypeStr.find( "charset=" );
656 if ( cidx != -1 ) {
657 charset = contentTypeStr.mid( cidx + 8 );
658 if ( !charset.isEmpty() && ( charset[0] == '"' ) ) {
659 charset = charset.mid( 1 );
660 }
661 cidx = 0;
662 while ( (unsigned int) cidx < charset.length() ) {
663 if ( charset[cidx] == '"' || ( !isalnum(charset[cidx]) &&
664 charset[cidx] != '-' && charset[cidx] != '_' ) )
665 break;
666 ++cidx;
667 }
668 charset.truncate( cidx );
669 // kdDebug() << "KMFolderMaildir::readFileHeaderIntern() charset found: " <<
670 // charset << " from " << contentTypeStr << endl;
671 }
672 }
673
674 mi = new KMMsgInfo(folder());
675 mi->init( subjStr.stripWhiteSpace(),
676 fromStr.stripWhiteSpace(),
677 toStr.stripWhiteSpace(),
678 0, KMMsgStatusNew,
679 xmarkStr.stripWhiteSpace(),
680 replyToIdStr, replyToAuxIdStr, msgIdStr,
681 KMMsgEncryptionStateUnknown, KMMsgSignatureStateUnknown,
682 KMMsgMDNStateUnknown, charset, offs, size, sizeServer, uid );
683 mi->setStatus(status, xstatus);
684 mi->setDate( dateStr.stripWhiteSpace().data() );
685 mi->setDirty(false);
686 mMsgList.append(mi, mExportsSernums );
687
688 *status = '\0';
689 *xstatus = '\0';
690 needStatus = 3;
691 xmarkStr = "";
692 replyToIdStr = "";
693 replyToAuxIdStr = "";
694 referencesStr = "";
695 msgIdStr = "";
696 dateStr = "";
697 fromStr = "";
698 subjStr = "";
699 sizeServer = 0;
700 uid = 0;
701 }
702 else num--,numStatus++;
703 }
704
705 offs = ftell(mStream);
706 num++;
707 numStatus--;
708 inHeader = true;
709 continue;
710 }
711 // Is this a long header line?
712 if (inHeader && (line[0]=='\t' || line[0]==' '))
713 {
714 i = 0;
715 while (line [i]=='\t' || line [i]==' ') i++;
716 if (line [i] < ' ' && line [i]>0) inHeader = false;
717 else if (lastStr) *lastStr += line + i;
718 }
719 else lastStr = 0;
720
721 if (inHeader && (line [0]=='\n' || line [0]=='\r'))
722 inHeader = false;
723 if (!inHeader) continue;
724
725 /* -sanders Make all messages read when auto-recreating index */
726 /* Reverted, as it breaks reading the sent mail status, for example.
727 -till */
728 if ((needStatus & 1) && strncasecmp(line, "Status:", 7) == 0)
729 {
730 for(i=0; i<4 && line[i+8] > ' '; i++)
731 status[i] = line[i+8];
732 status[i] = '\0';
733 needStatus &= ~1;
734 }
735 else if ((needStatus & 2) && strncasecmp(line, "X-Status:", 9)==0)
736 {
737 for(i=0; i<4 && line[i+10] > ' '; i++)
738 xstatus[i] = line[i+10];
739 xstatus[i] = '\0';
740 needStatus &= ~2;
741 }
742 else if (strncasecmp(line,"X-KMail-Mark:",13)==0)
743 xmarkStr = TQCString(line+13);
744 else if (strncasecmp(line,"In-Reply-To:",12)==0) {
745 replyToIdStr = TQCString(line+12);
746 lastStr = &replyToIdStr;
747 }
748 else if (strncasecmp(line,"References:",11)==0) {
749 referencesStr = TQCString(line+11);
750 lastStr = &referencesStr;
751 }
752 else if (strncasecmp(line,"Message-Id:",11)==0) {
753 msgIdStr = TQCString(line+11);
754 lastStr = &msgIdStr;
755 }
756 else if (strncasecmp(line,"Date:",5)==0)
757 {
758 dateStr = TQCString(line+5);
759 lastStr = &dateStr;
760 }
761 else if (strncasecmp(line,"From:", 5)==0)
762 {
763 fromStr = TQCString(line+5);
764 lastStr = &fromStr;
765 }
766 else if (strncasecmp(line,"To:", 3)==0)
767 {
768 toStr = TQCString(line+3);
769 lastStr = &toStr;
770 }
771 else if (strncasecmp(line,"Subject:",8)==0)
772 {
773 subjStr = TQCString(line+8);
774 lastStr = &subjStr;
775 }
776 else if (strncasecmp(line,"X-Length:",9)==0)
777 {
778 sizeServerStr = TQCString(line+9);
779 sizeServer = sizeServerStr.toULong();
780 lastStr = &sizeServerStr;
781 }
782 else if (strncasecmp(line,"X-UID:",6)==0)
783 {
784 uidStr = TQCString(line+6);
785 uid = uidStr.toULong();
786 lastStr = &uidStr;
787 }
788 else if (strncasecmp(line, "Content-Type:", 13) == 0)
789 {
790 contentTypeStr = TQCString(line+13);
791 lastStr = &contentTypeStr;
792 }
793 }
794
795 if (mAutoCreateIndex)
796 {
797 emit statusMsg(i18n("Writing index file"));
798 writeIndex();
799 }
800 else mHeaderOffset = 0;
801
802 correctUnreadMsgsCount();
803
804 if (kmkernel->outboxFolder() == folder() && count() > 0)
805 KMessageBox::queuedMessageBox(0, KMessageBox::Information,
806 i18n("Your outbox contains messages which were "
807 "most-likely not created by KMail;\nplease remove them from there if you "
808 "do not want KMail to send them."));
809
810 invalidateFolder();
811 return 0;
812}
813
814
815//-----------------------------------------------------------------------------
816KMMessage* KMFolderMbox::readMsg(int idx)
817{
818 KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
819
820 assert(mi!=0 && !mi->isMessage());
821 assert(mStream != 0);
822
823 KMMessage *msg = new KMMessage(*mi);
824 msg->setMsgInfo( mi ); // remember the KMMsgInfo object to that we can restore it when the KMMessage object is no longer needed
825 mMsgList.set(idx,&msg->toMsgBase()); // done now so that the serial number can be computed
826 msg->fromDwString(getDwString(idx));
827 return msg;
828}
829
830
831#define STRDIM(x) (sizeof(x)/sizeof(*x)-1)
832// performs (\n|^)>{n}From_ -> \1>{n-1}From_ conversion
833static size_t unescapeFrom( char* str, size_t strLen ) {
834 if ( !str )
835 return 0;
836 if ( strLen <= STRDIM(">From ") )
837 return strLen;
838
839 // yes, *d++ = *s++ is a no-op as long as d == s (until after the
840 // first >From_), but writes are cheap compared to reads and the
841 // data is already in the cache from the read, so special-casing
842 // might even be slower...
843 const char * s = str;
844 char * d = str;
845 const char * const e = str + strLen - STRDIM(">From ");
846
847 while ( s < e ) {
848 if ( *s == '\n' && *(s+1) == '>' ) { // we can do the lookahead, since e is 6 chars from the end!
849 *d++ = *s++; // == '\n'
850 *d++ = *s++; // == '>'
851 while ( s < e && *s == '>' )
852 *d++ = *s++;
853 if ( tqstrncmp( s, "From ", STRDIM("From ") ) == 0 )
854 --d;
855 }
856 *d++ = *s++; // yes, s might be e here, but e is not the end :-)
857 }
858 // copy the rest:
859 while ( s < str + strLen )
860 *d++ = *s++;
861 if ( d < s ) // only NUL-terminate if it's shorter
862 *d = 0;
863
864 return d - str;
865}
866
867//static
868TQByteArray KMFolderMbox::escapeFrom( const DwString & str ) {
869 const unsigned int strLen = str.length();
870 if ( strLen <= STRDIM("From ") )
871 return KMail::Util::ByteArray( str );
872 // worst case: \nFrom_\nFrom_\nFrom_... => grows to 7/6
873 TQByteArray result( int( strLen + 5 ) / 6 * 7 + 1 );
874
875 const char * s = str.data();
876 const char * const e = s + strLen - STRDIM("From ");
877 char * d = result.data();
878
879 bool onlyAnglesAfterLF = false; // dont' match ^From_
880 while ( s < e ) {
881 switch ( *s ) {
882 case '\n':
883 onlyAnglesAfterLF = true;
884 break;
885 case '>':
886 break;
887 case 'F':
888 if ( onlyAnglesAfterLF && tqstrncmp( s+1, "rom ", STRDIM("rom ") ) == 0 )
889 *d++ = '>';
890 // fall through
891 default:
892 onlyAnglesAfterLF = false;
893 break;
894 }
895 *d++ = *s++;
896 }
897 while ( s < str.data() + strLen )
898 *d++ = *s++;
899
900 result.truncate( d - result.data() );
901 return result;
902}
903
904#undef STRDIM
905
906//-----------------------------------------------------------------------------
907DwString KMFolderMbox::getDwString(int idx)
908{
909 KMMsgInfo* mi = (KMMsgInfo*)mMsgList[idx];
910
911 assert(mi!=0);
912 assert(mStream != 0);
913
914 size_t msgSize = mi->msgSize();
915 char* msgText = new char[ msgSize + 1 ];
916
917 fseek(mStream, mi->folderOffset(), SEEK_SET);
918 fread(msgText, msgSize, 1, mStream);
919 msgText[msgSize] = '\0';
920
921 size_t newMsgSize = unescapeFrom( msgText, msgSize );
922 newMsgSize = KMail::Util::crlf2lf( msgText, newMsgSize );
923
924 DwString msgStr;
925 // the DwString takes possession of msgText, so we must not delete msgText
926 msgStr.TakeBuffer( msgText, msgSize + 1, 0, newMsgSize );
927 return msgStr;
928}
929
930
931//-----------------------------------------------------------------------------
932int KMFolderMbox::addMsg( KMMessage* aMsg, int* aIndex_ret )
933{
934 if (!canAddMsgNow(aMsg, aIndex_ret)) return 0;
935 TQByteArray msgText;
936 char endStr[3];
937 int idx = -1, rc;
938 KMFolder* msgParent;
939 bool editing = false;
940 int growth = 0;
941
942 KMFolderOpener openThis(folder(), "mboxaddMsg");
943 rc = openThis.openResult();
944 if (rc)
945 {
946 kdDebug(5006) << "KMFolderMbox::addMsg-open: " << rc << " of folder: " << label() << endl;
947 return rc;
948 }
949
950 // take message out of the folder it is currently in, if any
951 msgParent = aMsg->parent();
952 if (msgParent)
953 {
954 if ( msgParent== folder() )
955 {
956 if (kmkernel->folderIsDraftOrOutbox( folder() ))
957 //special case for Edit message.
958 {
959 kdDebug(5006) << "Editing message in outbox or drafts" << endl;
960 editing = true;
961 }
962 else
963 return 0;
964 }
965
966 idx = msgParent->find(aMsg);
967 msgParent->getMsg( idx );
968 }
969
970 if (folderType() != KMFolderTypeImap)
971 {
972/*
973TQFile fileD0( "testdat_xx-kmfoldermbox-0" );
974if( fileD0.open( IO_WriteOnly ) ) {
975 TQDataStream ds( &fileD0 );
976 ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
977 fileD0.close(); // If data is 0 we just create a zero length file.
978}
979*/
980 aMsg->setStatusFields();
981/*
982TQFile fileD1( "testdat_xx-kmfoldermbox-1" );
983if( fileD1.open( IO_WriteOnly ) ) {
984 TQDataStream ds( &fileD1 );
985 ds.writeRawBytes( aMsg->asString(), aMsg->asString().length() );
986 fileD1.close(); // If data is 0 we just create a zero length file.
987}
988*/
989 if (aMsg->headerField("Content-Type").isEmpty()) // This might be added by
990 aMsg->removeHeaderField("Content-Type"); // the line above
991 }
992 msgText = escapeFrom( aMsg->asDwString() );
993 size_t len = msgText.size();
994
995 assert(mStream != 0);
996 clearerr(mStream);
997 if (len <= 0)
998 {
999 kdDebug(5006) << "Message added to folder `" << name() << "' contains no data. Ignoring it." << endl;
1000 return 0;
1001 }
1002
1003 // Make sure the file is large enough to check for an end
1004 // character
1005 fseek(mStream, 0, SEEK_END);
1006 off_t revert = ftell(mStream);
1007 if (ftell(mStream) >= 2) {
1008 // write message to folder file
1009 fseek(mStream, -2, SEEK_END);
1010 fread(endStr, 1, 2, mStream); // ensure separating empty line
1011 if (ftell(mStream) > 0 && endStr[0]!='\n') {
1012 ++growth;
1013 if (endStr[1]!='\n') {
1014 //printf ("****endStr[1]=%c\n", endStr[1]);
1015 fwrite("\n\n", 1, 2, mStream);
1016 ++growth;
1017 }
1018 else fwrite("\n", 1, 1, mStream);
1019 }
1020 }
1021 fseek(mStream,0,SEEK_END); // this is needed on solaris and others
1022 int error = ferror(mStream);
1023 if (error)
1024 return error;
1025
1026 TQCString messageSeparator( aMsg->mboxMessageSeparator() );
1027 fwrite( messageSeparator.data(), messageSeparator.length(), 1, mStream );
1028 off_t offs = ftell(mStream);
1029 fwrite(msgText.data(), len, 1, mStream);
1030 if (msgText[(int)len-1]!='\n') fwrite("\n\n", 1, 2, mStream);
1031 fflush(mStream);
1032 size_t size = ftell(mStream) - offs;
1033
1034 error = ferror(mStream);
1035 if (error) {
1036 kdDebug(5006) << "Error: Could not add message to folder: " << strerror(errno) << endl;
1037 if (ftell(mStream) > revert) {
1038 kdDebug(5006) << "Undoing changes" << endl;
1039 truncate( TQFile::encodeName(location()), revert );
1040 }
1041 kmkernel->emergencyExit( i18n("Could not add message to folder: ") + TQString::fromLocal8Bit(strerror(errno)));
1042
1043 /* This code is not 100% reliable
1044 bool busy = kmkernel->kbp()->isBusy();
1045 if (busy) kmkernel->kbp()->idle();
1046 KMessageBox::sorry(0,
1047 i18n("Unable to add message to folder.\n"
1048 "(No space left on device or insufficient quota?)\n"
1049 "Free space and sufficient quota are required to continue safely."));
1050 if (busy) kmkernel->kbp()->busy();
1051 kmkernel->kbp()->idle();
1052 */
1053 return error;
1054 }
1055
1056 if (msgParent) {
1057 if (idx >= 0) msgParent->take(idx);
1058 }
1059// if (mAccount) aMsg->removeHeaderField("X-UID");
1060
1061 if (aMsg->isUnread() || aMsg->isNew() ||
1062 (folder() == kmkernel->outboxFolder())) {
1063 if (mUnreadMsgs == -1) mUnreadMsgs = 1;
1064 else ++mUnreadMsgs;
1065 if ( !mQuiet )
1066 emit numUnreadMsgsChanged( folder() );
1067 }
1068 ++mTotalMsgs;
1069 mSize = -1;
1070
1071 if ( aMsg->attachmentState() == KMMsgAttachmentUnknown && aMsg->readyToShow() ) {
1072 aMsg->updateAttachmentState();
1073 }
1074 if ( aMsg->invitationState() == KMMsgInvitationUnknown && aMsg->readyToShow() ) {
1075 aMsg->updateInvitationState();
1076 }
1077
1078 // store information about the position in the folder file in the message
1079 aMsg->setParent(folder());
1080 aMsg->setFolderOffset(offs);
1081 aMsg->setMsgSize(size);
1082 idx = mMsgList.append(&aMsg->toMsgBase(), mExportsSernums );
1083 if ( aMsg->getMsgSerNum() <= 0 )
1084 aMsg->setMsgSerNum();
1085 else
1086 replaceMsgSerNum( aMsg->getMsgSerNum(), &aMsg->toMsgBase(), idx );
1087
1088 // change the length of the previous message to encompass white space added
1089 if ((idx > 0) && (growth > 0)) {
1090 // don't grow if a deleted message claims space at the end of the file
1091 if ((ulong)revert == mMsgList[idx - 1]->folderOffset() + mMsgList[idx - 1]->msgSize() )
1092 mMsgList[idx - 1]->setMsgSize( mMsgList[idx - 1]->msgSize() + growth );
1093 }
1094
1095 // write index entry if desired
1096 if (mAutoCreateIndex)
1097 {
1098 assert(mIndexStream != 0);
1099 clearerr(mIndexStream);
1100 fseek(mIndexStream, 0, SEEK_END);
1101 revert = ftell(mIndexStream);
1102
1103 KMMsgBase * mb = &aMsg->toMsgBase();
1104 int len;
1105 const uchar *buffer = mb->asIndexString(len);
1106 fwrite(&len,sizeof(len), 1, mIndexStream);
1107 mb->setIndexOffset( ftell(mIndexStream) );
1108 mb->setIndexLength( len );
1109 if(fwrite(buffer, len, 1, mIndexStream) != 1)
1110 kdDebug(5006) << "Whoa! " << __FILE__ << ":" << __LINE__ << endl;
1111
1112 fflush(mIndexStream);
1113 error = ferror(mIndexStream);
1114
1115 if ( mExportsSernums )
1116 error |= appendToFolderIdsFile( idx );
1117
1118 if (error) {
1119 kdWarning(5006) << "Error: Could not add message to folder (No space left on device?)" << endl;
1120 if (ftell(mIndexStream) > revert) {
1121 kdWarning(5006) << "Undoing changes" << endl;
1122 truncate( TQFile::encodeName(indexLocation()), revert );
1123 }
1124 if ( errno )
1125 kmkernel->emergencyExit( i18n("Could not add message to folder:") + TQString::fromLocal8Bit(strerror(errno)));
1126 else
1127 kmkernel->emergencyExit( i18n("Could not add message to folder (No space left on device?)") );
1128
1129 /* This code may not be 100% reliable
1130 bool busy = kmkernel->kbp()->isBusy();
1131 if (busy) kmkernel->kbp()->idle();
1132 KMessageBox::sorry(0,
1133 i18n("Unable to add message to folder.\n"
1134 "(No space left on device or insufficient quota?)\n"
1135 "Free space and sufficient quota are required to continue safely."));
1136 if (busy) kmkernel->kbp()->busy();
1137 */
1138 return error;
1139 }
1140 }
1141
1142 if (aIndex_ret) *aIndex_ret = idx;
1143 emitMsgAddedSignals(idx);
1144
1145 // All streams have been flushed without errors if we arrive here
1146 // Return success!
1147 // (Don't return status of stream, it may have been closed already.)
1148 return 0;
1149}
1150
1151int KMFolderMbox::compact( unsigned int startIndex, int nbMessages, FILE* tmpfile, off_t& offs, bool& done )
1152{
1153 int rc = 0;
1154 TQCString mtext;
1155 unsigned int stopIndex = nbMessages == -1 ? mMsgList.count() :
1156 TQMIN( mMsgList.count(), startIndex + nbMessages );
1157 //kdDebug(5006) << "KMFolderMbox: compacting from " << startIndex << " to " << stopIndex << endl;
1158 for(unsigned int idx = startIndex; idx < stopIndex; ++idx) {
1159 KMMsgInfo* mi = (KMMsgInfo*)mMsgList.at(idx);
1160 size_t msize = mi->msgSize();
1161 if (mtext.size() < msize + 2)
1162 mtext.resize(msize+2);
1163 off_t folder_offset = mi->folderOffset();
1164
1165 //now we need to find the separator! grr...
1166 for(off_t i = folder_offset-25; true; i -= 20) {
1167 off_t chunk_offset = i <= 0 ? 0 : i;
1168 if(fseek(mStream, chunk_offset, SEEK_SET) == -1) {
1169 rc = errno;
1170 break;
1171 }
1172 if (mtext.size() < 20)
1173 mtext.resize(20);
1174 fread(mtext.data(), 20, 1, mStream);
1175 if(i <= 0) { //woops we've reached the top of the file, last try..
1176 if ( mtext.contains( "from ", false ) ) {
1177 if (mtext.size() < (size_t)folder_offset)
1178 mtext.resize(folder_offset);
1179 if(fseek(mStream, chunk_offset, SEEK_SET) == -1 ||
1180 !fread(mtext.data(), folder_offset, 1, mStream) ||
1181 !fwrite(mtext.data(), folder_offset, 1, tmpfile)) {
1182 rc = errno;
1183 break;
1184 }
1185 offs += folder_offset;
1186 } else {
1187 rc = 666;
1188 }
1189 break;
1190 } else {
1191 int last_crlf = -1;
1192 for(int i2 = 0; i2 < 20; i2++) {
1193 if(*(mtext.data()+i2) == '\n')
1194 last_crlf = i2;
1195 }
1196 if(last_crlf != -1) {
1197 int size = folder_offset - (i + last_crlf+1);
1198 if ((int)mtext.size() < size)
1199 mtext.resize(size);
1200 if(fseek(mStream, i + last_crlf+1, SEEK_SET) == -1 ||
1201 !fread(mtext.data(), size, 1, mStream) ||
1202 !fwrite(mtext.data(), size, 1, tmpfile)) {
1203 rc = errno;
1204 break;
1205 }
1206 offs += size;
1207 break;
1208 }
1209 }
1210 }
1211 if (rc)
1212 break;
1213
1214 //now actually write the message
1215 if(fseek(mStream, folder_offset, SEEK_SET) == -1 ||
1216 !fread(mtext.data(), msize, 1, mStream) || !fwrite(mtext.data(), msize, 1, tmpfile)) {
1217 rc = errno;
1218 break;
1219 }
1220 mi->setFolderOffset(offs);
1221 offs += msize;
1222 }
1223 done = ( !rc && stopIndex == mMsgList.count() ); // finished without errors
1224 return rc;
1225}
1226
1227//-----------------------------------------------------------------------------
1228int KMFolderMbox::compact( bool silent )
1229{
1230 // This is called only when the user explicitely requests compaction,
1231 // so we don't check needsCompact.
1232
1233 KMail::MboxCompactionJob* job = new KMail::MboxCompactionJob( folder(), true /*immediate*/ );
1234 int rc = job->executeNow( silent );
1235 // Note that job autodeletes itself.
1236
1237 // If this is the current folder, the changed signal will ultimately call
1238 // KMHeaders::setFolderInfoStatus which will override the message, so save/restore it
1239 TQString statusMsg = BroadcastStatus::instance()->statusMsg();
1240 emit changed();
1241 BroadcastStatus::instance()->setStatusMsg( statusMsg );
1242 return rc;
1243}
1244
1245
1246//-----------------------------------------------------------------------------
1247void KMFolderMbox::setLockType( LockType ltype )
1248{
1249 mLockType = ltype;
1250}
1251
1252//-----------------------------------------------------------------------------
1253void KMFolderMbox::setProcmailLockFileName( const TQString &fname )
1254{
1255 mProcmailLockFileName = fname;
1256}
1257
1258//-----------------------------------------------------------------------------
1259int KMFolderMbox::removeContents()
1260{
1261 int rc = 0;
1262 rc = unlink(TQFile::encodeName(location()));
1263 return rc;
1264}
1265
1266//-----------------------------------------------------------------------------
1267int KMFolderMbox::expungeContents()
1268{
1269 int rc = 0;
1270 if (truncate(TQFile::encodeName(location()), 0))
1271 rc = errno;
1272 return rc;
1273}
1274
1275//-----------------------------------------------------------------------------
1276/*virtual*/
1277TQ_INT64 KMFolderMbox::doFolderSize() const
1278{
1279 TQFileInfo info( location() );
1280 return (TQ_INT64)(info.size());
1281}
1282
1283//-----------------------------------------------------------------------------
1284#include "kmfoldermbox.moc"
virtual int addMsg(TQPtrList< KMMessage > &, TQValueList< int > &index_return)
Adds the given messages to the folder.
sets a cursor and makes sure it's restored on destruction Create a KCursorSaver object when you want ...
Definition: kcursorsaver.h:14
A FolderStorage with an index for faster access to often used message properties.
Definition: kmfolderindex.h:38
IndexStatus
This enum indicates the status of the index file.
Definition: kmfolderindex.h:50
RAII for KMFolder::open() / close().
Definition: kmfolder.h:688
Mail folder.
Definition: kmfolder.h:69
KMMessage * take(int idx)
Detach message from this folder.
Definition: kmfolder.cpp:380
KMMessage * getMsg(int idx)
Read message at given index.
Definition: kmfolder.cpp:321
int find(const KMMsgBase *msg) const
Returns the index of the given message or -1 if not found.
Definition: kmfolder.cpp:435
This is a Mime Message.
Definition: kmmessage.h:68
bool readyToShow() const
Return if the message is ready to be shown.
Definition: kmmessage.h:872
void setStatusFields()
Set "Status" and "X-Status" fields of the message from the internal message status.
Definition: kmmessage.cpp:351
void removeHeaderField(const TQCString &name)
Remove header field with given name.
Definition: kmmessage.cpp:2317
void fromDwString(const DwString &str, bool setStatus=false)
Parse the string and create this message from it.
Definition: kmmessage.cpp:402
KMMsgBase & toMsgBase()
Get KMMsgBase for this object.
Definition: kmmessage.h:114
const DwString & asDwString() const
Return the entire message contents in the DwString.
Definition: kmmessage.cpp:292
void setMsgSerNum(unsigned long newMsgSerNum=0)
Sets the message serial number.
Definition: kmmessage.cpp:223
TQString headerField(const TQCString &name) const
Returns the value of a header field with the given name.
Definition: kmmessage.cpp:2289
TQCString mboxMessageSeparator()
Returns an mbox message separator line for this message, i.e.
Definition: kmmessage.cpp:4481
void setMsgInfo(KMMsgInfo *msgInfo)
Set the KMMsgInfo object corresponding to this message.
Definition: kmmessage.h:932
A job that runs in the background and compacts mbox folders.
Definition: compactionjob.h:40
TQByteArray ByteArray(const DwString &str)
Construct a TQByteArray from a DwString.
Definition: util.cpp:122
size_t crlf2lf(char *str, const size_t strLen)
Convert all sequences of "\r\n" (carriage return followed by a line feed) to a single "\n" (line feed...
Definition: util.cpp:44