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"
56 using 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 //-----------------------------------------------------------------------------
73 KMFolderMbox::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 //-----------------------------------------------------------------------------
84 KMFolderMbox::~KMFolderMbox()
85 {
86  if (mOpenCount>0)
87  close("~kmfoldermbox", true);
88  if (kmkernel->undoStack())
89  kmkernel->undoStack()->folderDestroyed( folder() );
90 }
91 
92 //-----------------------------------------------------------------------------
93 int 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 //----------------------------------------------------------------------------
197 int 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 //-----------------------------------------------------------------------------
209 int 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 //-----------------------------------------------------------------------------
258 void 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 //-----------------------------------------------------------------------------
293 void 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 //-----------------------------------------------------------------------------
303 int 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 //-------------------------------------------------------------
435 FolderJob*
436 KMFolderMbox::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 //-------------------------------------------------------------
445 FolderJob*
446 KMFolderMbox::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 //-----------------------------------------------------------------------------
455 int 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 //-----------------------------------------------------------------------------
522 KMFolderIndex::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 //-----------------------------------------------------------------------------
543 int 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 //-----------------------------------------------------------------------------
816 KMMessage* 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
833 static 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
868 TQByteArray 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 //-----------------------------------------------------------------------------
907 DwString 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 //-----------------------------------------------------------------------------
932 int 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 /*
973 TQFile fileD0( "testdat_xx-kmfoldermbox-0" );
974 if( 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 /*
982 TQFile fileD1( "testdat_xx-kmfoldermbox-1" );
983 if( 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 
1151 int 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 //-----------------------------------------------------------------------------
1228 int 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 //-----------------------------------------------------------------------------
1247 void KMFolderMbox::setLockType( LockType ltype )
1248 {
1249  mLockType = ltype;
1250 }
1251 
1252 //-----------------------------------------------------------------------------
1253 void KMFolderMbox::setProcmailLockFileName( const TQString &fname )
1254 {
1255  mProcmailLockFileName = fname;
1256 }
1257 
1258 //-----------------------------------------------------------------------------
1259 int KMFolderMbox::removeContents()
1260 {
1261  int rc = 0;
1262  rc = unlink(TQFile::encodeName(location()));
1263  return rc;
1264 }
1265 
1266 //-----------------------------------------------------------------------------
1267 int 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*/
1277 TQ_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
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
KMMsgBase & toMsgBase()
Get KMMsgBase for this object.
Definition: kmmessage.h:114
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