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