kmail

kmmessage.cpp
1 // -*- mode: C++; c-file-style: "gnu" -*-
2 // kmmessage.cpp
3 
4 // if you do not want GUI elements in here then set ALLOW_GUI to 0.
5 #include <config.h>
6 // needed temporarily until KMime is replacing the partNode helper class:
7 #include "partNode.h"
8 
9 
10 #define ALLOW_GUI 1
11 #include "kmkernel.h"
12 #include "kmmessage.h"
13 #include "mailinglist-magic.h"
14 #include "messageproperty.h"
15 using KMail::MessageProperty;
16 #include "objecttreeparser.h"
17 using KMail::ObjectTreeParser;
18 #include "kmfolderindex.h"
19 #include "undostack.h"
20 #include "kmversion.h"
21 #include "headerstrategy.h"
22 #include "globalsettings.h"
23 using KMail::HeaderStrategy;
24 #include "kmaddrbook.h"
25 #include "kcursorsaver.h"
26 #include "templateparser.h"
27 
28 #include <libkpimidentities/identity.h>
29 #include <libkpimidentities/identitymanager.h>
30 #include <libemailfunctions/email.h>
31 
32 #include <kasciistringtools.h>
33 
34 #include <kpgpblock.h>
35 #include <kaddrbook.h>
36 
37 #include <kapplication.h>
38 #include <kglobalsettings.h>
39 #include <kdebug.h>
40 #include <kconfig.h>
41 #include <khtml_part.h>
42 #include <kuser.h>
43 #include <kidna.h>
44 #include <kasciistricmp.h>
45 
46 #include <tqcursor.h>
47 #include <tqtextcodec.h>
48 #include <tqmessagebox.h>
49 #include <kmime_util.h>
50 #include <kmime_charfreq.h>
51 
52 #include <kmime_header_parsing.h>
53 using KMime::HeaderParsing::parseAddressList;
54 using namespace KMime::Types;
55 
56 #include <mimelib/body.h>
57 #include <mimelib/field.h>
58 #include <mimelib/mimepp.h>
59 #include <mimelib/string.h>
60 #include <assert.h>
61 #include <sys/time.h>
62 #include <time.h>
63 #include <klocale.h>
64 #include <stdlib.h>
65 #include <unistd.h>
66 #include "util.h"
67 
68 #if ALLOW_GUI
69 #include <kmessagebox.h>
70 #endif
71 
72 using namespace KMime;
73 
74 static DwString emptyString("");
75 
76 // Values that are set from the config file with KMMessage::readConfig()
77 static TQString sReplyLanguage, sReplyStr, sReplyAllStr, sIndentPrefixStr;
78 static bool sSmartQuote,
79  sWordWrap;
80 static int sWrapCol;
81 static TQStringList sPrefCharsets;
82 
83 TQString KMMessage::sForwardStr;
84 const HeaderStrategy * KMMessage::sHeaderStrategy = HeaderStrategy::rich();
85 //helper
86 static void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart );
87 
88 TQValueList<KMMessage*> KMMessage::sPendingDeletes;
89 
90 //-----------------------------------------------------------------------------
91 KMMessage::KMMessage(DwMessage* aMsg)
92  : KMMsgBase()
93 {
94  init( aMsg );
95  // aMsg might need assembly
96  mNeedsAssembly = true;
97 }
98 
99 //-----------------------------------------------------------------------------
100 KMMessage::KMMessage(KMFolder* parent): KMMsgBase(parent)
101 {
102  init();
103 }
104 
105 
106 //-----------------------------------------------------------------------------
107 KMMessage::KMMessage(KMMsgInfo& msgInfo): KMMsgBase()
108 {
109  init();
110  // now overwrite a few from the msgInfo
111  mMsgSize = msgInfo.msgSize();
112  mFolderOffset = msgInfo.folderOffset();
113  mStatus = msgInfo.status();
114  mEncryptionState = msgInfo.encryptionState();
115  mSignatureState = msgInfo.signatureState();
116  mMDNSentState = msgInfo.mdnSentState();
117  mDate = msgInfo.date();
118  mFileName = msgInfo.fileName();
119  KMMsgBase::assign(&msgInfo);
120 }
121 
122 
123 //-----------------------------------------------------------------------------
125  KMMsgBase( other ),
126  ISubject(),
127  mMsg(0)
128 {
129  init(); // to be safe
130  assign( other );
131 }
132 
133 void KMMessage::init( DwMessage* aMsg )
134 {
135  mNeedsAssembly = false;
136  if ( aMsg ) {
137  mMsg = aMsg;
138  } else {
139  mMsg = new DwMessage;
140  }
141  mOverrideCodec = 0;
142  mDecodeHTML = false;
143  mComplete = true;
144  mReadyToShow = true;
145  mMsgSize = 0;
146  mMsgLength = 0;
147  mFolderOffset = 0;
148  mStatus = KMMsgStatusNew;
149  mEncryptionState = KMMsgEncryptionStateUnknown;
150  mSignatureState = KMMsgSignatureStateUnknown;
151  mMDNSentState = KMMsgMDNStateUnknown;
152  mDate = 0;
153  mUnencryptedMsg = 0;
154  mLastUpdated = 0;
155  mCursorPos = 0;
156  mMsgInfo = 0;
157  mIsParsed = false;
158 }
159 
160 void KMMessage::assign( const KMMessage& other )
161 {
162  MessageProperty::forget( this );
163  delete mMsg;
164  delete mUnencryptedMsg;
165 
166  mNeedsAssembly = true;//other.mNeedsAssembly;
167  if( other.mMsg )
168  mMsg = new DwMessage( *(other.mMsg) );
169  else
170  mMsg = 0;
171  mOverrideCodec = other.mOverrideCodec;
172  mDecodeHTML = other.mDecodeHTML;
173  mMsgSize = other.mMsgSize;
174  mMsgLength = other.mMsgLength;
175  mFolderOffset = other.mFolderOffset;
176  mStatus = other.mStatus;
177  mEncryptionState = other.mEncryptionState;
178  mSignatureState = other.mSignatureState;
179  mMDNSentState = other.mMDNSentState;
180  mIsParsed = other.mIsParsed;
181  mDate = other.mDate;
182  if( other.hasUnencryptedMsg() )
183  mUnencryptedMsg = new KMMessage( *other.unencryptedMsg() );
184  else
185  mUnencryptedMsg = 0;
186  setDrafts( other.drafts() );
187  setTemplates( other.templates() );
188  //mFileName = ""; // we might not want to copy the other messages filename (?)
189  //KMMsgBase::assign( &other );
190 }
191 
192 //-----------------------------------------------------------------------------
194 {
195  delete mMsgInfo;
196  delete mMsg;
197  kmkernel->undoStack()->msgDestroyed( this );
198 }
199 
200 
201 //-----------------------------------------------------------------------------
202 void KMMessage::setReferences(const TQCString& aStr)
203 {
204  if (aStr.isNull()) return;
205  mMsg->Headers().References().FromString(aStr);
206  mNeedsAssembly = true;
207 }
208 
209 
210 //-----------------------------------------------------------------------------
211 TQCString KMMessage::id() const
212 {
213  DwHeaders& header = mMsg->Headers();
214  if (header.HasMessageId())
215  return KMail::Util::CString( header.MessageId().AsString() );
216  else
217  return "";
218 }
219 
220 
221 //-----------------------------------------------------------------------------
222 //WARNING: This method updates the memory resident cache of serial numbers
223 //WARNING: held in MessageProperty, but it does not update the persistent
224 //WARNING: store of serial numbers on the file system that is managed by
225 //WARNING: KMMsgDict
226 void KMMessage::setMsgSerNum(unsigned long newMsgSerNum)
227 {
228  MessageProperty::setSerialCache( this, newMsgSerNum );
229 }
230 
231 
232 //-----------------------------------------------------------------------------
234 {
235  return true;
236 }
237 
238 //-----------------------------------------------------------------------------
240 {
241  return MessageProperty::transferInProgress( getMsgSerNum() );
242 }
243 
244 
245 //-----------------------------------------------------------------------------
246 void KMMessage::setTransferInProgress(bool value, bool force)
247 {
248  MessageProperty::setTransferInProgress( getMsgSerNum(), value, force );
249  if ( !transferInProgress() && sPendingDeletes.contains( this ) ) {
250  sPendingDeletes.remove( this );
251  if ( parent() ) {
252  int idx = parent()->find( this );
253  if ( idx > 0 ) {
254  parent()->removeMsg( idx );
255  }
256  }
257  }
258 }
259 
260 
261 
262 bool KMMessage::isUrgent() const {
263  return headerField( "Priority" ).contains( "urgent", false )
264  || headerField( "X-Priority" ).startsWith( "2" );
265 }
266 
267 //-----------------------------------------------------------------------------
269 {
270  delete mUnencryptedMsg;
271  mUnencryptedMsg = unencrypted;
272 }
273 
274 //-----------------------------------------------------------------------------
275 //FIXME: move to libemailfunctions
276 KPIM::EmailParseResult KMMessage::isValidEmailAddressList( const TQString& aStr,
277  TQString& brokenAddress )
278 {
279  if ( aStr.isEmpty() ) {
280  return KPIM::AddressEmpty;
281  }
282 
283  TQStringList list = KPIM::splitEmailAddrList( aStr );
284  for( TQStringList::const_iterator it = list.begin(); it != list.end(); ++it ) {
285  KPIM::EmailParseResult errorCode = KPIM::isValidEmailAddress( *it );
286  if ( errorCode != KPIM::AddressOk ) {
287  brokenAddress = ( *it );
288  return errorCode;
289  }
290  }
291  return KPIM::AddressOk;
292 }
293 
294 //-----------------------------------------------------------------------------
295 const DwString& KMMessage::asDwString() const
296 {
297  if (mNeedsAssembly)
298  {
299  mNeedsAssembly = false;
300  mMsg->Assemble();
301  }
302  return mMsg->AsString();
303 }
304 
305 //-----------------------------------------------------------------------------
306 const DwMessage* KMMessage::asDwMessage()
307 {
308  if (mNeedsAssembly)
309  {
310  mNeedsAssembly = false;
311  mMsg->Assemble();
312  }
313  return mMsg;
314 }
315 
316 //-----------------------------------------------------------------------------
317 TQCString KMMessage::asString() const {
318  return KMail::Util::CString( asDwString() );
319 }
320 
321 
322 TQByteArray KMMessage::asSendableString() const
323 {
324  KMMessage msg( new DwMessage( *this->mMsg ) );
326  msg.removeHeaderField("Bcc");
327  return KMail::Util::ByteArray( msg.asDwString() ); // and another copy again!
328 }
329 
331 {
332  KMMessage msg( new DwMessage( *this->mMsg ) );
334  msg.removeHeaderField("Bcc");
335  return msg.headerAsString().latin1();
336 }
337 
339  removeHeaderField("Status");
340  removeHeaderField("X-Status");
341  removeHeaderField("X-KMail-EncryptionState");
342  removeHeaderField("X-KMail-SignatureState");
343  removeHeaderField("X-KMail-MDN-Sent");
344  removeHeaderField("X-KMail-Transport");
345  removeHeaderField("X-KMail-Identity");
346  removeHeaderField("X-KMail-Fcc");
347  removeHeaderField("X-KMail-Redirect-From");
348  removeHeaderField("X-KMail-Link-Message");
349  removeHeaderField("X-KMail-Link-Type");
350  removeHeaderField( "X-KMail-Markup" );
351 }
352 
353 //-----------------------------------------------------------------------------
355 {
356  char str[2] = { 0, 0 };
357 
358  setHeaderField("Status", status() & KMMsgStatusNew ? "R" : "RO");
359  setHeaderField("X-Status", statusToStr(status()));
360 
361  str[0] = (char)encryptionState();
362  setHeaderField("X-KMail-EncryptionState", str);
363 
364  str[0] = (char)signatureState();
365  //kdDebug(5006) << "Setting SignatureState header field to " << str[0] << endl;
366  setHeaderField("X-KMail-SignatureState", str);
367 
368  str[0] = static_cast<char>( mdnSentState() );
369  setHeaderField("X-KMail-MDN-Sent", str);
370 
371  // We better do the assembling ourselves now to prevent the
372  // mimelib from changing the message *body*. (khz, 10.8.2002)
373  mNeedsAssembly = false;
374  mMsg->Headers().Assemble();
375  mMsg->Assemble( mMsg->Headers(),
376  mMsg->Body() );
377 }
378 
379 
380 //----------------------------------------------------------------------------
382 {
383  DwHeaders& header = mMsg->Headers();
384  header.Assemble();
385  if ( header.AsString().empty() )
386  return TQString();
387  return TQString::fromLatin1( header.AsString().c_str() );
388 }
389 
390 
391 //-----------------------------------------------------------------------------
393 {
394  return mMsg->Headers().ContentType();
395 }
396 
397 void KMMessage::fromByteArray( const TQByteArray & ba, bool setStatus ) {
398  return fromDwString( DwString( ba.data(), ba.size() ), setStatus );
399 }
400 
401 void KMMessage::fromString( const TQCString & str, bool aSeStatus ) {
402  return fromDwString( KMail::Util::dwString( str ), aSeStatus );
403 }
404 
405 void KMMessage::fromDwString(const DwString& str, bool aSeStatus)
406 {
407  delete mMsg;
408  mMsg = new DwMessage;
409  mMsg->FromString( str );
410  mMsg->Parse();
411 
412  if (aSeStatus) {
413  setStatus(headerField("Status").latin1(), headerField("X-Status").latin1());
414  setEncryptionStateChar( headerField("X-KMail-EncryptionState").at(0) );
415  setSignatureStateChar( headerField("X-KMail-SignatureState").at(0) );
416  setMDNSentState( static_cast<KMMsgMDNSentState>( headerField("X-KMail-MDN-Sent").at(0).latin1() ) );
417  }
418  if ( invitationState() == KMMsgInvitationUnknown && readyToShow() )
419  updateInvitationState();
420  if ( attachmentState() == KMMsgAttachmentUnknown && readyToShow() )
421  updateAttachmentState();
422 
423  mNeedsAssembly = false;
424  mDate = date();
425 }
426 
427 
428 //-----------------------------------------------------------------------------
429 TQString KMMessage::formatString(const TQString& aStr) const
430 {
431  TQString result, str;
432  TQChar ch;
433  uint j;
434 
435  if (aStr.isEmpty())
436  return aStr;
437 
438  unsigned int strLength(aStr.length());
439  for (uint i=0; i<strLength;) {
440  ch = aStr[i++];
441  if (ch == '%') {
442  ch = aStr[i++];
443  switch ((char)ch) {
444  case 'D':
445  /* I'm not too sure about this change. Is it not possible
446  to have a long form of the date used? I don't
447  like this change to a short XX/XX/YY date format.
448  At least not for the default. -sanders */
449  result += KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized,
450  date(), sReplyLanguage, false );
451  break;
452  case 'e':
453  result += from();
454  break;
455  case 'F':
456  result += fromStrip();
457  break;
458  case 'f':
459  {
460  str = fromStrip();
461 
462  for (j=0; str[j]>' '; j++)
463  ;
464  unsigned int strLength(str.length());
465  for (; j < strLength && str[j] <= ' '; j++)
466  ;
467  result += str[0];
468  if (str[j]>' ')
469  result += str[j];
470  else
471  if (str[1]>' ')
472  result += str[1];
473  }
474  break;
475  case 'T':
476  result += toStrip();
477  break;
478  case 't':
479  result += to();
480  break;
481  case 'C':
482  result += ccStrip();
483  break;
484  case 'c':
485  result += cc();
486  break;
487  case 'S':
488  result += subject();
489  break;
490  case '_':
491  result += ' ';
492  break;
493  case 'L':
494  result += "\n";
495  break;
496  case '%':
497  result += '%';
498  break;
499  default:
500  result += '%';
501  result += ch;
502  break;
503  }
504  } else
505  result += ch;
506  }
507  return result;
508 }
509 
510 static void removeTrailingSpace( TQString &line )
511 {
512  int i = line.length()-1;
513  while( (i >= 0) && ((line[i] == ' ') || (line[i] == '\t')))
514  i--;
515  line.truncate( i+1);
516 }
517 
518 static TQString splitLine( TQString &line)
519 {
520  removeTrailingSpace( line );
521  int i = 0;
522  int j = -1;
523  int l = line.length();
524 
525  // TODO: Replace tabs with spaces first.
526 
527  while(i < l)
528  {
529  TQChar c = line[i];
530  if ((c == '>') || (c == ':') || (c == '|'))
531  j = i+1;
532  else if ((c != ' ') && (c != '\t'))
533  break;
534  i++;
535  }
536 
537  if ( j <= 0 )
538  {
539  return "";
540  }
541  if ( i == l )
542  {
543  TQString result = line.left(j);
544  line = TQString();
545  return result;
546  }
547 
548  TQString result = line.left(j);
549  line = line.mid(j);
550  return result;
551 }
552 
553 static TQString flowText(TQString &text, const TQString& indent, int maxLength)
554 {
555  maxLength--;
556  if (text.isEmpty())
557  {
558  return indent+"<NULL>\n";
559  }
560  TQString result;
561  while (1)
562  {
563  int i;
564  if ((int) text.length() > maxLength)
565  {
566  i = maxLength;
567  while( (i >= 0) && (text[i] != ' '))
568  i--;
569  if (i <= 0)
570  {
571  // Couldn't break before maxLength.
572  i = maxLength;
573 // while( (i < (int) text.length()) && (text[i] != ' '))
574 // i++;
575  }
576  }
577  else
578  {
579  i = text.length();
580  }
581 
582  TQString line = text.left(i);
583  if (i < (int) text.length())
584  text = text.mid(i);
585  else
586  text = TQString();
587 
588  result += indent + line + '\n';
589 
590  if (text.isEmpty())
591  return result;
592  }
593 }
594 
595 static bool flushPart(TQString &msg, TQStringList &part,
596  const TQString &indent, int maxLength)
597 {
598  maxLength -= indent.length();
599  if (maxLength < 20) maxLength = 20;
600 
601  // Remove empty lines at end of quote
602  while ((part.begin() != part.end()) && part.last().isEmpty())
603  {
604  part.remove(part.fromLast());
605  }
606 
607  TQString text;
608  for(TQStringList::Iterator it2 = part.begin();
609  it2 != part.end();
610  it2++)
611  {
612  TQString line = (*it2);
613 
614  if (line.isEmpty())
615  {
616  if (!text.isEmpty())
617  msg += flowText(text, indent, maxLength);
618  msg += indent + '\n';
619  }
620  else
621  {
622  if (text.isEmpty())
623  text = line;
624  else
625  text += ' '+line.stripWhiteSpace();
626 
627  if (((int) text.length() < maxLength) || ((int) line.length() < (maxLength-10)))
628  msg += flowText(text, indent, maxLength);
629  }
630  }
631  if (!text.isEmpty())
632  msg += flowText(text, indent, maxLength);
633 
634  bool appendEmptyLine = true;
635  if (!part.count())
636  appendEmptyLine = false;
637 
638  part.clear();
639  return appendEmptyLine;
640 }
641 
642 static TQString stripSignature( const TQString & msg, bool clearSigned ) {
643  if ( clearSigned )
644  return msg.left( msg.findRev( TQRegExp( "\n--\\s?\n" ) ) );
645  else
646  return msg.left( msg.findRev( "\n-- \n" ) );
647 }
648 
649 TQString KMMessage::smartQuote( const TQString & msg, int maxLineLength )
650 {
651  TQStringList part;
652  TQString oldIndent;
653  bool firstPart = true;
654 
655 
656  const TQStringList lines = TQStringList::split('\n', msg, true);
657 
658  TQString result;
659  for(TQStringList::const_iterator it = lines.begin();
660  it != lines.end();
661  ++it)
662  {
663  TQString line = *it;
664 
665  const TQString indent = splitLine( line );
666 
667  if ( line.isEmpty())
668  {
669  if (!firstPart)
670  part.append(TQString());
671  continue;
672  };
673 
674  if (firstPart)
675  {
676  oldIndent = indent;
677  firstPart = false;
678  }
679 
680  if (oldIndent != indent)
681  {
682  TQString fromLine;
683  // Search if the last non-blank line could be "From" line
684  if (part.count() && (oldIndent.length() < indent.length()))
685  {
686  TQStringList::Iterator it2 = part.fromLast();
687  while( (it2 != part.end()) && (*it2).isEmpty())
688  --it2;
689 
690  if ((it2 != part.end()) && ((*it2).endsWith(":")))
691  {
692  fromLine = oldIndent + (*it2) + '\n';
693  part.remove(it2);
694  }
695  }
696  if (flushPart( result, part, oldIndent, maxLineLength))
697  {
698  if (oldIndent.length() > indent.length())
699  result += indent + '\n';
700  else
701  result += oldIndent + '\n';
702  }
703  if (!fromLine.isEmpty())
704  {
705  result += fromLine;
706  }
707  oldIndent = indent;
708  }
709  part.append(line);
710  }
711  flushPart( result, part, oldIndent, maxLineLength);
712  return result;
713 }
714 
715 
716 //-----------------------------------------------------------------------------
718  TQCString& parsedString,
719  const TQTextCodec*& codec,
720  bool& isHTML ) const
721 {
722  if ( !root ) return;
723 
724  isHTML = false;
725  partNode * curNode = root->findType( DwMime::kTypeText,
726  DwMime::kSubtypeUnknown,
727  true,
728  false );
729  kdDebug(5006) << "\n\n======= KMMessage::parseTextStringFromDwPart() - "
730  << ( curNode ? "text part found!\n" : "sorry, no text node!\n" ) << endl;
731  if( curNode ) {
732  isHTML = DwMime::kSubtypeHtml == curNode->subType();
733  // now parse the TEXT message part we want to quote
734  ObjectTreeParser otp( 0, 0, true, false, true );
735  otp.parseObjectTree( curNode );
736  parsedString = otp.rawReplyString();
737  codec = curNode->msgPart().codec();
738  }
739 }
740 
741 //-----------------------------------------------------------------------------
742 
743 TQString KMMessage::asPlainTextFromObjectTree( partNode *root, bool aStripSignature,
744  bool allowDecryption ) const
745 {
746  Q_ASSERT( root );
747  Q_ASSERT( root->processed() );
748 
749  TQCString parsedString;
750  bool isHTML = false;
751  const TQTextCodec * codec = 0;
752 
753  if ( !root ) return TQString();
754  parseTextStringFromDwPart( root, parsedString, codec, isHTML );
755 
756  if ( mOverrideCodec || !codec )
757  codec = this->codec();
758 
759  if ( parsedString.isEmpty() )
760  return TQString();
761 
762  bool clearSigned = false;
763  TQString result;
764 
765  // decrypt
766  if ( allowDecryption ) {
767  TQPtrList<Kpgp::Block> pgpBlocks;
768  TQStrList nonPgpBlocks;
769  if ( Kpgp::Module::prepareMessageForDecryption( parsedString,
770  pgpBlocks,
771  nonPgpBlocks ) ) {
772  // Only decrypt/strip off the signature if there is only one OpenPGP
773  // block in the message
774  if ( pgpBlocks.count() == 1 ) {
775  Kpgp::Block * block = pgpBlocks.first();
776  if ( block->type() == Kpgp::PgpMessageBlock ||
777  block->type() == Kpgp::ClearsignedBlock ) {
778  if ( block->type() == Kpgp::PgpMessageBlock ) {
779  // try to decrypt this OpenPGP block
780  block->decrypt();
781  } else {
782  // strip off the signature
783  block->verify();
784  clearSigned = true;
785  }
786 
787  result = codec->toUnicode( nonPgpBlocks.first() )
788  + codec->toUnicode( block->text() )
789  + codec->toUnicode( nonPgpBlocks.last() );
790  }
791  }
792  }
793  }
794 
795  if ( result.isEmpty() ) {
796  result = codec->toUnicode( parsedString );
797  if ( result.isEmpty() )
798  return result;
799  }
800 
801  // html -> plaintext conversion, if necessary:
802  if ( isHTML && mDecodeHTML ) {
803  KHTMLPart htmlPart;
804  htmlPart.setOnlyLocalReferences( true );
805  htmlPart.setMetaRefreshEnabled( false );
806  htmlPart.setPluginsEnabled( false );
807  htmlPart.setJScriptEnabled( false );
808  htmlPart.setJavaEnabled( false );
809  htmlPart.begin();
810  htmlPart.write( result );
811  htmlPart.end();
812  htmlPart.selectAll();
813  result = htmlPart.selectedText();
814  }
815 
816  // strip the signature (footer):
817  if ( aStripSignature )
818  return stripSignature( result, clearSigned );
819  else
820  return result;
821 }
822 
823 //-----------------------------------------------------------------------------
824 
825 TQString KMMessage::asPlainText( bool aStripSignature, bool allowDecryption ) const
826 {
827  partNode *root = partNode::fromMessage( this );
828  if ( !root )
829  return TQString();
830 
831  ObjectTreeParser otp;
832  otp.parseObjectTree( root );
833  TQString result = asPlainTextFromObjectTree( root, aStripSignature, allowDecryption );
834  delete root;
835  return result;
836 }
837 
838 TQString KMMessage::asQuotedString( const TQString& aHeaderStr,
839  const TQString& aIndentStr,
840  const TQString& selection /* = TQString() */,
841  bool aStripSignature /* = true */,
842  bool allowDecryption /* = true */) const
843 {
844  TQString content = selection.isEmpty() ?
845  asPlainText( aStripSignature, allowDecryption ) : selection ;
846 
847  // Remove blank lines at the beginning:
848  const int firstNonWS = content.find( TQRegExp( "\\S" ) );
849  const int lineStart = content.findRev( '\n', firstNonWS );
850  if ( lineStart >= 0 )
851  content.remove( 0, static_cast<unsigned int>( lineStart ) );
852 
853  const TQString indentStr = formatString( aIndentStr );
854 
855  content.replace( '\n', '\n' + indentStr );
856  content.prepend( indentStr );
857  content += '\n';
858 
859  const TQString headerStr = formatString( aHeaderStr );
860  if ( sSmartQuote && sWordWrap )
861  return headerStr + smartQuote( content, sWrapCol );
862  return headerStr + content;
863 }
864 
865 //-----------------------------------------------------------------------------
866 KMMessage* KMMessage::createReply( KMail::ReplyStrategy replyStrategy,
867  TQString selection /* = TQString() */,
868  bool noQuote /* = false */,
869  bool allowDecryption /* = true */,
870  const TQString &tmpl /* = TQString() */ )
871 {
872  KMMessage* msg = new KMMessage;
873  TQString mailingListStr, replyToStr, toStr;
874  TQStringList mailingListAddresses;
875  TQCString refStr, headerName;
876  bool replyAll = true;
877 
878  msg->initFromMessage(this);
879 
880  MailingList::name(this, headerName, mailingListStr);
881  replyToStr = replyTo();
882 
883  msg->setCharset("utf-8");
884 
885  // determine the mailing list posting address
886  if ( parent() && parent()->isMailingListEnabled() &&
887  !parent()->mailingListPostAddress().isEmpty() ) {
888  mailingListAddresses << parent()->mailingListPostAddress();
889  }
890  if ( headerField("List-Post").find( "mailto:", 0, false ) != -1 ) {
891  TQString listPost = headerField("List-Post");
892  TQRegExp rx( "<mailto:([^@>]+)@([^>]+)>", false );
893  if ( rx.search( listPost, 0 ) != -1 ) // matched
894  mailingListAddresses << rx.cap(1) + '@' + rx.cap(2);
895  }
896 
897  // use the "On ... Joe User wrote:" header by default
898  switch( replyStrategy ) {
899  case KMail::ReplySmart : {
900  if ( !headerField( "Mail-Followup-To" ).isEmpty() ) {
901  toStr = headerField( "Mail-Followup-To" );
902  }
903  else if ( !replyToStr.isEmpty() ) {
904  // assume a Reply-To header mangling mailing list
905  toStr = replyToStr;
906  }
907  else if ( !mailingListAddresses.isEmpty() ) {
908  toStr = mailingListAddresses[0];
909  }
910  else {
911  // doesn't seem to be a mailing list, reply to From: address
912  toStr = from();
913  //replyStr = sReplyStr; // reply to author, so use "On ... you wrote:"
914  replyAll = false;
915  }
916  // strip all my addresses from the list of recipients
917  TQStringList recipients = KPIM::splitEmailAddrList( toStr );
918  toStr = stripMyAddressesFromAddressList( recipients ).join(", ");
919  // ... unless the list contains only my addresses (reply to self)
920  if ( toStr.isEmpty() && !recipients.isEmpty() )
921  toStr = recipients[0];
922 
923  break;
924  }
925  case KMail::ReplyList : {
926  if ( !headerField( "Mail-Followup-To" ).isEmpty() ) {
927  toStr = headerField( "Mail-Followup-To" );
928  }
929  else if ( !mailingListAddresses.isEmpty() ) {
930  toStr = mailingListAddresses[0];
931  }
932  else if ( !replyToStr.isEmpty() ) {
933  // assume a Reply-To header mangling mailing list
934  toStr = replyToStr;
935  }
936  // strip all my addresses from the list of recipients
937  TQStringList recipients = KPIM::splitEmailAddrList( toStr );
938  toStr = stripMyAddressesFromAddressList( recipients ).join(", ");
939 
940  break;
941  }
942  case KMail::ReplyAll : {
943  TQStringList recipients;
944  TQStringList ccRecipients;
945 
946  // add addresses from the Reply-To header to the list of recipients
947  if( !replyToStr.isEmpty() ) {
948  recipients += KPIM::splitEmailAddrList( replyToStr );
949  // strip all possible mailing list addresses from the list of Reply-To
950  // addresses
951  for ( TQStringList::const_iterator it = mailingListAddresses.begin();
952  it != mailingListAddresses.end();
953  ++it ) {
954  recipients = stripAddressFromAddressList( *it, recipients );
955  }
956  }
957 
958  if ( !mailingListAddresses.isEmpty() ) {
959  // this is a mailing list message
960  if ( recipients.isEmpty() && !from().isEmpty() ) {
961  // The sender didn't set a Reply-to address, so we add the From
962  // address to the list of CC recipients.
963  ccRecipients += from();
964  kdDebug(5006) << "Added " << from() << " to the list of CC recipients"
965  << endl;
966  }
967  // if it is a mailing list, add the posting address
968  recipients.prepend( mailingListAddresses[0] );
969  }
970  else {
971  // this is a normal message
972  if ( recipients.isEmpty() && !from().isEmpty() ) {
973  // in case of replying to a normal message only then add the From
974  // address to the list of recipients if there was no Reply-to address
975  recipients += from();
976  kdDebug(5006) << "Added " << from() << " to the list of recipients"
977  << endl;
978  }
979  }
980 
981  // strip all my addresses from the list of recipients
982  toStr = stripMyAddressesFromAddressList( recipients ).join(", ");
983 
984  // merge To header and CC header into a list of CC recipients
985  if( !cc().isEmpty() || !to().isEmpty() ) {
986  TQStringList list;
987  if (!to().isEmpty())
988  list += KPIM::splitEmailAddrList(to());
989  if (!cc().isEmpty())
990  list += KPIM::splitEmailAddrList(cc());
991  for( TQStringList::Iterator it = list.begin(); it != list.end(); ++it ) {
992  if( !addressIsInAddressList( *it, recipients )
993  && !addressIsInAddressList( *it, ccRecipients ) ) {
994  ccRecipients += *it;
995  kdDebug(5006) << "Added " << *it << " to the list of CC recipients"
996  << endl;
997  }
998  }
999  }
1000 
1001  if ( !ccRecipients.isEmpty() ) {
1002  // strip all my addresses from the list of CC recipients
1003  ccRecipients = stripMyAddressesFromAddressList( ccRecipients );
1004 
1005  // in case of a reply to self toStr might be empty. if that's the case
1006  // then propagate a cc recipient to To: (if there is any).
1007  if ( toStr.isEmpty() && !ccRecipients.isEmpty() ) {
1008  toStr = ccRecipients[0];
1009  ccRecipients.pop_front();
1010  }
1011 
1012  msg->setCc( ccRecipients.join(", ") );
1013  }
1014 
1015  if ( toStr.isEmpty() && !recipients.isEmpty() ) {
1016  // reply to self without other recipients
1017  toStr = recipients[0];
1018  }
1019  break;
1020  }
1021  case KMail::ReplyAuthor : {
1022  if ( !replyToStr.isEmpty() ) {
1023  TQStringList recipients = KPIM::splitEmailAddrList( replyToStr );
1024  // strip the mailing list post address from the list of Reply-To
1025  // addresses since we want to reply in private
1026  for ( TQStringList::const_iterator it = mailingListAddresses.begin();
1027  it != mailingListAddresses.end();
1028  ++it ) {
1029  recipients = stripAddressFromAddressList( *it, recipients );
1030  }
1031  if ( !recipients.isEmpty() ) {
1032  toStr = recipients.join(", ");
1033  }
1034  else {
1035  // there was only the mailing list post address in the Reply-To header,
1036  // so use the From address instead
1037  toStr = from();
1038  }
1039  }
1040  else if ( !from().isEmpty() ) {
1041  toStr = from();
1042  }
1043  replyAll = false;
1044  break;
1045  }
1046  case KMail::ReplyNone : {
1047  // the addressees will be set by the caller
1048  }
1049  }
1050 
1051  msg->setTo(toStr);
1052 
1053  refStr = getRefStr();
1054  if (!refStr.isEmpty())
1055  msg->setReferences(refStr);
1056  //In-Reply-To = original msg-id
1057  msg->setReplyToId(msgId());
1058 
1059 // if (!noQuote) {
1060 // if( selectionIsBody ){
1061 // TQCString cStr = selection.latin1();
1062 // msg->setBody( cStr );
1063 // }else{
1064 // msg->setBody(asQuotedString(replyStr + "\n", sIndentPrefixStr, selection,
1065 // sSmartQuote, allowDecryption).utf8());
1066 // }
1067 // }
1068 
1069  msg->setSubject( replySubject() );
1070  msg->setHeaderField( "X-KMail-QuotePrefix",
1071  formatString( GlobalSettings::self()->quoteString() ) );
1072  if( !noQuote ) {
1073  TemplateParser parser( msg, ( replyAll ? TemplateParser::ReplyAll : TemplateParser::Reply ) );
1074  parser.setAllowDecryption( allowDecryption );
1075  if ( GlobalSettings::quoteSelectionOnly() ) {
1076  parser.setSelection( selection );
1077  }
1078  if ( !tmpl.isEmpty() ) {
1079  parser.process( tmpl, this );
1080  } else {
1081  parser.process( this );
1082  }
1083  }
1084  // setStatus(KMMsgStatusReplied);
1085  msg->link(this, KMMsgStatusReplied);
1086 
1087  if ( parent() && parent()->putRepliesInSameFolder() )
1088  msg->setFcc( parent()->idString() );
1089 
1090  // replies to an encrypted message should be encrypted as well
1091  if ( encryptionState() == KMMsgPartiallyEncrypted ||
1092  encryptionState() == KMMsgFullyEncrypted ) {
1093  msg->setEncryptionState( KMMsgFullyEncrypted );
1094  }
1095 
1096  return msg;
1097 }
1098 
1099 
1100 //-----------------------------------------------------------------------------
1101 TQCString KMMessage::getRefStr() const
1102 {
1103  TQCString firstRef, lastRef, refStr, retRefStr;
1104  int i, j;
1105 
1106  refStr = headerField("References").stripWhiteSpace().latin1();
1107 
1108  if (refStr.isEmpty())
1109  return headerField("Message-Id").latin1();
1110 
1111  i = refStr.find('<');
1112  j = refStr.find('>');
1113  firstRef = refStr.mid(i, j-i+1);
1114  if (!firstRef.isEmpty())
1115  retRefStr = firstRef + ' ';
1116 
1117  i = refStr.findRev('<');
1118  j = refStr.findRev('>');
1119 
1120  lastRef = refStr.mid(i, j-i+1);
1121  if (!lastRef.isEmpty() && lastRef != firstRef)
1122  retRefStr += lastRef + ' ';
1123 
1124  retRefStr += headerField("Message-Id").latin1();
1125  return retRefStr;
1126 }
1127 
1128 
1129 KMMessage* KMMessage::createRedirect( const TQString &toStr )
1130 {
1131  // copy the message 1:1
1132  KMMessage* msg = new KMMessage( new DwMessage( *this->mMsg ) );
1133  KMMessagePart msgPart;
1134 
1135  uint id = 0;
1136  TQString strId = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace();
1137  if ( !strId.isEmpty())
1138  id = strId.toUInt();
1139  const KPIM::Identity & ident =
1140  kmkernel->identityManager()->identityForUoidOrDefault( id );
1141 
1142  // X-KMail-Redirect-From: content
1143  TQString strByWayOf = TQString("%1 (by way of %2 <%3>)")
1144  .arg( from() )
1145  .arg( ident.fullName() )
1146  .arg( ident.primaryEmailAddress() );
1147 
1148  // Resent-From: content
1149  TQString strFrom = TQString("%1 <%2>")
1150  .arg( ident.fullName() )
1151  .arg( ident.primaryEmailAddress() );
1152 
1153  // format the current date to be used in Resent-Date:
1154  TQString origDate = msg->headerField( "Date" );
1155  msg->setDateToday();
1156  TQString newDate = msg->headerField( "Date" );
1157  // make sure the Date: header is valid
1158  if ( origDate.isEmpty() )
1159  msg->removeHeaderField( "Date" );
1160  else
1161  msg->setHeaderField( "Date", origDate );
1162 
1163  // prepend Resent-*: headers (c.f. RFC2822 3.6.6)
1164  msg->setHeaderField( "Resent-Message-ID", generateMessageId( msg->sender() ),
1165  Structured, true);
1166  msg->setHeaderField( "Resent-Date", newDate, Structured, true );
1167  msg->setHeaderField( "Resent-To", toStr, Address, true );
1168  msg->setHeaderField( "Resent-From", strFrom, Address, true );
1169 
1170  msg->setHeaderField( "X-KMail-Redirect-From", strByWayOf );
1171  msg->setHeaderField( "X-KMail-Recipients", toStr, Address );
1172 
1173  msg->link(this, KMMsgStatusForwarded);
1174 
1175  return msg;
1176 }
1177 
1178 
1179 //-----------------------------------------------------------------------------
1181 {
1182  TQString s;
1183  TQCString str;
1184 
1185  if (sHeaderStrategy == HeaderStrategy::all()) {
1186  s = "\n\n---------- " + sForwardStr + " ----------\n\n";
1187  s += headerAsString();
1188  str = asQuotedString(s, "", TQString(), false, false).utf8();
1189  str += "\n-------------------------------------------------------\n";
1190  } else {
1191  s = "\n\n---------- " + sForwardStr + " ----------\n\n";
1192  s += "Subject: " + subject() + "\n";
1193  s += "Date: "
1194  + KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized,
1195  date(), sReplyLanguage, false )
1196  + "\n";
1197  s += "From: " + from() + "\n";
1198  s += "To: " + to() + "\n";
1199  if (!cc().isEmpty()) s += "Cc: " + cc() + "\n";
1200  s += "\n";
1201  str = asQuotedString(s, "", TQString(), false, false).utf8();
1202  str += "\n-------------------------------------------------------\n";
1203  }
1204 
1205  return str;
1206 }
1207 
1208 void KMMessage::sanitizeHeaders( const TQStringList& whiteList )
1209 {
1210  // Strip out all headers apart from the content description and other
1211  // whitelisted ones, because we don't want to inherit them.
1212  DwHeaders& header = mMsg->Headers();
1213  DwField* field = header.FirstField();
1214  DwField* nextField;
1215  while (field)
1216  {
1217  nextField = field->Next();
1218  if ( field->FieldNameStr().find( "ontent" ) == DwString::npos
1219  && !whiteList.contains( TQString::fromLatin1( field->FieldNameStr().c_str() ) ) )
1220  header.RemoveField(field);
1221  field = nextField;
1222  }
1223  mMsg->Assemble();
1224 }
1225 
1226 //-----------------------------------------------------------------------------
1227 KMMessage* KMMessage::createForward( const TQString &tmpl /* = TQString() */ )
1228 {
1229  KMMessage* msg = new KMMessage();
1230 
1231  // If this is a multipart mail or if the main part is only the text part,
1232  // Make an identical copy of the mail, minus headers, so attachments are
1233  // preserved
1234  if ( type() == DwMime::kTypeMultipart ||
1235  ( type() == DwMime::kTypeText && subtype() == DwMime::kSubtypePlain ) ) {
1236  // ## slow, we could probably use: delete msg->mMsg; msg->mMsg = new DwMessage( this->mMsg );
1237  msg->fromDwString( this->asDwString() );
1238  // remember the type and subtype, initFromMessage sets the contents type to
1239  // text/plain, via initHeader, for unclear reasons
1240  DwMediaType oldContentType = msg->mMsg->Headers().ContentType();
1241 
1242  msg->sanitizeHeaders();
1243 
1244  // strip blacklisted parts
1245  TQStringList blacklist = GlobalSettings::self()->mimetypesToStripWhenInlineForwarding();
1246  for ( TQStringList::Iterator it = blacklist.begin(); it != blacklist.end(); ++it ) {
1247  TQString entry = (*it);
1248  int sep = entry.find( '/' );
1249  TQCString type = entry.left( sep ).latin1();
1250  TQCString subtype = entry.mid( sep+1 ).latin1();
1251  kdDebug( 5006 ) << "Looking for blacklisted type: " << type << "/" << subtype << endl;
1252  while ( DwBodyPart * part = msg->findDwBodyPart( type, subtype ) ) {
1253  msg->mMsg->Body().RemoveBodyPart( part );
1254  }
1255  }
1256  msg->mMsg->Assemble();
1257  msg->initFromMessage( this );
1258 
1259  //restore type
1260  msg->mMsg->Headers().ContentType().FromString( oldContentType.AsString() );
1261  msg->mMsg->Headers().ContentType().Parse();
1262  msg->mMsg->Assemble();
1263  }
1264  else if( type() == DwMime::kTypeText && subtype() == DwMime::kSubtypeHtml ) {
1265  // This is non-multipart html mail. Let`s make it text/plain and allow
1266  // template parser do the hard job.
1267  msg->initFromMessage( this );
1268  msg->setType( DwMime::kTypeText );
1269  msg->setSubtype( DwMime::kSubtypeHtml );
1270  msg->mNeedsAssembly = true;
1271  msg->cleanupHeader();
1272  }
1273  else {
1274  // This is a non-multipart, non-text mail (e.g. text/calendar). Construct
1275  // a multipart/mixed mail and add the original body as an attachment.
1276  msg->initFromMessage( this );
1277  msg->removeHeaderField("Content-Type");
1278  msg->removeHeaderField("Content-Transfer-Encoding");
1279  // Modify the ContentType directly (replaces setAutomaticFields(true))
1280  DwHeaders & header = msg->mMsg->Headers();
1281  header.MimeVersion().FromString("1.0");
1282  DwMediaType & contentType = msg->dwContentType();
1283  contentType.SetType( DwMime::kTypeMultipart );
1284  contentType.SetSubtype( DwMime::kSubtypeMixed );
1285  contentType.CreateBoundary(0);
1286  contentType.Assemble();
1287 
1288  // empty text part
1289  KMMessagePart msgPart;
1290  bodyPart( 0, &msgPart );
1291  msg->addBodyPart(&msgPart);
1292  // the old contents of the mail
1293  KMMessagePart secondPart;
1294  secondPart.setType( type() );
1295  secondPart.setSubtype( subtype() );
1296  secondPart.setBody( mMsg->Body().AsString() );
1297  // use the headers of the original mail
1298  applyHeadersToMessagePart( mMsg->Headers(), &secondPart );
1299  msg->addBodyPart(&secondPart);
1300  msg->mNeedsAssembly = true;
1301  msg->cleanupHeader();
1302  }
1303  // TQString st = TQString::fromUtf8(createForwardBody());
1304 
1305  msg->setSubject( forwardSubject() );
1306 
1307  TemplateParser parser( msg, TemplateParser::Forward );
1308  if ( !tmpl.isEmpty() ) {
1309  parser.process( tmpl, this );
1310  } else {
1311  parser.process( this );
1312  }
1313 
1314  // TQCString encoding = autoDetectCharset(charset(), sPrefCharsets, msg->body());
1315  // if (encoding.isEmpty()) encoding = "utf-8";
1316  // msg->setCharset(encoding);
1317 
1318  // force utf-8
1319  // msg->setCharset( "utf-8" );
1320 
1321  msg->link(this, KMMsgStatusForwarded);
1322  return msg;
1323 }
1324 
1325 static const struct {
1326  const char * dontAskAgainID;
1327  bool canDeny;
1328  const char * text;
1329 } mdnMessageBoxes[] = {
1330  { "mdnNormalAsk", true,
1331  I18N_NOOP("This message contains a request to return a notification "
1332  "about your reception of the message.\n"
1333  "You can either ignore the request or let KMail send a "
1334  "\"denied\" or normal response.") },
1335  { "mdnUnknownOption", false,
1336  I18N_NOOP("This message contains a request to send a notification "
1337  "about your reception of the message.\n"
1338  "It contains a processing instruction that is marked as "
1339  "\"required\", but which is unknown to KMail.\n"
1340  "You can either ignore the request or let KMail send a "
1341  "\"failed\" response.") },
1342  { "mdnMultipleAddressesInReceiptTo", true,
1343  I18N_NOOP("This message contains a request to send a notification "
1344  "about your reception of the message,\n"
1345  "but it is requested to send the notification to more "
1346  "than one address.\n"
1347  "You can either ignore the request or let KMail send a "
1348  "\"denied\" or normal response.") },
1349  { "mdnReturnPathEmpty", true,
1350  I18N_NOOP("This message contains a request to send a notification "
1351  "about your reception of the message,\n"
1352  "but there is no return-path set.\n"
1353  "You can either ignore the request or let KMail send a "
1354  "\"denied\" or normal response.") },
1355  { "mdnReturnPathNotInReceiptTo", true,
1356  I18N_NOOP("This message contains a request to send a notification "
1357  "about your reception of the message,\n"
1358  "but the return-path address differs from the address "
1359  "the notification was requested to be sent to.\n"
1360  "You can either ignore the request or let KMail send a "
1361  "\"denied\" or normal response.") },
1362 };
1363 
1364 static const int numMdnMessageBoxes
1365  = sizeof mdnMessageBoxes / sizeof *mdnMessageBoxes;
1366 
1367 
1368 static int requestAdviceOnMDN( const char * what ) {
1369  for ( int i = 0 ; i < numMdnMessageBoxes ; ++i ) {
1370  if ( !qstrcmp( what, mdnMessageBoxes[i].dontAskAgainID ) ) {
1371  if ( mdnMessageBoxes[i].canDeny ) {
1372  const KCursorSaver saver( TQCursor::ArrowCursor );
1373  int answer = TQMessageBox::information( 0,
1374  i18n("Message Disposition Notification Request"),
1375  i18n( mdnMessageBoxes[i].text ),
1376  i18n("&Ignore"), i18n("Send \"&denied\""), i18n("&Send") );
1377  return answer ? answer + 1 : 0 ; // map to "mode" in createMDN
1378  } else {
1379  const KCursorSaver saver( TQCursor::ArrowCursor );
1380  int answer = TQMessageBox::information( 0,
1381  i18n("Message Disposition Notification Request"),
1382  i18n( mdnMessageBoxes[i].text ),
1383  i18n("&Ignore"), i18n("&Send") );
1384  return answer ? answer + 2 : 0 ; // map to "mode" in createMDN
1385  }
1386  }
1387  }
1388  kdWarning(5006) << "didn't find data for message box \""
1389  << what << "\"" << endl;
1390  return 0;
1391 }
1392 
1393 KMMessage* KMMessage::createMDN( MDN::ActionMode a,
1394  MDN::DispositionType d,
1395  bool allowGUI,
1396  TQValueList<MDN::DispositionModifier> m )
1397 {
1398  // RFC 2298: At most one MDN may be issued on behalf of each
1399  // particular recipient by their user agent. That is, once an MDN
1400  // has been issued on behalf of a recipient, no further MDNs may be
1401  // issued on behalf of that recipient, even if another disposition
1402  // is performed on the message.
1403 //#define MDN_DEBUG 1
1404 #ifndef MDN_DEBUG
1405  if ( mdnSentState() != KMMsgMDNStateUnknown &&
1406  mdnSentState() != KMMsgMDNNone )
1407  return 0;
1408 #else
1409  char st[2]; st[0] = (char)mdnSentState(); st[1] = 0;
1410  kdDebug(5006) << "mdnSentState() == '" << st << "'" << endl;
1411 #endif
1412 
1413  // RFC 2298: An MDN MUST NOT be generated in response to an MDN.
1414  if ( findDwBodyPart( DwMime::kTypeMessage,
1415  DwMime::kSubtypeDispositionNotification ) ) {
1416  setMDNSentState( KMMsgMDNIgnore );
1417  return 0;
1418  }
1419 
1420  // extract where to send to:
1421  TQString receiptTo = headerField("Disposition-Notification-To");
1422  if ( receiptTo.stripWhiteSpace().isEmpty() ) return 0;
1423  receiptTo.remove( '\n' );
1424 
1425 
1426  MDN::SendingMode s = MDN::SentAutomatically; // set to manual if asked user
1427  TQString special; // fill in case of error, warning or failure
1428  KConfigGroup mdnConfig( KMKernel::config(), "MDN" );
1429 
1430  // default:
1431  int mode = mdnConfig.readNumEntry( "default-policy", 0 );
1432  if ( !mode || mode < 0 || mode > 3 ) {
1433  // early out for ignore:
1434  setMDNSentState( KMMsgMDNIgnore );
1435  return 0;
1436  }
1437 
1438  // RFC 2298: An importance of "required" indicates that
1439  // interpretation of the parameter is necessary for proper
1440  // generation of an MDN in response to this request. If a UA does
1441  // not understand the meaning of the parameter, it MUST NOT generate
1442  // an MDN with any disposition type other than "failed" in response
1443  // to the request.
1444  TQString notificationOptions = headerField("Disposition-Notification-Options");
1445  if ( notificationOptions.contains( "required", false ) ) {
1446  // ### hacky; should parse...
1447  // There is a required option that we don't understand. We need to
1448  // ask the user what we should do:
1449  if ( !allowGUI ) return 0; // don't setMDNSentState here!
1450  mode = requestAdviceOnMDN( "mdnUnknownOption" );
1451  s = MDN::SentManually;
1452 
1453  special = i18n("Header \"Disposition-Notification-Options\" contained "
1454  "required, but unknown parameter");
1455  d = MDN::Failed;
1456  m.clear(); // clear modifiers
1457  }
1458 
1459  // RFC 2298: [ Confirmation from the user SHOULD be obtained (or no
1460  // MDN sent) ] if there is more than one distinct address in the
1461  // Disposition-Notification-To header.
1462  kdDebug(5006) << "KPIM::splitEmailAddrList(receiptTo): "
1463  << KPIM::splitEmailAddrList(receiptTo).join("\n") << endl;
1464  if ( KPIM::splitEmailAddrList(receiptTo).count() > 1 ) {
1465  if ( !allowGUI ) return 0; // don't setMDNSentState here!
1466  mode = requestAdviceOnMDN( "mdnMultipleAddressesInReceiptTo" );
1467  s = MDN::SentManually;
1468  }
1469 
1470  // RFC 2298: MDNs SHOULD NOT be sent automatically if the address in
1471  // the Disposition-Notification-To header differs from the address
1472  // in the Return-Path header. [...] Confirmation from the user
1473  // SHOULD be obtained (or no MDN sent) if there is no Return-Path
1474  // header in the message [...]
1475  AddrSpecList returnPathList = extractAddrSpecs("Return-Path");
1476  TQString returnPath = returnPathList.isEmpty() ? TQString()
1477  : returnPathList.front().localPart + '@' + returnPathList.front().domain ;
1478  kdDebug(5006) << "clean return path: " << returnPath << endl;
1479  if ( returnPath.isEmpty() || !receiptTo.contains( returnPath, false ) ) {
1480  if ( !allowGUI ) return 0; // don't setMDNSentState here!
1481  mode = requestAdviceOnMDN( returnPath.isEmpty() ?
1482  "mdnReturnPathEmpty" :
1483  "mdnReturnPathNotInReceiptTo" );
1484  s = MDN::SentManually;
1485  }
1486 
1487  if ( a != KMime::MDN::AutomaticAction ) {
1488  //TODO: only ingore user settings for AutomaticAction if requested
1489  if ( mode == 1 ) { // ask
1490  if ( !allowGUI ) return 0; // don't setMDNSentState here!
1491  mode = requestAdviceOnMDN( "mdnNormalAsk" );
1492  s = MDN::SentManually; // asked user
1493  }
1494 
1495  switch ( mode ) {
1496  case 0: // ignore:
1497  setMDNSentState( KMMsgMDNIgnore );
1498  return 0;
1499  default:
1500  case 1:
1501  kdFatal(5006) << "KMMessage::createMDN(): The \"ask\" mode should "
1502  << "never appear here!" << endl;
1503  break;
1504  case 2: // deny
1505  d = MDN::Denied;
1506  m.clear();
1507  break;
1508  case 3:
1509  break;
1510  }
1511  }
1512 
1513 
1514  // extract where to send from:
1515  TQString finalRecipient = kmkernel->identityManager()
1516  ->identityForUoidOrDefault( identityUoid() ).fullEmailAddr();
1517 
1518  //
1519  // Generate message:
1520  //
1521 
1522  KMMessage * receipt = new KMMessage();
1523  receipt->initFromMessage( this );
1524  receipt->removeHeaderField("Content-Type");
1525  receipt->removeHeaderField("Content-Transfer-Encoding");
1526  // Modify the ContentType directly (replaces setAutomaticFields(true))
1527  DwHeaders & header = receipt->mMsg->Headers();
1528  header.MimeVersion().FromString("1.0");
1529  DwMediaType & contentType = receipt->dwContentType();
1530  contentType.SetType( DwMime::kTypeMultipart );
1531  contentType.SetSubtype( DwMime::kSubtypeReport );
1532  contentType.CreateBoundary(0);
1533  receipt->mNeedsAssembly = true;
1534  receipt->setContentTypeParam( "report-type", "disposition-notification" );
1535 
1536  TQString description = replaceHeadersInString( MDN::descriptionFor( d, m ) );
1537 
1538  // text/plain part:
1539  KMMessagePart firstMsgPart;
1540  firstMsgPart.setTypeStr( "text" );
1541  firstMsgPart.setSubtypeStr( "plain" );
1542  firstMsgPart.setBodyFromUnicode( description );
1543  receipt->addBodyPart( &firstMsgPart );
1544 
1545  // message/disposition-notification part:
1546  KMMessagePart secondMsgPart;
1547  secondMsgPart.setType( DwMime::kTypeMessage );
1548  secondMsgPart.setSubtype( DwMime::kSubtypeDispositionNotification );
1549  //secondMsgPart.setCharset( "us-ascii" );
1550  //secondMsgPart.setCteStr( "7bit" );
1551  secondMsgPart.setBodyEncoded( MDN::dispositionNotificationBodyContent(
1552  finalRecipient,
1553  rawHeaderField("Original-Recipient"),
1554  id(), /* Message-ID */
1555  d, a, s, m, special ) );
1556  receipt->addBodyPart( &secondMsgPart );
1557 
1558  // message/rfc822 or text/rfc822-headers body part:
1559  int num = mdnConfig.readNumEntry( "quote-message", 0 );
1560  if ( num < 0 || num > 2 ) num = 0;
1561  MDN::ReturnContent returnContent = static_cast<MDN::ReturnContent>( num );
1562 
1563  KMMessagePart thirdMsgPart;
1564  switch ( returnContent ) {
1565  case MDN::All:
1566  thirdMsgPart.setTypeStr( "message" );
1567  thirdMsgPart.setSubtypeStr( "rfc822" );
1568  thirdMsgPart.setBody( asSendableString() );
1569  receipt->addBodyPart( &thirdMsgPart );
1570  break;
1571  case MDN::HeadersOnly:
1572  thirdMsgPart.setTypeStr( "text" );
1573  thirdMsgPart.setSubtypeStr( "rfc822-headers" );
1574  thirdMsgPart.setBody( headerAsSendableString() );
1575  receipt->addBodyPart( &thirdMsgPart );
1576  break;
1577  case MDN::Nothing:
1578  default:
1579  break;
1580  };
1581 
1582  receipt->setTo( receiptTo );
1583  receipt->setSubject( "Message Disposition Notification" );
1584  receipt->setReplyToId( msgId() );
1585  receipt->setReferences( getRefStr() );
1586 
1587  receipt->cleanupHeader();
1588 
1589  kdDebug(5006) << "final message:\n" + receipt->asString() << endl;
1590 
1591  //
1592  // Set "MDN sent" status:
1593  //
1594  KMMsgMDNSentState state = KMMsgMDNStateUnknown;
1595  switch ( d ) {
1596  case MDN::Displayed: state = KMMsgMDNDisplayed; break;
1597  case MDN::Deleted: state = KMMsgMDNDeleted; break;
1598  case MDN::Dispatched: state = KMMsgMDNDispatched; break;
1599  case MDN::Processed: state = KMMsgMDNProcessed; break;
1600  case MDN::Denied: state = KMMsgMDNDenied; break;
1601  case MDN::Failed: state = KMMsgMDNFailed; break;
1602  };
1603  setMDNSentState( state );
1604 
1605  return receipt;
1606 }
1607 
1608 TQString KMMessage::replaceHeadersInString( const TQString & s ) const {
1609  TQString result = s;
1610  TQRegExp rx( "\\$\\{([a-z0-9-]+)\\}", false );
1611  Q_ASSERT( rx.isValid() );
1612 
1613  TQRegExp rxDate( "\\$\\{date\\}" );
1614  Q_ASSERT( rxDate.isValid() );
1615 
1616  TQString sDate = KMime::DateFormatter::formatDate(
1617  KMime::DateFormatter::Localized, date() );
1618 
1619  int idx = 0;
1620  if( ( idx = rxDate.search( result, idx ) ) != -1 ) {
1621  result.replace( idx, rxDate.matchedLength(), sDate );
1622  }
1623 
1624  idx = 0;
1625  while ( ( idx = rx.search( result, idx ) ) != -1 ) {
1626  TQString replacement = headerField( TQString(rx.cap(1)).latin1() );
1627  result.replace( idx, rx.matchedLength(), replacement );
1628  idx += replacement.length();
1629  }
1630  return result;
1631 }
1632 
1634 {
1635  TQString str, receiptTo;
1636  KMMessage *receipt;
1637 
1638  receiptTo = headerField("Disposition-Notification-To");
1639  if ( receiptTo.stripWhiteSpace().isEmpty() ) return 0;
1640  receiptTo.remove( '\n' );
1641 
1642  receipt = new KMMessage;
1643  receipt->initFromMessage(this);
1644  receipt->setTo(receiptTo);
1645  receipt->setSubject(i18n("Receipt: ") + subject());
1646 
1647  str = "Your message was successfully delivered.";
1648  str += "\n\n---------- Message header follows ----------\n";
1649  str += headerAsString();
1650  str += "--------------------------------------------\n";
1651  // Conversion to latin1 is correct here as Mail headers should contain
1652  // ascii only
1653  receipt->setBody(str.latin1());
1654  receipt->setAutomaticFields();
1655 
1656  return receipt;
1657 }
1658 
1659 
1661 {
1662  const KPIM::Identity & ident =
1663  kmkernel->identityManager()->identityForUoidOrDefault( id );
1664 
1665  if(ident.fullEmailAddr().isEmpty())
1666  setFrom("");
1667  else
1668  setFrom(ident.fullEmailAddr());
1669 
1670  if(ident.replyToAddr().isEmpty())
1671  setReplyTo("");
1672  else
1673  setReplyTo(ident.replyToAddr());
1674 
1675  if(ident.bcc().isEmpty())
1676  setBcc("");
1677  else
1678  setBcc(ident.bcc());
1679 
1680  if (ident.organization().isEmpty())
1681  removeHeaderField("Organization");
1682  else
1683  setHeaderField("Organization", ident.organization());
1684 
1685  if (ident.isDefault())
1686  removeHeaderField("X-KMail-Identity");
1687  else
1688  setHeaderField("X-KMail-Identity", TQString::number( ident.uoid() ));
1689 
1690  if ( ident.transport().isEmpty() )
1691  removeHeaderField( "X-KMail-Transport" );
1692  else
1693  setHeaderField( "X-KMail-Transport", ident.transport() );
1694 
1695  if ( ident.fcc().isEmpty() )
1696  setFcc( TQString() );
1697  else
1698  setFcc( ident.fcc() );
1699 
1700  if ( ident.drafts().isEmpty() )
1701  setDrafts( TQString() );
1702  else
1703  setDrafts( ident.drafts() );
1704 
1705  if ( ident.templates().isEmpty() )
1706  setTemplates( TQString() );
1707  else
1708  setTemplates( ident.templates() );
1709 
1710 }
1711 
1712 //-----------------------------------------------------------------------------
1713 void KMMessage::initHeader( uint id )
1714 {
1715  applyIdentity( id );
1716  setTo("");
1717  setSubject("");
1718  setDateToday();
1719 
1720  setHeaderField("User-Agent", "KMail/" KMAIL_VERSION );
1721  // This will allow to change Content-Type:
1722  setHeaderField("Content-Type","text/plain");
1723 }
1724 
1726  TQString idString = headerField("X-KMail-Identity").stripWhiteSpace();
1727  bool ok = false;
1728  int id = idString.toUInt( &ok );
1729 
1730  if ( !ok || id == 0 )
1731  id = kmkernel->identityManager()->identityForAddress( to() + ", " + cc() ).uoid();
1732  if ( id == 0 && parent() )
1733  id = parent()->identity();
1734 
1735  return id;
1736 }
1737 
1738 
1739 //-----------------------------------------------------------------------------
1740 void KMMessage::initFromMessage(const KMMessage *msg, bool idHeaders)
1741 {
1742  uint id = msg->identityUoid();
1743 
1744  if ( idHeaders ) initHeader(id);
1745  else setHeaderField("X-KMail-Identity", TQString::number(id));
1746  if (!msg->headerField("X-KMail-Transport").isEmpty())
1747  setHeaderField("X-KMail-Transport", msg->headerField("X-KMail-Transport"));
1748 }
1749 
1750 
1751 //-----------------------------------------------------------------------------
1753 {
1754  DwHeaders& header = mMsg->Headers();
1755  DwField* field = header.FirstField();
1756  DwField* nextField;
1757 
1758  if (mNeedsAssembly) mMsg->Assemble();
1759  mNeedsAssembly = false;
1760 
1761  while (field)
1762  {
1763  nextField = field->Next();
1764  if (field->FieldBody()->AsString().empty())
1765  {
1766  header.RemoveField(field);
1767  mNeedsAssembly = true;
1768  }
1769  field = nextField;
1770  }
1771 }
1772 
1773 
1774 //-----------------------------------------------------------------------------
1776 {
1777  DwHeaders& header = mMsg->Headers();
1778  header.MimeVersion().FromString("1.0");
1779 
1780  if (aIsMulti || numBodyParts() > 1)
1781  {
1782  // Set the type to 'Multipart' and the subtype to 'Mixed'
1783  DwMediaType& contentType = dwContentType();
1784  contentType.SetType( DwMime::kTypeMultipart);
1785  contentType.SetSubtype(DwMime::kSubtypeMixed );
1786 
1787  // Create a random printable string and set it as the boundary parameter
1788  contentType.CreateBoundary(0);
1789  }
1790  mNeedsAssembly = true;
1791 }
1792 
1793 
1794 //-----------------------------------------------------------------------------
1795 TQString KMMessage::dateStr() const
1796 {
1797  KConfigGroup general( KMKernel::config(), "General" );
1798  DwHeaders& header = mMsg->Headers();
1799  time_t unixTime;
1800 
1801  if (!header.HasDate()) return "";
1802  unixTime = header.Date().AsUnixTime();
1803 
1804  //kdDebug(5006)<<"#### Date = "<<header.Date().AsString().c_str()<<endl;
1805 
1806  return KMime::DateFormatter::formatDate(
1807  static_cast<KMime::DateFormatter::FormatType>(general.readNumEntry( "dateFormat", KMime::DateFormatter::Fancy )),
1808  unixTime, general.readEntry( "customDateFormat" ));
1809 }
1810 
1811 
1812 //-----------------------------------------------------------------------------
1813 TQCString KMMessage::dateShortStr() const
1814 {
1815  DwHeaders& header = mMsg->Headers();
1816  time_t unixTime;
1817 
1818  if (!header.HasDate()) return "";
1819  unixTime = header.Date().AsUnixTime();
1820 
1821  TQCString result = ctime(&unixTime);
1822  int len = result.length();
1823  if (result[len-1]=='\n')
1824  result.truncate(len-1);
1825 
1826  return result;
1827 }
1828 
1829 
1830 //-----------------------------------------------------------------------------
1831 TQString KMMessage::dateIsoStr() const
1832 {
1833  DwHeaders& header = mMsg->Headers();
1834  time_t unixTime;
1835 
1836  if (!header.HasDate()) return "";
1837  unixTime = header.Date().AsUnixTime();
1838 
1839  char cstr[64];
1840  strftime(cstr, 63, "%Y-%m-%d %H:%M:%S", localtime(&unixTime));
1841  return TQString(cstr);
1842 }
1843 
1844 
1845 //-----------------------------------------------------------------------------
1846 time_t KMMessage::date() const
1847 {
1848  time_t res = ( time_t )-1;
1849  DwHeaders& header = mMsg->Headers();
1850  if (header.HasDate())
1851  res = header.Date().AsUnixTime();
1852  return res;
1853 }
1854 
1855 
1856 //-----------------------------------------------------------------------------
1858 {
1859  struct timeval tval;
1860  gettimeofday(&tval, 0);
1861  setDate((time_t)tval.tv_sec);
1862 }
1863 
1864 
1865 //-----------------------------------------------------------------------------
1866 void KMMessage::setDate(time_t aDate)
1867 {
1868  mDate = aDate;
1869  mMsg->Headers().Date().FromCalendarTime(aDate);
1870  mMsg->Headers().Date().Assemble();
1871  mNeedsAssembly = true;
1872  mDirty = true;
1873 }
1874 
1875 
1876 //-----------------------------------------------------------------------------
1877 void KMMessage::setDate(const TQCString& aStr)
1878 {
1879  DwHeaders& header = mMsg->Headers();
1880 
1881  header.Date().FromString(aStr);
1882  header.Date().Parse();
1883  mNeedsAssembly = true;
1884  mDirty = true;
1885 
1886  if (header.HasDate())
1887  mDate = header.Date().AsUnixTime();
1888 }
1889 
1890 
1891 //-----------------------------------------------------------------------------
1892 TQString KMMessage::to() const
1893 {
1894  // handle To same as Cc below, bug 80747
1895  TQValueList<TQCString> rawHeaders = rawHeaderFields( "To" );
1896  TQStringList headers;
1897  for ( TQValueList<TQCString>::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) {
1898  headers << *it;
1899  }
1900  return KPIM::normalizeAddressesAndDecodeIDNs( headers.join( ", " ) );
1901 }
1902 
1903 
1904 //-----------------------------------------------------------------------------
1905 void KMMessage::setTo(const TQString& aStr)
1906 {
1907  setHeaderField( "To", aStr, Address );
1908 }
1909 
1910 //-----------------------------------------------------------------------------
1911 TQString KMMessage::toStrip() const
1912 {
1913  return stripEmailAddr( to() );
1914 }
1915 
1916 //-----------------------------------------------------------------------------
1917 TQString KMMessage::replyTo() const
1918 {
1919  return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("Reply-To") );
1920 }
1921 
1922 
1923 //-----------------------------------------------------------------------------
1924 void KMMessage::setReplyTo(const TQString& aStr)
1925 {
1926  setHeaderField( "Reply-To", aStr, Address );
1927 }
1928 
1929 
1930 //-----------------------------------------------------------------------------
1931 void KMMessage::setReplyTo(KMMessage* aMsg)
1932 {
1933  setHeaderField( "Reply-To", aMsg->from(), Address );
1934 }
1935 
1936 
1937 //-----------------------------------------------------------------------------
1938 TQString KMMessage::cc() const
1939 {
1940  // get the combined contents of all Cc headers (as workaround for invalid
1941  // messages with multiple Cc headers)
1942  TQValueList<TQCString> rawHeaders = rawHeaderFields( "Cc" );
1943  TQStringList headers;
1944  for ( TQValueList<TQCString>::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) {
1945  headers << *it;
1946  }
1947  return KPIM::normalizeAddressesAndDecodeIDNs( headers.join( ", " ) );
1948 }
1949 
1950 
1951 //-----------------------------------------------------------------------------
1952 void KMMessage::setCc(const TQString& aStr)
1953 {
1954  setHeaderField( "Cc", aStr, Address );
1955 }
1956 
1957 
1958 //-----------------------------------------------------------------------------
1959 TQString KMMessage::ccStrip() const
1960 {
1961  return stripEmailAddr( cc() );
1962 }
1963 
1964 
1965 //-----------------------------------------------------------------------------
1966 TQString KMMessage::bcc() const
1967 {
1968  return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("Bcc") );
1969 }
1970 
1971 
1972 //-----------------------------------------------------------------------------
1973 void KMMessage::setBcc(const TQString& aStr)
1974 {
1975  setHeaderField( "Bcc", aStr, Address );
1976 }
1977 
1978 //-----------------------------------------------------------------------------
1979 TQString KMMessage::fcc() const
1980 {
1981  return headerField( "X-KMail-Fcc" );
1982 }
1983 
1984 
1985 //-----------------------------------------------------------------------------
1986 void KMMessage::setFcc( const TQString &aStr )
1987 {
1988  setHeaderField( "X-KMail-Fcc", aStr );
1989 }
1990 
1991 //-----------------------------------------------------------------------------
1992 void KMMessage::setDrafts( const TQString &aStr )
1993 {
1994  mDrafts = aStr;
1995 }
1996 
1997 //-----------------------------------------------------------------------------
1998 void KMMessage::setTemplates( const TQString &aStr )
1999 {
2000  mTemplates = aStr;
2001 }
2002 
2003 //-----------------------------------------------------------------------------
2004 TQString KMMessage::who() const
2005 {
2006  if (mParent)
2007  return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField(mParent->whoField().utf8()) );
2008  return from();
2009 }
2010 
2011 
2012 //-----------------------------------------------------------------------------
2013 TQString KMMessage::from() const
2014 {
2015  return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("From") );
2016 }
2017 
2018 
2019 //-----------------------------------------------------------------------------
2020 void KMMessage::setFrom(const TQString& bStr)
2021 {
2022  TQString aStr = bStr;
2023  if (aStr.isNull())
2024  aStr = "";
2025  setHeaderField( "From", aStr, Address );
2026  mDirty = true;
2027 }
2028 
2029 
2030 //-----------------------------------------------------------------------------
2031 TQString KMMessage::fromStrip() const
2032 {
2033  return stripEmailAddr( from() );
2034 }
2035 
2036 //-----------------------------------------------------------------------------
2037 TQString KMMessage::sender() const {
2038  AddrSpecList asl = extractAddrSpecs( "Sender" );
2039  if ( asl.empty() )
2040  asl = extractAddrSpecs( "From" );
2041  if ( asl.empty() )
2042  return TQString();
2043  return asl.front().asString();
2044 }
2045 
2046 //-----------------------------------------------------------------------------
2047 TQString KMMessage::subject() const
2048 {
2049  return headerField("Subject");
2050 }
2051 
2052 
2053 //-----------------------------------------------------------------------------
2054 void KMMessage::setSubject(const TQString& aStr)
2055 {
2056  setHeaderField("Subject",aStr);
2057  mDirty = true;
2058 }
2059 
2060 
2061 //-----------------------------------------------------------------------------
2062 TQString KMMessage::xmark() const
2063 {
2064  return headerField("X-KMail-Mark");
2065 }
2066 
2067 
2068 //-----------------------------------------------------------------------------
2069 void KMMessage::setXMark(const TQString& aStr)
2070 {
2071  setHeaderField("X-KMail-Mark", aStr);
2072  mDirty = true;
2073 }
2074 
2075 
2076 //-----------------------------------------------------------------------------
2077 TQString KMMessage::replyToId() const
2078 {
2079  int leftAngle, rightAngle;
2080  TQString replyTo, references;
2081 
2082  replyTo = headerField("In-Reply-To");
2083  // search the end of the (first) message id in the In-Reply-To header
2084  rightAngle = replyTo.find( '>' );
2085  if (rightAngle != -1)
2086  replyTo.truncate( rightAngle + 1 );
2087  // now search the start of the message id
2088  leftAngle = replyTo.findRev( '<' );
2089  if (leftAngle != -1)
2090  replyTo = replyTo.mid( leftAngle );
2091 
2092  // if we have found a good message id we can return immediately
2093  // We ignore mangled In-Reply-To headers which are created by a
2094  // misconfigured Mutt. They look like this <"from foo"@bar.baz>, i.e.
2095  // they contain double quotes and spaces. We only check for '"'.
2096  if (!replyTo.isEmpty() && (replyTo[0] == '<') &&
2097  ( -1 == replyTo.find( '"' ) ) )
2098  return replyTo;
2099 
2100  references = headerField("References");
2101  leftAngle = references.findRev( '<' );
2102  if (leftAngle != -1)
2103  references = references.mid( leftAngle );
2104  rightAngle = references.find( '>' );
2105  if (rightAngle != -1)
2106  references.truncate( rightAngle + 1 );
2107 
2108  // if we found a good message id in the References header return it
2109  if (!references.isEmpty() && references[0] == '<')
2110  return references;
2111  // else return the broken message id we found in the In-Reply-To header
2112  else
2113  return replyTo;
2114 }
2115 
2116 
2117 //-----------------------------------------------------------------------------
2118 TQString KMMessage::replyToIdMD5() const {
2119  return base64EncodedMD5( replyToId() );
2120 }
2121 
2122 //-----------------------------------------------------------------------------
2123 TQString KMMessage::references() const
2124 {
2125  int leftAngle, rightAngle;
2126  TQString references = headerField( "References" );
2127 
2128  // keep the last two entries for threading
2129  leftAngle = references.findRev( '<' );
2130  leftAngle = references.findRev( '<', leftAngle - 1 );
2131  if( leftAngle != -1 )
2132  references = references.mid( leftAngle );
2133  rightAngle = references.findRev( '>' );
2134  if( rightAngle != -1 )
2135  references.truncate( rightAngle + 1 );
2136 
2137  if( !references.isEmpty() && references[0] == '<' )
2138  return references;
2139  else
2140  return TQString();
2141 }
2142 
2143 //-----------------------------------------------------------------------------
2145 {
2146  TQString result = references();
2147  // references contains two items, use the first one
2148  // (the second to last reference)
2149  const int rightAngle = result.find( '>' );
2150  if( rightAngle != -1 )
2151  result.truncate( rightAngle + 1 );
2152 
2153  return base64EncodedMD5( result );
2154 }
2155 
2156 //-----------------------------------------------------------------------------
2158  return base64EncodedMD5( stripOffPrefixes( subject() ), true /*utf8*/ );
2159 }
2160 
2161 //-----------------------------------------------------------------------------
2162 TQString KMMessage::subjectMD5() const {
2163  return base64EncodedMD5( subject(), true /*utf8*/ );
2164 }
2165 
2166 //-----------------------------------------------------------------------------
2168  return subjectMD5() != strippedSubjectMD5();
2169 }
2170 
2171 //-----------------------------------------------------------------------------
2172 void KMMessage::setReplyToId(const TQString& aStr)
2173 {
2174  setHeaderField("In-Reply-To", aStr);
2175  mDirty = true;
2176 }
2177 
2178 
2179 //-----------------------------------------------------------------------------
2180 TQString KMMessage::msgId() const
2181 {
2182  TQString msgId = headerField("Message-Id");
2183 
2184  // search the end of the message id
2185  const int rightAngle = msgId.find( '>' );
2186  if (rightAngle != -1)
2187  msgId.truncate( rightAngle + 1 );
2188  // now search the start of the message id
2189  const int leftAngle = msgId.findRev( '<' );
2190  if (leftAngle != -1)
2191  msgId = msgId.mid( leftAngle );
2192  return msgId;
2193 }
2194 
2195 
2196 //-----------------------------------------------------------------------------
2197 TQString KMMessage::msgIdMD5() const {
2198  return base64EncodedMD5( msgId() );
2199 }
2200 
2201 
2202 //-----------------------------------------------------------------------------
2203 void KMMessage::setMsgId(const TQString& aStr)
2204 {
2205  setHeaderField("Message-Id", aStr);
2206  mDirty = true;
2207 }
2208 
2209 //-----------------------------------------------------------------------------
2211  return headerField( "X-Length" ).toULong();
2212 }
2213 
2214 
2215 //-----------------------------------------------------------------------------
2216 void KMMessage::setMsgSizeServer(size_t size)
2217 {
2218  setHeaderField("X-Length", TQCString().setNum(size));
2219  mDirty = true;
2220 }
2221 
2222 //-----------------------------------------------------------------------------
2223 ulong KMMessage::UID() const {
2224  return headerField( "X-UID" ).toULong();
2225 }
2226 
2227 
2228 //-----------------------------------------------------------------------------
2229 void KMMessage::setUID(ulong uid)
2230 {
2231  setHeaderField("X-UID", TQCString().setNum(uid));
2232  mDirty = true;
2233 }
2234 
2235 //-----------------------------------------------------------------------------
2236 AddressList KMMessage::splitAddrField( const TQCString & str )
2237 {
2238  AddressList result;
2239  const char * scursor = str.begin();
2240  if ( !scursor )
2241  return AddressList();
2242  const char * const send = str.begin() + str.length();
2243  if ( !parseAddressList( scursor, send, result ) )
2244  kdDebug(5006) << "Error in address splitting: parseAddressList returned false!"
2245  << endl;
2246  return result;
2247 }
2248 
2249 AddressList KMMessage::headerAddrField( const TQCString & aName ) const {
2250  return KMMessage::splitAddrField( rawHeaderField( aName ) );
2251 }
2252 
2253 AddrSpecList KMMessage::extractAddrSpecs( const TQCString & header ) const {
2254  AddressList al = headerAddrField( header );
2255  AddrSpecList result;
2256  for ( AddressList::const_iterator ait = al.begin() ; ait != al.end() ; ++ait )
2257  for ( MailboxList::const_iterator mit = (*ait).mailboxList.begin() ; mit != (*ait).mailboxList.end() ; ++mit )
2258  result.push_back( (*mit).addrSpec );
2259  return result;
2260 }
2261 
2262 TQCString KMMessage::rawHeaderField( const TQCString & name ) const {
2263  if ( name.isEmpty() ) return TQCString();
2264 
2265  DwHeaders & header = mMsg->Headers();
2266  DwField * field = header.FindField( name );
2267 
2268  if ( !field ) return TQCString();
2269 
2270  return header.FieldBody( name.data() ).AsString().c_str();
2271 }
2272 
2273 TQValueList<TQCString> KMMessage::rawHeaderFields( const TQCString& field ) const
2274 {
2275  if ( field.isEmpty() || !mMsg->Headers().FindField( field ) )
2276  return TQValueList<TQCString>();
2277 
2278  std::vector<DwFieldBody*> v = mMsg->Headers().AllFieldBodies( field.data() );
2279  TQValueList<TQCString> headerFields;
2280  for ( uint i = 0; i < v.size(); ++i ) {
2281  headerFields.append( v[i]->AsString().c_str() );
2282  }
2283 
2284  return headerFields;
2285 }
2286 
2287 TQString KMMessage::headerField(const TQCString& aName) const
2288 {
2289  if ( aName.isEmpty() )
2290  return TQString();
2291 
2292  if ( !mMsg->Headers().FindField( aName ) )
2293  return TQString();
2294 
2295  return decodeRFC2047String( mMsg->Headers().FieldBody( aName.data() ).AsString().c_str(),
2296  charset() );
2297 
2298 }
2299 
2300 TQStringList KMMessage::headerFields( const TQCString& field ) const
2301 {
2302  if ( field.isEmpty() || !mMsg->Headers().FindField( field ) )
2303  return TQStringList();
2304 
2305  std::vector<DwFieldBody*> v = mMsg->Headers().AllFieldBodies( field.data() );
2306  TQStringList headerFields;
2307  for ( uint i = 0; i < v.size(); ++i ) {
2308  headerFields.append( decodeRFC2047String( v[i]->AsString().c_str(), charset() ) );
2309  }
2310 
2311  return headerFields;
2312 }
2313 
2314 //-----------------------------------------------------------------------------
2315 void KMMessage::removeHeaderField(const TQCString& aName)
2316 {
2317  DwHeaders & header = mMsg->Headers();
2318  DwField * field = header.FindField(aName);
2319  if (!field) return;
2320 
2321  header.RemoveField(field);
2322  mNeedsAssembly = true;
2323 }
2324 
2325 //-----------------------------------------------------------------------------
2326 void KMMessage::removeHeaderFields(const TQCString& aName)
2327 {
2328  DwHeaders & header = mMsg->Headers();
2329  while ( DwField * field = header.FindField(aName) ) {
2330  header.RemoveField(field);
2331  mNeedsAssembly = true;
2332  }
2333 }
2334 
2335 
2336 //-----------------------------------------------------------------------------
2337 void KMMessage::setHeaderField( const TQCString& aName, const TQString& bValue,
2338  HeaderFieldType type, bool prepend )
2339 {
2340 #if 0
2341  if ( type != Unstructured )
2342  kdDebug(5006) << "KMMessage::setHeaderField( \"" << aName << "\", \""
2343  << bValue << "\", " << type << " )" << endl;
2344 #endif
2345  if (aName.isEmpty()) return;
2346 
2347  DwHeaders& header = mMsg->Headers();
2348 
2349  DwString str;
2350  DwField* field;
2351  TQCString aValue;
2352  if (!bValue.isEmpty())
2353  {
2354  TQString value = bValue;
2355  if ( type == Address )
2356  value = KPIM::normalizeAddressesAndEncodeIDNs( value );
2357 #if 0
2358  if ( type != Unstructured )
2359  kdDebug(5006) << "value: \"" << value << "\"" << endl;
2360 #endif
2361  TQCString encoding = autoDetectCharset( charset(), sPrefCharsets, value );
2362  if (encoding.isEmpty())
2363  encoding = "utf-8";
2364  aValue = encodeRFC2047String( value, encoding );
2365 #if 0
2366  if ( type != Unstructured )
2367  kdDebug(5006) << "aValue: \"" << aValue << "\"" << endl;
2368 #endif
2369  }
2370  str = aName.data();
2371  if (str[str.length()-1] != ':') str += ": ";
2372  else str += ' ';
2373  if ( !aValue.isEmpty() )
2374  str += aValue.data();
2375  if (str[str.length()-1] != '\n') str += '\n';
2376 
2377  field = new DwField(str, mMsg);
2378  field->Parse();
2379 
2380  if ( prepend )
2381  header.AddFieldAt( 1, field );
2382  else
2383  header.AddOrReplaceField( field );
2384  mNeedsAssembly = true;
2385 }
2386 
2387 
2388 //-----------------------------------------------------------------------------
2389 TQCString KMMessage::typeStr() const
2390 {
2391  DwHeaders& header = mMsg->Headers();
2392  if (header.HasContentType()) return header.ContentType().TypeStr().c_str();
2393  else return "";
2394 }
2395 
2396 
2397 //-----------------------------------------------------------------------------
2398 int KMMessage::type() const
2399 {
2400  DwHeaders& header = mMsg->Headers();
2401  if (header.HasContentType()) return header.ContentType().Type();
2402  else return DwMime::kTypeNull;
2403 }
2404 
2405 
2406 //-----------------------------------------------------------------------------
2407 void KMMessage::setTypeStr(const TQCString& aStr)
2408 {
2409  dwContentType().SetTypeStr(DwString(aStr));
2410  dwContentType().Parse();
2411  mNeedsAssembly = true;
2412 }
2413 
2414 
2415 //-----------------------------------------------------------------------------
2416 void KMMessage::setType(int aType)
2417 {
2418  dwContentType().SetType(aType);
2419  dwContentType().Assemble();
2420  mNeedsAssembly = true;
2421 }
2422 
2423 
2424 
2425 //-----------------------------------------------------------------------------
2426 TQCString KMMessage::subtypeStr() const
2427 {
2428  DwHeaders& header = mMsg->Headers();
2429  if (header.HasContentType()) return header.ContentType().SubtypeStr().c_str();
2430  else return "";
2431 }
2432 
2433 
2434 //-----------------------------------------------------------------------------
2435 int KMMessage::subtype() const
2436 {
2437  DwHeaders& header = mMsg->Headers();
2438  if (header.HasContentType()) return header.ContentType().Subtype();
2439  else return DwMime::kSubtypeNull;
2440 }
2441 
2442 
2443 //-----------------------------------------------------------------------------
2444 void KMMessage::setSubtypeStr(const TQCString& aStr)
2445 {
2446  dwContentType().SetSubtypeStr(DwString(aStr));
2447  dwContentType().Parse();
2448  mNeedsAssembly = true;
2449 }
2450 
2451 
2452 //-----------------------------------------------------------------------------
2453 void KMMessage::setSubtype(int aSubtype)
2454 {
2455  dwContentType().SetSubtype(aSubtype);
2456  dwContentType().Assemble();
2457  mNeedsAssembly = true;
2458 }
2459 
2460 
2461 //-----------------------------------------------------------------------------
2462 void KMMessage::setDwMediaTypeParam( DwMediaType &mType,
2463  const TQCString& attr,
2464  const TQCString& val )
2465 {
2466  mType.Parse();
2467  DwParameter *param = mType.FirstParameter();
2468  while(param) {
2469  if (!kasciistricmp(param->Attribute().c_str(), attr))
2470  break;
2471  else
2472  param = param->Next();
2473  }
2474  if (!param){
2475  param = new DwParameter;
2476  param->SetAttribute(DwString( attr ));
2477  mType.AddParameter( param );
2478  }
2479  else
2480  mType.SetModified();
2481  param->SetValue(DwString( val ));
2482  mType.Assemble();
2483 }
2484 
2485 
2486 //-----------------------------------------------------------------------------
2487 void KMMessage::setContentTypeParam(const TQCString& attr, const TQCString& val)
2488 {
2489  if (mNeedsAssembly) mMsg->Assemble();
2490  mNeedsAssembly = false;
2491  setDwMediaTypeParam( dwContentType(), attr, val );
2492  mNeedsAssembly = true;
2493 }
2494 
2495 
2496 //-----------------------------------------------------------------------------
2498 {
2499  DwHeaders& header = mMsg->Headers();
2500  if (header.HasContentTransferEncoding())
2501  return header.ContentTransferEncoding().AsString().c_str();
2502  else return "";
2503 }
2504 
2505 
2506 //-----------------------------------------------------------------------------
2507 int KMMessage::contentTransferEncoding( DwEntity *entity ) const
2508 {
2509  if ( !entity )
2510  entity = mMsg;
2511 
2512  DwHeaders& header = entity->Headers();
2513  if ( header.HasContentTransferEncoding() )
2514  return header.ContentTransferEncoding().AsEnum();
2515  else return DwMime::kCteNull;
2516 }
2517 
2518 
2519 //-----------------------------------------------------------------------------
2520 void KMMessage::setContentTransferEncodingStr( const TQCString& cteString,
2521  DwEntity *entity )
2522 {
2523  if ( !entity )
2524  entity = mMsg;
2525 
2526  entity->Headers().ContentTransferEncoding().FromString( cteString );
2527  entity->Headers().ContentTransferEncoding().Parse();
2528  mNeedsAssembly = true;
2529 }
2530 
2531 
2532 //-----------------------------------------------------------------------------
2533 void KMMessage::setContentTransferEncoding( int cte, DwEntity *entity )
2534 {
2535  if ( !entity )
2536  entity = mMsg;
2537 
2538  entity->Headers().ContentTransferEncoding().FromEnum( cte );
2539  mNeedsAssembly = true;
2540 }
2541 
2542 
2543 //-----------------------------------------------------------------------------
2544 DwHeaders& KMMessage::headers() const
2545 {
2546  return mMsg->Headers();
2547 }
2548 
2549 
2550 //-----------------------------------------------------------------------------
2552 {
2553  mNeedsAssembly = true;
2554 }
2555 
2556 //-----------------------------------------------------------------------------
2558 {
2559  Q_ASSERT( mMsg );
2560 
2561  if ( mNeedsAssembly ) {
2562  mMsg->Assemble();
2563  mNeedsAssembly = false;
2564  }
2565 }
2566 
2567 //-----------------------------------------------------------------------------
2568 TQCString KMMessage::body() const
2569 {
2570  const DwString& body = mMsg->Body().AsString();
2571  TQCString str = KMail::Util::CString( body );
2572  // Calls length() -> slow
2573  //kdWarning( str.length() != body.length(), 5006 )
2574  // << "KMMessage::body(): body is binary but used as text!" << endl;
2575  return str;
2576 }
2577 
2578 
2579 //-----------------------------------------------------------------------------
2580 TQByteArray KMMessage::bodyDecodedBinary() const
2581 {
2582  DwString dwstr;
2583  const DwString& dwsrc = mMsg->Body().AsString();
2584 
2585  switch (cte())
2586  {
2587  case DwMime::kCteBase64:
2588  DwDecodeBase64(dwsrc, dwstr);
2589  break;
2590  case DwMime::kCteQuotedPrintable:
2591  DwDecodeQuotedPrintable(dwsrc, dwstr);
2592  break;
2593  default:
2594  dwstr = dwsrc;
2595  break;
2596  }
2597 
2598  int len = dwstr.size();
2599  TQByteArray ba(len);
2600  memcpy(ba.data(),dwstr.data(),len);
2601  return ba;
2602 }
2603 
2604 
2605 //-----------------------------------------------------------------------------
2606 TQCString KMMessage::bodyDecoded() const
2607 {
2608  DwString dwstr;
2609  DwString dwsrc = mMsg->Body().AsString();
2610 
2611  switch (cte())
2612  {
2613  case DwMime::kCteBase64:
2614  DwDecodeBase64(dwsrc, dwstr);
2615  break;
2616  case DwMime::kCteQuotedPrintable:
2617  DwDecodeQuotedPrintable(dwsrc, dwstr);
2618  break;
2619  default:
2620  dwstr = dwsrc;
2621  break;
2622  }
2623 
2624  return KMail::Util::CString( dwstr );
2625 
2626  // Calling TQCString::length() is slow
2627  //TQCString result = KMail::Util::CString( dwstr );
2628  //kdWarning(result.length() != len, 5006)
2629  // << "KMMessage::bodyDecoded(): body is binary but used as text!" << endl;
2630  //return result;
2631 }
2632 
2633 
2634 //-----------------------------------------------------------------------------
2635 TQValueList<int> KMMessage::determineAllowedCtes( const CharFreq& cf,
2636  bool allow8Bit,
2637  bool willBeSigned )
2638 {
2639  TQValueList<int> allowedCtes;
2640 
2641  switch ( cf.type() ) {
2642  case CharFreq::SevenBitText:
2643  allowedCtes << DwMime::kCte7bit;
2644  case CharFreq::EightBitText:
2645  if ( allow8Bit )
2646  allowedCtes << DwMime::kCte8bit;
2647  case CharFreq::SevenBitData:
2648  if ( cf.printableRatio() > 5.0/6.0 ) {
2649  // let n the length of data and p the number of printable chars.
2650  // Then base64 \approx 4n/3; qp \approx p + 3(n-p)
2651  // => qp < base64 iff p > 5n/6.
2652  allowedCtes << DwMime::kCteQp;
2653  allowedCtes << DwMime::kCteBase64;
2654  } else {
2655  allowedCtes << DwMime::kCteBase64;
2656  allowedCtes << DwMime::kCteQp;
2657  }
2658  break;
2659  case CharFreq::EightBitData:
2660  allowedCtes << DwMime::kCteBase64;
2661  break;
2662  case CharFreq::None:
2663  default:
2664  // just nothing (avoid compiler warning)
2665  ;
2666  }
2667 
2668  // In the following cases only QP and Base64 are allowed:
2669  // - the buffer will be OpenPGP/MIME signed and it contains trailing
2670  // whitespace (cf. RFC 3156)
2671  // - a line starts with "From "
2672  if ( ( willBeSigned && cf.hasTrailingWhitespace() ) ||
2673  cf.hasLeadingFrom() ) {
2674  allowedCtes.remove( DwMime::kCte8bit );
2675  allowedCtes.remove( DwMime::kCte7bit );
2676  }
2677 
2678  return allowedCtes;
2679 }
2680 
2681 
2682 //-----------------------------------------------------------------------------
2683 void KMMessage::setBodyAndGuessCte( const TQByteArray& aBuf,
2684  TQValueList<int> & allowedCte,
2685  bool allow8Bit,
2686  bool willBeSigned,
2687  DwEntity *entity )
2688 {
2689  if ( !entity )
2690  entity = mMsg;
2691 
2692  CharFreq cf( aBuf ); // it's safe to pass null arrays
2693  allowedCte = determineAllowedCtes( cf, allow8Bit, willBeSigned );
2694  setCte( allowedCte[0], entity ); // choose best fitting
2695  setBodyEncodedBinary( aBuf, entity );
2696 }
2697 
2698 
2699 //-----------------------------------------------------------------------------
2700 void KMMessage::setBodyAndGuessCte( const TQCString& aBuf,
2701  TQValueList<int> & allowedCte,
2702  bool allow8Bit,
2703  bool willBeSigned,
2704  DwEntity *entity )
2705 {
2706  if ( !entity )
2707  entity = mMsg;
2708 
2709  CharFreq cf( aBuf.data(), aBuf.size()-1 ); // it's safe to pass null strings
2710  allowedCte = determineAllowedCtes( cf, allow8Bit, willBeSigned );
2711  setCte( allowedCte[0], entity ); // choose best fitting
2712  setBodyEncoded( aBuf, entity );
2713 }
2714 
2715 
2716 //-----------------------------------------------------------------------------
2717 void KMMessage::setBodyEncoded(const TQCString& aStr, DwEntity *entity )
2718 {
2719  if ( !entity )
2720  entity = mMsg;
2721 
2722  DwString dwSrc(aStr.data(), aStr.size()-1 /* not the trailing NUL */);
2723  DwString dwResult;
2724 
2725  switch (cte( entity ))
2726  {
2727  case DwMime::kCteBase64:
2728  DwEncodeBase64(dwSrc, dwResult);
2729  break;
2730  case DwMime::kCteQuotedPrintable:
2731  DwEncodeQuotedPrintable(dwSrc, dwResult);
2732  break;
2733  default:
2734  dwResult = dwSrc;
2735  break;
2736  }
2737 
2738  entity->Body().FromString(dwResult);
2739  mNeedsAssembly = true;
2740 }
2741 
2742 //-----------------------------------------------------------------------------
2743 void KMMessage::setBodyEncodedBinary( const TQByteArray& aStr, DwEntity *entity )
2744 {
2745  if ( !entity )
2746  entity = mMsg;
2747 
2748  DwString dwSrc(aStr.data(), aStr.size());
2749  DwString dwResult;
2750 
2751  switch ( cte( entity ) )
2752  {
2753  case DwMime::kCteBase64:
2754  DwEncodeBase64( dwSrc, dwResult );
2755  break;
2756  case DwMime::kCteQuotedPrintable:
2757  DwEncodeQuotedPrintable( dwSrc, dwResult );
2758  break;
2759  default:
2760  dwResult = dwSrc;
2761  break;
2762  }
2763 
2764  entity->Body().FromString( dwResult );
2765  entity->Body().Parse();
2766 
2767  mNeedsAssembly = true;
2768 }
2769 
2770 
2771 //-----------------------------------------------------------------------------
2772 void KMMessage::setBody(const TQCString& aStr)
2773 {
2774  mMsg->Body().FromString(KMail::Util::dwString(aStr));
2775  mNeedsAssembly = true;
2776 }
2777 void KMMessage::setBody(const DwString& aStr)
2778 {
2779  mMsg->Body().FromString(aStr);
2780  mNeedsAssembly = true;
2781 }
2782 void KMMessage::setBody(const char* aStr)
2783 {
2784  mMsg->Body().FromString(aStr);
2785  mNeedsAssembly = true;
2786 }
2787 
2788 //-----------------------------------------------------------------------------
2789 void KMMessage::setMultiPartBody( const TQCString & aStr ) {
2790  setBody( aStr );
2791  mMsg->Body().Parse();
2792  mNeedsAssembly = true;
2793 }
2794 
2795 
2796 // Patched by Daniel Moisset <dmoisset@grulic.org.ar>
2797 // modified numbodyparts, bodypart to take nested body parts as
2798 // a linear sequence.
2799 // third revision, Sep 26 2000
2800 
2801 // this is support structure for traversing tree without recursion
2802 
2803 //-----------------------------------------------------------------------------
2805 {
2806  int count = 0;
2807  DwBodyPart* part = getFirstDwBodyPart();
2808  TQPtrList< DwBodyPart > parts;
2809 
2810  while (part)
2811  {
2812  //dive into multipart messages
2813  while ( part
2814  && part->hasHeaders()
2815  && part->Headers().HasContentType()
2816  && part->Body().FirstBodyPart()
2817  && (DwMime::kTypeMultipart == part->Headers().ContentType().Type()) )
2818  {
2819  parts.append( part );
2820  part = part->Body().FirstBodyPart();
2821  }
2822  // this is where currPart->msgPart contains a leaf message part
2823  count++;
2824  // go up in the tree until reaching a node with next
2825  // (or the last top-level node)
2826  while (part && !(part->Next()) && !(parts.isEmpty()))
2827  {
2828  part = parts.getLast();
2829  parts.removeLast();
2830  }
2831 
2832  if (part && part->Body().Message() &&
2833  part->Body().Message()->Body().FirstBodyPart())
2834  {
2835  part = part->Body().Message()->Body().FirstBodyPart();
2836  } else if (part) {
2837  part = part->Next();
2838  }
2839  }
2840 
2841  return count;
2842 }
2843 
2844 
2845 //-----------------------------------------------------------------------------
2846 DwBodyPart * KMMessage::getFirstDwBodyPart() const
2847 {
2848  return mMsg->Body().FirstBodyPart();
2849 }
2850 
2851 
2852 //-----------------------------------------------------------------------------
2853 int KMMessage::partNumber( DwBodyPart * aDwBodyPart ) const
2854 {
2855  DwBodyPart *curpart;
2856  TQPtrList< DwBodyPart > parts;
2857  int curIdx = 0;
2858  int idx = 0;
2859  // Get the DwBodyPart for this index
2860 
2861  curpart = getFirstDwBodyPart();
2862 
2863  while (curpart && !idx) {
2864  //dive into multipart messages
2865  while( curpart
2866  && curpart->hasHeaders()
2867  && curpart->Headers().HasContentType()
2868  && curpart->Body().FirstBodyPart()
2869  && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) )
2870  {
2871  parts.append( curpart );
2872  curpart = curpart->Body().FirstBodyPart();
2873  }
2874  // this is where currPart->msgPart contains a leaf message part
2875  if (curpart == aDwBodyPart)
2876  idx = curIdx;
2877  curIdx++;
2878  // go up in the tree until reaching a node with next
2879  // (or the last top-level node)
2880  while (curpart && !(curpart->Next()) && !(parts.isEmpty()))
2881  {
2882  curpart = parts.getLast();
2883  parts.removeLast();
2884  } ;
2885  if (curpart)
2886  curpart = curpart->Next();
2887  }
2888  return idx;
2889 }
2890 
2891 
2892 //-----------------------------------------------------------------------------
2893 DwBodyPart * KMMessage::dwBodyPart( int aIdx ) const
2894 {
2895  DwBodyPart *part, *curpart;
2896  TQPtrList< DwBodyPart > parts;
2897  int curIdx = 0;
2898  // Get the DwBodyPart for this index
2899 
2900  curpart = getFirstDwBodyPart();
2901  part = 0;
2902 
2903  while (curpart && !part) {
2904  //dive into multipart messages
2905  while( curpart
2906  && curpart->hasHeaders()
2907  && curpart->Headers().HasContentType()
2908  && curpart->Body().FirstBodyPart()
2909  && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) )
2910  {
2911  parts.append( curpart );
2912  curpart = curpart->Body().FirstBodyPart();
2913  }
2914  // this is where currPart->msgPart contains a leaf message part
2915  if (curIdx==aIdx)
2916  part = curpart;
2917  curIdx++;
2918  // go up in the tree until reaching a node with next
2919  // (or the last top-level node)
2920  while (curpart && !(curpart->Next()) && !(parts.isEmpty()))
2921  {
2922  curpart = parts.getLast();
2923  parts.removeLast();
2924  }
2925  if (curpart)
2926  curpart = curpart->Next();
2927  }
2928  return part;
2929 }
2930 
2931 
2932 //-----------------------------------------------------------------------------
2933 DwBodyPart * KMMessage::findDwBodyPart( int type, int subtype ) const
2934 {
2935  DwBodyPart *part, *curpart;
2936  TQPtrList< DwBodyPart > parts;
2937  // Get the DwBodyPart for this index
2938 
2939  curpart = getFirstDwBodyPart();
2940  part = 0;
2941 
2942  while (curpart && !part) {
2943  //dive into multipart messages
2944  while(curpart
2945  && curpart->hasHeaders()
2946  && curpart->Headers().HasContentType()
2947  && curpart->Body().FirstBodyPart()
2948  && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) {
2949  parts.append( curpart );
2950  curpart = curpart->Body().FirstBodyPart();
2951  }
2952  // this is where curPart->msgPart contains a leaf message part
2953 
2954  // pending(khz): Find out WHY this look does not travel down *into* an
2955  // embedded "Message/RfF822" message containing a "Multipart/Mixed"
2956  if ( curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() ) {
2957  kdDebug(5006) << curpart->Headers().ContentType().TypeStr().c_str()
2958  << " " << curpart->Headers().ContentType().SubtypeStr().c_str() << endl;
2959  }
2960 
2961  if (curpart &&
2962  curpart->hasHeaders() &&
2963  curpart->Headers().HasContentType() &&
2964  curpart->Headers().ContentType().Type() == type &&
2965  curpart->Headers().ContentType().Subtype() == subtype) {
2966  part = curpart;
2967  } else {
2968  // go up in the tree until reaching a node with next
2969  // (or the last top-level node)
2970  while (curpart && !(curpart->Next()) && !(parts.isEmpty())) {
2971  curpart = parts.getLast();
2972  parts.removeLast();
2973  } ;
2974  if (curpart)
2975  curpart = curpart->Next();
2976  }
2977  }
2978  return part;
2979 }
2980 
2981 //-----------------------------------------------------------------------------
2982 DwBodyPart * KMMessage::findDwBodyPart( const TQCString& type, const TQCString& subtype ) const
2983 {
2984  DwBodyPart *part, *curpart;
2985  TQPtrList< DwBodyPart > parts;
2986  // Get the DwBodyPart for this index
2987 
2988  curpart = getFirstDwBodyPart();
2989  part = 0;
2990 
2991  while (curpart && !part) {
2992  //dive into multipart messages
2993  while(curpart
2994  && curpart->hasHeaders()
2995  && curpart->Headers().HasContentType()
2996  && curpart->Body().FirstBodyPart()
2997  && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) {
2998  parts.append( curpart );
2999  curpart = curpart->Body().FirstBodyPart();
3000  }
3001  // this is where curPart->msgPart contains a leaf message part
3002 
3003  // pending(khz): Find out WHY this look does not travel down *into* an
3004  // embedded "Message/RfF822" message containing a "Multipart/Mixed"
3005  if (curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() ) {
3006  kdDebug(5006) << curpart->Headers().ContentType().TypeStr().c_str()
3007  << " " << curpart->Headers().ContentType().SubtypeStr().c_str() << endl;
3008  }
3009 
3010  if (curpart &&
3011  curpart->hasHeaders() &&
3012  curpart->Headers().HasContentType() &&
3013  curpart->Headers().ContentType().TypeStr().c_str() == type &&
3014  curpart->Headers().ContentType().SubtypeStr().c_str() == subtype) {
3015  part = curpart;
3016  } else {
3017  // go up in the tree until reaching a node with next
3018  // (or the last top-level node)
3019  while (curpart && !(curpart->Next()) && !(parts.isEmpty())) {
3020  curpart = parts.getLast();
3021  parts.removeLast();
3022  } ;
3023  if (curpart)
3024  curpart = curpart->Next();
3025  }
3026  }
3027  return part;
3028 }
3029 
3030 
3031 void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart )
3032 {
3033  // TODO: Instead of manually implementing RFC2231 header encoding (i.e.
3034  // possibly multiple values given as paramname*0=..; parmaname*1=..;...
3035  // or par as paramname*0*=..; parmaname*1*=..;..., which should be
3036  // concatenated), use a generic method to decode the header, using RFC
3037  // 2047 or 2231, or whatever future RFC might be appropriate!
3038  // Right now, some fields are decoded, while others are not. E.g.
3039  // Content-Disposition is not decoded here, rather only on demand in
3040  // KMMsgPart::fileName; Name however is decoded here and stored as a
3041  // decoded String in KMMsgPart...
3042  // Content-type
3043  TQCString additionalCTypeParams;
3044  if (headers.HasContentType())
3045  {
3046  DwMediaType& ct = headers.ContentType();
3047  aPart->setOriginalContentTypeStr( ct.AsString().c_str() );
3048  aPart->setTypeStr(ct.TypeStr().c_str());
3049  aPart->setSubtypeStr(ct.SubtypeStr().c_str());
3050  DwParameter *param = ct.FirstParameter();
3051  while(param)
3052  {
3053  if (!qstricmp(param->Attribute().c_str(), "charset"))
3054  aPart->setCharset(TQCString(param->Value().c_str()).lower());
3055  else if (!qstrnicmp(param->Attribute().c_str(), "name*", 5))
3056  aPart->setName(KMMsgBase::decodeRFC2231String(KMMsgBase::extractRFC2231HeaderField( param->Value().c_str(), "name" )));
3057  else {
3058  additionalCTypeParams += ';';
3059  additionalCTypeParams += param->AsString().c_str();
3060  }
3061  param=param->Next();
3062  }
3063  }
3064  else
3065  {
3066  aPart->setTypeStr("text"); // Set to defaults
3067  aPart->setSubtypeStr("plain");
3068  }
3069  aPart->setAdditionalCTypeParamStr( additionalCTypeParams );
3070  // Modification by Markus
3071  if (aPart->name().isEmpty())
3072  {
3073  if (headers.HasContentType() && !headers.ContentType().Name().empty()) {
3074  aPart->setName(KMMsgBase::decodeRFC2047String(headers.
3075  ContentType().Name().c_str()) );
3076  } else if (headers.HasSubject() && !headers.Subject().AsString().empty()) {
3077  aPart->setName( KMMsgBase::decodeRFC2047String(headers.
3078  Subject().AsString().c_str()) );
3079  }
3080  }
3081 
3082  // Content-transfer-encoding
3083  if (headers.HasContentTransferEncoding())
3084  aPart->setCteStr(headers.ContentTransferEncoding().AsString().c_str());
3085  else
3086  aPart->setCteStr("7bit");
3087 
3088  // Content-description
3089  if (headers.HasContentDescription())
3090  aPart->setContentDescription( KMMsgBase::decodeRFC2047String(
3091  headers.ContentDescription().AsString().c_str() ) );
3092  else
3093  aPart->setContentDescription("");
3094 
3095  // Content-disposition
3096  if (headers.HasContentDisposition())
3097  aPart->setContentDisposition(headers.ContentDisposition().AsString().c_str());
3098  else
3099  aPart->setContentDisposition("");
3100 }
3101 
3102 //-----------------------------------------------------------------------------
3103 void KMMessage::bodyPart(DwBodyPart* aDwBodyPart, KMMessagePart* aPart,
3104  bool withBody)
3105 {
3106  if ( !aPart )
3107  return;
3108 
3109  aPart->clear();
3110 
3111  if( aDwBodyPart && aDwBodyPart->hasHeaders() ) {
3112  // This must not be an empty string, because we'll get a
3113  // spurious empty Subject: line in some of the parts.
3114  //aPart->setName(" ");
3115  // partSpecifier
3116  TQString partId( aDwBodyPart->partId() );
3117  aPart->setPartSpecifier( partId );
3118 
3119  DwHeaders& headers = aDwBodyPart->Headers();
3120  applyHeadersToMessagePart( headers, aPart );
3121 
3122  // Body
3123  if (withBody)
3124  aPart->setBody( aDwBodyPart->Body().AsString() );
3125  else
3126  aPart->setBody( TQCString("") );
3127 
3128  // Content-id
3129  if ( headers.HasContentId() ) {
3130  const TQCString contentId = headers.ContentId().AsString().c_str();
3131  // ignore leading '<' and trailing '>'
3132  aPart->setContentId( contentId.mid( 1, contentId.length() - 2 ) );
3133  }
3134  }
3135  // If no valid body part was given,
3136  // set all MultipartBodyPart attributes to empty values.
3137  else
3138  {
3139  aPart->setTypeStr("");
3140  aPart->setSubtypeStr("");
3141  aPart->setCteStr("");
3142  // This must not be an empty string, because we'll get a
3143  // spurious empty Subject: line in some of the parts.
3144  //aPart->setName(" ");
3145  aPart->setContentDescription("");
3146  aPart->setContentDisposition("");
3147  aPart->setBody(TQCString(""));
3148  aPart->setContentId("");
3149  }
3150 }
3151 
3152 
3153 //-----------------------------------------------------------------------------
3154 void KMMessage::bodyPart(int aIdx, KMMessagePart* aPart) const
3155 {
3156  if ( !aPart )
3157  return;
3158 
3159  // If the DwBodyPart was found get the header fields and body
3160  if ( DwBodyPart *part = dwBodyPart( aIdx ) ) {
3161  KMMessage::bodyPart(part, aPart);
3162  if( aPart->name().isEmpty() )
3163  aPart->setName( i18n("Attachment: %1").arg( aIdx ) );
3164  }
3165 }
3166 
3167 
3168 //-----------------------------------------------------------------------------
3170 {
3171  mMsg->Body().DeleteBodyParts();
3172 }
3173 
3174 //-----------------------------------------------------------------------------
3175 
3176 bool KMMessage::deleteBodyPart( int partIndex )
3177 {
3178  KMMessagePart part;
3179  DwBodyPart *dwpart = findPart( partIndex );
3180  if ( !dwpart )
3181  return false;
3182  KMMessage::bodyPart( dwpart, &part, true );
3183  if ( !part.isComplete() )
3184  return false;
3185 
3186  DwBody *parentNode = dynamic_cast<DwBody*>( dwpart->Parent() );
3187  if ( !parentNode )
3188  return false;
3189  parentNode->RemoveBodyPart( dwpart );
3190 
3191  // add dummy part to show that a attachment has been deleted
3192  KMMessagePart dummyPart;
3193  dummyPart.duplicate( part );
3194  TQString comment = i18n("This attachment has been deleted.");
3195  if ( !part.fileName().isEmpty() )
3196  comment = i18n( "The attachment '%1' has been deleted." ).arg( part.fileName() );
3197  dummyPart.setContentDescription( comment );
3198  dummyPart.setBodyEncodedBinary( TQByteArray() );
3199  TQCString cd = dummyPart.contentDisposition();
3200  if ( cd.find( "inline", 0, false ) == 0 ) {
3201  cd.replace( 0, 10, "attachment" );
3202  dummyPart.setContentDisposition( cd );
3203  } else if ( cd.isEmpty() ) {
3204  dummyPart.setContentDisposition( "attachment" );
3205  }
3206  DwBodyPart* newDwPart = createDWBodyPart( &dummyPart );
3207  parentNode->AddBodyPart( newDwPart );
3208  getTopLevelPart()->Assemble();
3209  return true;
3210 }
3211 
3212 //-----------------------------------------------------------------------------
3213 DwBodyPart* KMMessage::createDWBodyPart(const KMMessagePart* aPart)
3214 {
3215  DwBodyPart* part = DwBodyPart::NewBodyPart(emptyString, 0);
3216 
3217  if ( !aPart )
3218  return part;
3219 
3220  TQCString charset = aPart->charset();
3221  TQCString type = aPart->typeStr();
3222  TQCString subtype = aPart->subtypeStr();
3223  TQCString cte = aPart->cteStr();
3224  TQCString contDesc = aPart->contentDescriptionEncoded();
3225  TQCString contDisp = aPart->contentDisposition();
3226  TQCString name = KMMsgBase::encodeRFC2231StringAutoDetectCharset( aPart->name(), charset );
3227  bool RFC2231encoded = aPart->name() != TQString(name);
3228  TQCString paramAttr = aPart->parameterAttribute();
3229 
3230  DwHeaders& headers = part->Headers();
3231 
3232  DwMediaType& ct = headers.ContentType();
3233  if (!type.isEmpty() && !subtype.isEmpty())
3234  {
3235  ct.SetTypeStr(type.data());
3236  ct.SetSubtypeStr(subtype.data());
3237  if (!charset.isEmpty()){
3238  DwParameter *param;
3239  param=new DwParameter;
3240  param->SetAttribute("charset");
3241  param->SetValue(charset.data());
3242  ct.AddParameter(param);
3243  }
3244  }
3245 
3246  TQCString additionalParam = aPart->additionalCTypeParamStr();
3247  if( !additionalParam.isEmpty() )
3248  {
3249  TQCString parAV;
3250  DwString parA, parV;
3251  int iL, i1, i2, iM;
3252  iL = additionalParam.length();
3253  i1 = 0;
3254  i2 = additionalParam.find(';', i1, false);
3255  while ( i1 < iL )
3256  {
3257  if( -1 == i2 )
3258  i2 = iL;
3259  if( i1+1 < i2 ) {
3260  parAV = additionalParam.mid( i1, (i2-i1) );
3261  iM = parAV.find('=');
3262  if( -1 < iM )
3263  {
3264  parA = parAV.left( iM ).data();
3265  parV = parAV.right( parAV.length() - iM - 1 ).data();
3266  if( ('"' == parV.at(0)) && ('"' == parV.at(parV.length()-1)) )
3267  {
3268  parV.erase( 0, 1);
3269  parV.erase( parV.length()-1 );
3270  }
3271  }
3272  else
3273  {
3274  parA = parAV.data();
3275  parV = "";
3276  }
3277  DwParameter *param;
3278  param = new DwParameter;
3279  param->SetAttribute( parA );
3280  param->SetValue( parV );
3281  ct.AddParameter( param );
3282  }
3283  i1 = i2+1;
3284  i2 = additionalParam.find(';', i1, false);
3285  }
3286  }
3287 
3288  if ( !name.isEmpty() ) {
3289  if (RFC2231encoded)
3290  {
3291  DwParameter *nameParam;
3292  nameParam = new DwParameter;
3293  nameParam->SetAttribute("name*");
3294  nameParam->SetValue(name.data(),true);
3295  ct.AddParameter(nameParam);
3296  } else {
3297  ct.SetName(name.data());
3298  }
3299  }
3300 
3301  if (!paramAttr.isEmpty())
3302  {
3303  TQCString paramValue;
3304  paramValue = KMMsgBase::encodeRFC2231StringAutoDetectCharset( aPart->parameterValue(), charset );
3305  DwParameter *param = new DwParameter;
3306  if (aPart->parameterValue() != TQString(paramValue))
3307  {
3308  param->SetAttribute((paramAttr + '*').data());
3309  param->SetValue(paramValue.data(),true);
3310  } else {
3311  param->SetAttribute(paramAttr.data());
3312  param->SetValue(paramValue.data());
3313  }
3314  ct.AddParameter(param);
3315  }
3316 
3317  if (!cte.isEmpty())
3318  headers.Cte().FromString(cte);
3319 
3320  if (!contDesc.isEmpty())
3321  headers.ContentDescription().FromString(contDesc);
3322 
3323  if (!contDisp.isEmpty())
3324  headers.ContentDisposition().FromString(contDisp);
3325 
3326  const DwString bodyStr = aPart->dwBody();
3327  if (!bodyStr.empty())
3328  part->Body().FromString(bodyStr);
3329  else
3330  part->Body().FromString("");
3331 
3332  if (!aPart->partSpecifier().isNull())
3333  part->SetPartId( aPart->partSpecifier().latin1() );
3334 
3335  if (aPart->decodedSize() > 0)
3336  part->SetBodySize( aPart->decodedSize() );
3337 
3338  return part;
3339 }
3340 
3341 
3342 //-----------------------------------------------------------------------------
3343 void KMMessage::addDwBodyPart(DwBodyPart * aDwPart)
3344 {
3345  mMsg->Body().AddBodyPart( aDwPart );
3346  mNeedsAssembly = true;
3347 }
3348 
3349 
3350 //-----------------------------------------------------------------------------
3351 void KMMessage::addBodyPart(const KMMessagePart* aPart)
3352 {
3353  DwBodyPart* part = createDWBodyPart( aPart );
3354  addDwBodyPart( part );
3355 }
3356 
3357 
3358 //-----------------------------------------------------------------------------
3359 TQString KMMessage::generateMessageId( const TQString& addr )
3360 {
3361  TQDateTime datetime = TQDateTime::currentDateTime();
3362  TQString msgIdStr;
3363 
3364  msgIdStr = '<' + datetime.toString( "yyyyMMddhhmm.sszzz" );
3365 
3366  TQString msgIdSuffix;
3367  KConfigGroup general( KMKernel::config(), "General" );
3368 
3369  if( general.readBoolEntry( "useCustomMessageIdSuffix", false ) )
3370  msgIdSuffix = general.readEntry( "myMessageIdSuffix" );
3371 
3372  if( !msgIdSuffix.isEmpty() )
3373  msgIdStr += '@' + msgIdSuffix;
3374  else
3375  msgIdStr += '.' + KPIM::encodeIDN( addr );
3376 
3377  msgIdStr += '>';
3378 
3379  return msgIdStr;
3380 }
3381 
3382 
3383 //-----------------------------------------------------------------------------
3384 TQCString KMMessage::html2source( const TQCString & src )
3385 {
3386  TQCString result( 1 + 6*(src.size()-1) ); // maximal possible length
3387 
3388  TQCString::ConstIterator s = src.begin();
3389  TQCString::Iterator d = result.begin();
3390  while ( *s ) {
3391  switch ( *s ) {
3392  case '<': {
3393  *d++ = '&';
3394  *d++ = 'l';
3395  *d++ = 't';
3396  *d++ = ';';
3397  ++s;
3398  }
3399  break;
3400  case '\r': {
3401  ++s;
3402  }
3403  break;
3404  case '\n': {
3405  *d++ = '<';
3406  *d++ = 'b';
3407  *d++ = 'r';
3408  *d++ = '>';
3409  ++s;
3410  }
3411  break;
3412  case '>': {
3413  *d++ = '&';
3414  *d++ = 'g';
3415  *d++ = 't';
3416  *d++ = ';';
3417  ++s;
3418  }
3419  break;
3420  case '&': {
3421  *d++ = '&';
3422  *d++ = 'a';
3423  *d++ = 'm';
3424  *d++ = 'p';
3425  *d++ = ';';
3426  ++s;
3427  }
3428  break;
3429  case '"': {
3430  *d++ = '&';
3431  *d++ = 'q';
3432  *d++ = 'u';
3433  *d++ = 'o';
3434  *d++ = 't';
3435  *d++ = ';';
3436  ++s;
3437  }
3438  break;
3439  case '\'': {
3440  *d++ = '&';
3441  *d++ = 'a';
3442  *d++ = 'p';
3443  *d++ = 's';
3444  *d++ = ';';
3445  ++s;
3446  }
3447  break;
3448  default:
3449  *d++ = *s++;
3450  }
3451  }
3452  result.truncate( d - result.begin() ); // adds trailing NUL
3453  return result;
3454 }
3455 
3456 //-----------------------------------------------------------------------------
3457 TQString KMMessage::encodeMailtoUrl( const TQString& str )
3458 {
3459  TQString result;
3460  result = TQString::fromLatin1( KMMsgBase::encodeRFC2047String( str,
3461  "utf-8" ) );
3462  result = KURL::encode_string( result );
3463  return result;
3464 }
3465 
3466 
3467 //-----------------------------------------------------------------------------
3468 TQString KMMessage::decodeMailtoUrl( const TQString& url )
3469 {
3470  TQString result;
3471  result = KURL::decode_string( url );
3472  result = KMMsgBase::decodeRFC2047String( result.latin1() );
3473  return result;
3474 }
3475 
3476 
3477 //-----------------------------------------------------------------------------
3478 TQCString KMMessage::stripEmailAddr( const TQCString& aStr )
3479 {
3480  //kdDebug(5006) << "KMMessage::stripEmailAddr( " << aStr << " )" << endl;
3481 
3482  if ( aStr.isEmpty() )
3483  return TQCString();
3484 
3485  TQCString result;
3486 
3487  // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
3488  // The purpose is to extract a displayable string from the mailboxes.
3489  // Comments in the addr-spec are not handled. No error checking is done.
3490 
3491  TQCString name;
3492  TQCString comment;
3493  TQCString angleAddress;
3494  enum { TopLevel, InComment, InAngleAddress } context = TopLevel;
3495  bool inQuotedString = false;
3496  int commentLevel = 0;
3497 
3498  for ( const char* p = aStr.data(); *p; ++p ) {
3499  switch ( context ) {
3500  case TopLevel : {
3501  switch ( *p ) {
3502  case '"' : inQuotedString = !inQuotedString;
3503  break;
3504  case '(' : if ( !inQuotedString ) {
3505  context = InComment;
3506  commentLevel = 1;
3507  }
3508  else
3509  name += *p;
3510  break;
3511  case '<' : if ( !inQuotedString ) {
3512  context = InAngleAddress;
3513  }
3514  else
3515  name += *p;
3516  break;
3517  case '\\' : // quoted character
3518  ++p; // skip the '\'
3519  if ( *p )
3520  name += *p;
3521  break;
3522  case ',' : if ( !inQuotedString ) {
3523  // next email address
3524  if ( !result.isEmpty() )
3525  result += ", ";
3526  name = name.stripWhiteSpace();
3527  comment = comment.stripWhiteSpace();
3528  angleAddress = angleAddress.stripWhiteSpace();
3529  /*
3530  kdDebug(5006) << "Name : \"" << name
3531  << "\"" << endl;
3532  kdDebug(5006) << "Comment : \"" << comment
3533  << "\"" << endl;
3534  kdDebug(5006) << "Address : \"" << angleAddress
3535  << "\"" << endl;
3536  */
3537  if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
3538  // handle Outlook-style addresses like
3539  // john.doe@invalid (John Doe)
3540  result += comment;
3541  }
3542  else if ( !name.isEmpty() ) {
3543  result += name;
3544  }
3545  else if ( !comment.isEmpty() ) {
3546  result += comment;
3547  }
3548  else if ( !angleAddress.isEmpty() ) {
3549  result += angleAddress;
3550  }
3551  name = TQCString();
3552  comment = TQCString();
3553  angleAddress = TQCString();
3554  }
3555  else
3556  name += *p;
3557  break;
3558  default : name += *p;
3559  }
3560  break;
3561  }
3562  case InComment : {
3563  switch ( *p ) {
3564  case '(' : ++commentLevel;
3565  comment += *p;
3566  break;
3567  case ')' : --commentLevel;
3568  if ( commentLevel == 0 ) {
3569  context = TopLevel;
3570  comment += ' '; // separate the text of several comments
3571  }
3572  else
3573  comment += *p;
3574  break;
3575  case '\\' : // quoted character
3576  ++p; // skip the '\'
3577  if ( *p )
3578  comment += *p;
3579  break;
3580  default : comment += *p;
3581  }
3582  break;
3583  }
3584  case InAngleAddress : {
3585  switch ( *p ) {
3586  case '"' : inQuotedString = !inQuotedString;
3587  angleAddress += *p;
3588  break;
3589  case '>' : if ( !inQuotedString ) {
3590  context = TopLevel;
3591  }
3592  else
3593  angleAddress += *p;
3594  break;
3595  case '\\' : // quoted character
3596  ++p; // skip the '\'
3597  if ( *p )
3598  angleAddress += *p;
3599  break;
3600  default : angleAddress += *p;
3601  }
3602  break;
3603  }
3604  } // switch ( context )
3605  }
3606  if ( !result.isEmpty() )
3607  result += ", ";
3608  name = name.stripWhiteSpace();
3609  comment = comment.stripWhiteSpace();
3610  angleAddress = angleAddress.stripWhiteSpace();
3611  /*
3612  kdDebug(5006) << "Name : \"" << name << "\"" << endl;
3613  kdDebug(5006) << "Comment : \"" << comment << "\"" << endl;
3614  kdDebug(5006) << "Address : \"" << angleAddress << "\"" << endl;
3615  */
3616  if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
3617  // handle Outlook-style addresses like
3618  // john.doe@invalid (John Doe)
3619  result += comment;
3620  }
3621  else if ( !name.isEmpty() ) {
3622  result += name;
3623  }
3624  else if ( !comment.isEmpty() ) {
3625  result += comment;
3626  }
3627  else if ( !angleAddress.isEmpty() ) {
3628  result += angleAddress;
3629  }
3630 
3631  //kdDebug(5006) << "KMMessage::stripEmailAddr(...) returns \"" << result
3632  // << "\"" << endl;
3633  return result;
3634 }
3635 
3636 //-----------------------------------------------------------------------------
3637 TQString KMMessage::stripEmailAddr( const TQString& aStr )
3638 {
3639  //kdDebug(5006) << "KMMessage::stripEmailAddr( " << aStr << " )" << endl;
3640 
3641  if ( aStr.isEmpty() )
3642  return TQString();
3643 
3644  TQString result;
3645 
3646  // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
3647  // The purpose is to extract a displayable string from the mailboxes.
3648  // Comments in the addr-spec are not handled. No error checking is done.
3649 
3650  TQString name;
3651  TQString comment;
3652  TQString angleAddress;
3653  enum { TopLevel, InComment, InAngleAddress } context = TopLevel;
3654  bool inQuotedString = false;
3655  int commentLevel = 0;
3656 
3657  TQChar ch;
3658  unsigned int strLength(aStr.length());
3659  for ( uint index = 0; index < strLength; ++index ) {
3660  ch = aStr[index];
3661  switch ( context ) {
3662  case TopLevel : {
3663  switch ( ch.latin1() ) {
3664  case '"' : inQuotedString = !inQuotedString;
3665  break;
3666  case '(' : if ( !inQuotedString ) {
3667  context = InComment;
3668  commentLevel = 1;
3669  }
3670  else
3671  name += ch;
3672  break;
3673  case '<' : if ( !inQuotedString ) {
3674  context = InAngleAddress;
3675  }
3676  else
3677  name += ch;
3678  break;
3679  case '\\' : // quoted character
3680  ++index; // skip the '\'
3681  if ( index < aStr.length() )
3682  name += aStr[index];
3683  break;
3684  case ',' : if ( !inQuotedString ) {
3685  // next email address
3686  if ( !result.isEmpty() )
3687  result += ", ";
3688  name = name.stripWhiteSpace();
3689  comment = comment.stripWhiteSpace();
3690  angleAddress = angleAddress.stripWhiteSpace();
3691  /*
3692  kdDebug(5006) << "Name : \"" << name
3693  << "\"" << endl;
3694  kdDebug(5006) << "Comment : \"" << comment
3695  << "\"" << endl;
3696  kdDebug(5006) << "Address : \"" << angleAddress
3697  << "\"" << endl;
3698  */
3699  if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
3700  // handle Outlook-style addresses like
3701  // john.doe@invalid (John Doe)
3702  result += comment;
3703  }
3704  else if ( !name.isEmpty() ) {
3705  result += name;
3706  }
3707  else if ( !comment.isEmpty() ) {
3708  result += comment;
3709  }
3710  else if ( !angleAddress.isEmpty() ) {
3711  result += angleAddress;
3712  }
3713  name = TQString();
3714  comment = TQString();
3715  angleAddress = TQString();
3716  }
3717  else
3718  name += ch;
3719  break;
3720  default : name += ch;
3721  }
3722  break;
3723  }
3724  case InComment : {
3725  switch ( ch.latin1() ) {
3726  case '(' : ++commentLevel;
3727  comment += ch;
3728  break;
3729  case ')' : --commentLevel;
3730  if ( commentLevel == 0 ) {
3731  context = TopLevel;
3732  comment += ' '; // separate the text of several comments
3733  }
3734  else
3735  comment += ch;
3736  break;
3737  case '\\' : // quoted character
3738  ++index; // skip the '\'
3739  if ( index < aStr.length() )
3740  comment += aStr[index];
3741  break;
3742  default : comment += ch;
3743  }
3744  break;
3745  }
3746  case InAngleAddress : {
3747  switch ( ch.latin1() ) {
3748  case '"' : inQuotedString = !inQuotedString;
3749  angleAddress += ch;
3750  break;
3751  case '>' : if ( !inQuotedString ) {
3752  context = TopLevel;
3753  }
3754  else
3755  angleAddress += ch;
3756  break;
3757  case '\\' : // quoted character
3758  ++index; // skip the '\'
3759  if ( index < aStr.length() )
3760  angleAddress += aStr[index];
3761  break;
3762  default : angleAddress += ch;
3763  }
3764  break;
3765  }
3766  } // switch ( context )
3767  }
3768  if ( !result.isEmpty() )
3769  result += ", ";
3770  name = name.stripWhiteSpace();
3771  comment = comment.stripWhiteSpace();
3772  angleAddress = angleAddress.stripWhiteSpace();
3773  /*
3774  kdDebug(5006) << "Name : \"" << name << "\"" << endl;
3775  kdDebug(5006) << "Comment : \"" << comment << "\"" << endl;
3776  kdDebug(5006) << "Address : \"" << angleAddress << "\"" << endl;
3777  */
3778  if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
3779  // handle Outlook-style addresses like
3780  // john.doe@invalid (John Doe)
3781  result += comment;
3782  }
3783  else if ( !name.isEmpty() ) {
3784  result += name;
3785  }
3786  else if ( !comment.isEmpty() ) {
3787  result += comment;
3788  }
3789  else if ( !angleAddress.isEmpty() ) {
3790  result += angleAddress;
3791  }
3792 
3793  //kdDebug(5006) << "KMMessage::stripEmailAddr(...) returns \"" << result
3794  // << "\"" << endl;
3795  return result;
3796 }
3797 
3798 //-----------------------------------------------------------------------------
3799 TQString KMMessage::quoteHtmlChars( const TQString& str, bool removeLineBreaks )
3800 {
3801  TQString result;
3802 
3803  unsigned int strLength(str.length());
3804  result.reserve( 6*strLength ); // maximal possible length
3805  for( unsigned int i = 0; i < strLength; ++i )
3806  switch ( str[i].latin1() ) {
3807  case '<':
3808  result += "&lt;";
3809  break;
3810  case '>':
3811  result += "&gt;";
3812  break;
3813  case '&':
3814  result += "&amp;";
3815  break;
3816  case '"':
3817  result += "&quot;";
3818  break;
3819  case '\n':
3820  if ( !removeLineBreaks )
3821  result += "<br>";
3822  break;
3823  case '\r':
3824  // ignore CR
3825  break;
3826  default:
3827  result += str[i];
3828  }
3829 
3830  result.squeeze();
3831  return result;
3832 }
3833 
3834 //-----------------------------------------------------------------------------
3835 TQString KMMessage::emailAddrAsAnchor(const TQString& aEmail, bool stripped, const TQString& cssStyle, bool aLink)
3836 {
3837  if( aEmail.isEmpty() )
3838  return aEmail;
3839 
3840  TQStringList addressList = KPIM::splitEmailAddrList( aEmail );
3841  TQString result;
3842 
3843  for( TQStringList::ConstIterator it = addressList.begin();
3844  ( it != addressList.end() );
3845  ++it ) {
3846  if( !(*it).isEmpty() ) {
3847 
3848  // Extract the name, mail and some pretty versions out of the mail address
3849  TQString name, mail;
3850  KPIM::getNameAndMail( *it, name, mail );
3851  TQString pretty;
3852  TQString prettyStripped;
3853  if ( name.stripWhiteSpace().isEmpty() ) {
3854  pretty = mail;
3855  prettyStripped = mail;
3856  } else {
3857  pretty = KPIM::quoteNameIfNecessary( name ) + " <" + mail + ">";
3858  prettyStripped = name;
3859  }
3860 
3861  if(aLink) {
3862  result += "<a href=\"mailto:"
3863  + KMMessage::encodeMailtoUrl( pretty )
3864  + "\" "+cssStyle+">";
3865  }
3866 
3867  if ( stripped ) {
3868  result += KMMessage::quoteHtmlChars( prettyStripped, true );
3869  }
3870  else {
3871  result += KMMessage::quoteHtmlChars( pretty, true );
3872  }
3873 
3874  if(aLink)
3875  result += "</a>, ";
3876  }
3877  }
3878  // cut of the trailing ", "
3879  if(aLink)
3880  result.truncate( result.length() - 2 );
3881 
3882  //kdDebug(5006) << "KMMessage::emailAddrAsAnchor('" << aEmail
3883  // << "') returns:\n-->" << result << "<--" << endl;
3884  return result;
3885 }
3886 
3887 //-----------------------------------------------------------------------------
3888 //static
3889 TQStringList KMMessage::stripAddressFromAddressList( const TQString& address,
3890  const TQStringList& list )
3891 {
3892  TQStringList addresses( list );
3893  TQString addrSpec( KPIM::getEmailAddress( address ) );
3894  for ( TQStringList::Iterator it = addresses.begin();
3895  it != addresses.end(); ) {
3896  if ( kasciistricmp( addrSpec.utf8().data(),
3897  KPIM::getEmailAddress( *it ).utf8().data() ) == 0 ) {
3898  kdDebug(5006) << "Removing " << *it << " from the address list"
3899  << endl;
3900  it = addresses.remove( it );
3901  }
3902  else
3903  ++it;
3904  }
3905  return addresses;
3906 }
3907 
3908 
3909 //-----------------------------------------------------------------------------
3910 //static
3911 TQStringList KMMessage::stripMyAddressesFromAddressList( const TQStringList& list )
3912 {
3913  TQStringList addresses = list;
3914  for( TQStringList::Iterator it = addresses.begin();
3915  it != addresses.end(); ) {
3916  kdDebug(5006) << "Check whether " << *it << " is one of my addresses"
3917  << endl;
3918  if( kmkernel->identityManager()->thatIsMe( KPIM::getEmailAddress( *it ) ) ) {
3919  kdDebug(5006) << "Removing " << *it << " from the address list"
3920  << endl;
3921  it = addresses.remove( it );
3922  }
3923  else
3924  ++it;
3925  }
3926  return addresses;
3927 }
3928 
3929 
3930 //-----------------------------------------------------------------------------
3931 //static
3932 bool KMMessage::addressIsInAddressList( const TQString& address,
3933  const TQStringList& addresses )
3934 {
3935  TQString addrSpec = KPIM::getEmailAddress( address );
3936  for( TQStringList::ConstIterator it = addresses.begin();
3937  it != addresses.end(); ++it ) {
3938  if ( kasciistricmp( addrSpec.utf8().data(),
3939  KPIM::getEmailAddress( *it ).utf8().data() ) == 0 )
3940  return true;
3941  }
3942  return false;
3943 }
3944 
3945 
3946 //-----------------------------------------------------------------------------
3947 //static
3948 TQString KMMessage::expandAliases( const TQString& recipients )
3949 {
3950  if ( recipients.isEmpty() )
3951  return TQString();
3952 
3953  TQStringList recipientList = KPIM::splitEmailAddrList( recipients );
3954 
3955  TQString expandedRecipients;
3956  for ( TQStringList::Iterator it = recipientList.begin();
3957  it != recipientList.end(); ++it ) {
3958  if ( !expandedRecipients.isEmpty() )
3959  expandedRecipients += ", ";
3960  TQString receiver = (*it).stripWhiteSpace();
3961 
3962  // try to expand distribution list
3963  TQString expandedList = KAddrBookExternal::expandDistributionList( receiver );
3964  if ( !expandedList.isEmpty() ) {
3965  expandedRecipients += expandedList;
3966  continue;
3967  }
3968 
3969  // try to expand nick name
3970  TQString expandedNickName = KabcBridge::expandNickName( receiver );
3971  if ( !expandedNickName.isEmpty() ) {
3972  expandedRecipients += expandedNickName;
3973  continue;
3974  }
3975 
3976  // check whether the address is missing the domain part
3977  // FIXME: looking for '@' might be wrong
3978  if ( receiver.find('@') == -1 ) {
3979  KConfigGroup general( KMKernel::config(), "General" );
3980  TQString defaultdomain = general.readEntry( "Default domain" );
3981  if( !defaultdomain.isEmpty() ) {
3982  expandedRecipients += receiver + "@" + defaultdomain;
3983  }
3984  else {
3985  expandedRecipients += guessEmailAddressFromLoginName( receiver );
3986  }
3987  }
3988  else
3989  expandedRecipients += receiver;
3990  }
3991 
3992  return expandedRecipients;
3993 }
3994 
3995 
3996 //-----------------------------------------------------------------------------
3997 //static
3998 TQString KMMessage::guessEmailAddressFromLoginName( const TQString& loginName )
3999 {
4000  if ( loginName.isEmpty() )
4001  return TQString();
4002 
4003  char hostnameC[256];
4004  // null terminate this C string
4005  hostnameC[255] = '\0';
4006  // set the string to 0 length if gethostname fails
4007  if ( gethostname( hostnameC, 255 ) )
4008  hostnameC[0] = '\0';
4009  TQString address = loginName;
4010  address += '@';
4011  address += TQString::fromLocal8Bit( hostnameC );
4012 
4013  // try to determine the real name
4014  const KUser user( loginName );
4015  if ( user.isValid() ) {
4016  TQString fullName = user.fullName();
4017  if ( fullName.find( TQRegExp( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ) ) != -1 )
4018  address = '"' + fullName.replace( '\\', "\\" ).replace( '"', "\\" )
4019  + "\" <" + address + '>';
4020  else
4021  address = fullName + " <" + address + '>';
4022  }
4023 
4024  return address;
4025 }
4026 
4027 //-----------------------------------------------------------------------------
4029 {
4031 
4032  KConfig *config=KMKernel::config();
4033  KConfigGroupSaver saver(config, "General");
4034 
4035  config->setGroup("General");
4036 
4037  int languageNr = config->readNumEntry("reply-current-language",0);
4038 
4039  { // area for config group "KMMessage #n"
4040  KConfigGroupSaver saver(config, TQString("KMMessage #%1").arg(languageNr));
4041  sReplyLanguage = config->readEntry("language",KGlobal::locale()->language());
4042  sReplyStr = config->readEntry("phrase-reply",
4043  i18n("On %D, you wrote:"));
4044  sReplyAllStr = config->readEntry("phrase-reply-all",
4045  i18n("On %D, %F wrote:"));
4046  sForwardStr = config->readEntry("phrase-forward",
4047  i18n("Forwarded Message"));
4048  sIndentPrefixStr = config->readEntry("indent-prefix",">%_");
4049  }
4050 
4051  { // area for config group "Composer"
4052  KConfigGroupSaver saver(config, "Composer");
4053  sSmartQuote = GlobalSettings::self()->smartQuote();
4054  sWordWrap = GlobalSettings::self()->wordWrap();
4055  sWrapCol = GlobalSettings::self()->lineWrapWidth();
4056  if ((sWrapCol == 0) || (sWrapCol > 78))
4057  sWrapCol = 78;
4058  if (sWrapCol < 30)
4059  sWrapCol = 30;
4060 
4061  sPrefCharsets = config->readListEntry("pref-charsets");
4062  }
4063 
4064  { // area for config group "Reader"
4065  KConfigGroupSaver saver(config, "Reader");
4066  sHeaderStrategy = HeaderStrategy::create( config->readEntry( "header-set-displayed", "rich" ) );
4067  }
4068 }
4069 
4071 {
4072  TQCString retval;
4073 
4074  if (!sPrefCharsets.isEmpty())
4075  retval = sPrefCharsets[0].latin1();
4076 
4077  if (retval.isEmpty() || (retval == "locale")) {
4078  retval = TQCString(kmkernel->networkCodec()->mimeName());
4079  KPIM::kAsciiToLower( retval.data() );
4080  }
4081 
4082  if (retval == "jisx0208.1983-0") retval = "iso-2022-jp";
4083  else if (retval == "ksc5601.1987-0") retval = "euc-kr";
4084  return retval;
4085 }
4086 
4087 const TQStringList &KMMessage::preferredCharsets()
4088 {
4089  return sPrefCharsets;
4090 }
4091 
4092 //-----------------------------------------------------------------------------
4093 TQCString KMMessage::charset() const
4094 {
4095  if ( mMsg->Headers().HasContentType() ) {
4096  DwMediaType &mType=mMsg->Headers().ContentType();
4097  mType.Parse();
4098  DwParameter *param=mType.FirstParameter();
4099  while(param){
4100  if (!kasciistricmp(param->Attribute().c_str(), "charset"))
4101  return param->Value().c_str();
4102  else param=param->Next();
4103  }
4104  }
4105  return ""; // us-ascii, but we don't have to specify it
4106 }
4107 
4108 //-----------------------------------------------------------------------------
4109 void KMMessage::setCharset( const TQCString &charset, DwEntity *entity )
4110 {
4111  kdWarning( type() != DwMime::kTypeText )
4112  << "KMMessage::setCharset(): trying to set a charset for a non-textual mimetype." << endl
4113  << "Fix this caller:" << endl
4114  << "====================================================================" << endl
4115  << kdBacktrace( 5 ) << endl
4116  << "====================================================================" << endl;
4117 
4118  if ( !entity )
4119  entity = mMsg;
4120 
4121  DwMediaType &mType = entity->Headers().ContentType();
4122  mType.Parse();
4123  DwParameter *param = mType.FirstParameter();
4124  while( param ) {
4125 
4126  // FIXME use the mimelib functions here for comparison.
4127  if ( !kasciistricmp( param->Attribute().c_str(), "charset" ) )
4128  break;
4129 
4130  param = param->Next();
4131  }
4132  if ( !param ) {
4133  param = new DwParameter;
4134  param->SetAttribute( "charset" );
4135  mType.AddParameter( param );
4136  }
4137  else
4138  mType.SetModified();
4139 
4140  TQCString lowerCharset = charset;
4141  KPIM::kAsciiToLower( lowerCharset.data() );
4142  param->SetValue( DwString( lowerCharset ) );
4143  mType.Assemble();
4144 }
4145 
4146 
4147 //-----------------------------------------------------------------------------
4148 void KMMessage::setStatus(const KMMsgStatus aStatus, int idx)
4149 {
4150  if (mStatus == aStatus)
4151  return;
4152  KMMsgBase::setStatus(aStatus, idx);
4153 }
4154 
4155 void KMMessage::setEncryptionState(const KMMsgEncryptionState s, int idx)
4156 {
4157  if( mEncryptionState == s )
4158  return;
4159  mEncryptionState = s;
4160  mDirty = true;
4162 }
4163 
4164 void KMMessage::setSignatureState(KMMsgSignatureState s, int idx)
4165 {
4166  if( mSignatureState == s )
4167  return;
4168  mSignatureState = s;
4169  mDirty = true;
4171 }
4172 
4173 void KMMessage::setMDNSentState( KMMsgMDNSentState status, int idx )
4174 {
4175  if ( mMDNSentState == status )
4176  return;
4177  if ( status == 0 )
4178  status = KMMsgMDNStateUnknown;
4179  mMDNSentState = status;
4180  mDirty = true;
4181  KMMsgBase::setMDNSentState( status, idx );
4182 }
4183 
4184 //-----------------------------------------------------------------------------
4185 void KMMessage::link( const KMMessage *aMsg, KMMsgStatus aStatus )
4186 {
4187  Q_ASSERT( aStatus == KMMsgStatusReplied
4188  || aStatus == KMMsgStatusForwarded
4189  || aStatus == KMMsgStatusDeleted );
4190 
4191  TQString message = headerField( "X-KMail-Link-Message" );
4192  if ( !message.isEmpty() )
4193  message += ',';
4194  TQString type = headerField( "X-KMail-Link-Type" );
4195  if ( !type.isEmpty() )
4196  type += ',';
4197 
4198  message += TQString::number( aMsg->getMsgSerNum() );
4199  if ( aStatus == KMMsgStatusReplied )
4200  type += "reply";
4201  else if ( aStatus == KMMsgStatusForwarded )
4202  type += "forward";
4203  else if ( aStatus == KMMsgStatusDeleted )
4204  type += "deleted";
4205 
4206  setHeaderField( "X-KMail-Link-Message", message );
4207  setHeaderField( "X-KMail-Link-Type", type );
4208 }
4209 
4210 //-----------------------------------------------------------------------------
4211 void KMMessage::getLink(int n, ulong *retMsgSerNum, KMMsgStatus *reStatus) const
4212 {
4213  *retMsgSerNum = 0;
4214  *reStatus = KMMsgStatusUnknown;
4215 
4216  TQString message = headerField("X-KMail-Link-Message");
4217  TQString type = headerField("X-KMail-Link-Type");
4218  message = message.section(',', n, n);
4219  type = type.section(',', n, n);
4220 
4221  if ( !message.isEmpty() && !type.isEmpty() ) {
4222  *retMsgSerNum = message.toULong();
4223  if ( type == "reply" )
4224  *reStatus = KMMsgStatusReplied;
4225  else if ( type == "forward" )
4226  *reStatus = KMMsgStatusForwarded;
4227  else if ( type == "deleted" )
4228  *reStatus = KMMsgStatusDeleted;
4229  }
4230 }
4231 
4232 //-----------------------------------------------------------------------------
4233 DwBodyPart* KMMessage::findDwBodyPart( DwBodyPart* part, const TQString & partSpecifier )
4234 {
4235  if ( !part ) return 0;
4236  DwBodyPart* current;
4237 
4238  if ( part->partId() == partSpecifier )
4239  return part;
4240 
4241  // multipart
4242  if ( part->hasHeaders() &&
4243  part->Headers().HasContentType() &&
4244  part->Body().FirstBodyPart() &&
4245  (DwMime::kTypeMultipart == part->Headers().ContentType().Type() ) &&
4246  (current = findDwBodyPart( part->Body().FirstBodyPart(), partSpecifier )) )
4247  {
4248  return current;
4249  }
4250 
4251  // encapsulated message
4252  if ( part->Body().Message() &&
4253  part->Body().Message()->Body().FirstBodyPart() &&
4254  (current = findDwBodyPart( part->Body().Message()->Body().FirstBodyPart(),
4255  partSpecifier )) )
4256  {
4257  return current;
4258  }
4259 
4260  // next part
4261  return findDwBodyPart( part->Next(), partSpecifier );
4262 }
4263 
4264 //-----------------------------------------------------------------------------
4265 void KMMessage::updateBodyPart(const TQString partSpecifier, const TQByteArray & data)
4266 {
4267  if ( !data.data() || !data.size() )
4268  return;
4269 
4270  DwString content( data.data(), data.size() );
4271  if ( numBodyParts() > 0 &&
4272  partSpecifier != "0" &&
4273  partSpecifier != "TEXT" )
4274  {
4275  TQString specifier = partSpecifier;
4276  if ( partSpecifier.endsWith(".HEADER") ||
4277  partSpecifier.endsWith(".MIME") ) {
4278  // get the parent bodypart
4279  specifier = partSpecifier.section( '.', 0, -2 );
4280  }
4281 
4282  // search for the bodypart
4283  mLastUpdated = findDwBodyPart( getFirstDwBodyPart(), specifier );
4284  kdDebug(5006) << "KMMessage::updateBodyPart " << specifier << endl;
4285  if (!mLastUpdated)
4286  {
4287  kdWarning(5006) << "KMMessage::updateBodyPart - can not find part "
4288  << specifier << endl;
4289  return;
4290  }
4291  if ( partSpecifier.endsWith(".MIME") )
4292  {
4293  // update headers
4294  // get rid of EOL
4295  content.resize( TQMAX( content.length(), 2 ) - 2 );
4296  // we have to delete the fields first as they might have been created by
4297  // an earlier call to DwHeaders::FieldBody
4298  mLastUpdated->Headers().DeleteAllFields();
4299  mLastUpdated->Headers().FromString( content );
4300  mLastUpdated->Headers().Parse();
4301  } else if ( partSpecifier.endsWith(".HEADER") )
4302  {
4303  // update header of embedded message
4304  mLastUpdated->Body().Message()->Headers().FromString( content );
4305  mLastUpdated->Body().Message()->Headers().Parse();
4306  } else {
4307  // update body
4308  mLastUpdated->Body().FromString( content );
4309  TQString parentSpec = partSpecifier.section( '.', 0, -2 );
4310  if ( !parentSpec.isEmpty() )
4311  {
4312  DwBodyPart* parent = findDwBodyPart( getFirstDwBodyPart(), parentSpec );
4313  if ( parent && parent->hasHeaders() && parent->Headers().HasContentType() )
4314  {
4315  const DwMediaType& contentType = parent->Headers().ContentType();
4316  if ( contentType.Type() == DwMime::kTypeMessage &&
4317  contentType.Subtype() == DwMime::kSubtypeRfc822 )
4318  {
4319  // an embedded message that is not multipart
4320  // update this directly
4321  parent->Body().Message()->Body().FromString( content );
4322  }
4323  }
4324  }
4325  }
4326 
4327  } else
4328  {
4329  // update text-only messages
4330  if ( partSpecifier == "TEXT" )
4331  deleteBodyParts(); // delete empty parts first
4332  mMsg->Body().FromString( content );
4333  mMsg->Body().Parse();
4334  }
4335  mNeedsAssembly = true;
4336  if (! partSpecifier.endsWith(".HEADER") )
4337  {
4338  // notify observers
4339  notify();
4340  }
4341 }
4342 
4343 void KMMessage::updateInvitationState()
4344 {
4345  if ( mMsg && mMsg->hasHeaders() && mMsg->Headers().HasContentType() ) {
4346  TQString cntType = mMsg->Headers().ContentType().TypeStr().c_str();
4347  cntType += '/';
4348  cntType += mMsg->Headers().ContentType().SubtypeStr().c_str();
4349  if ( cntType.lower() == "text/calendar" ) {
4350  setStatus( KMMsgStatusHasInvitation );
4351  return;
4352  }
4353  }
4354  setStatus( KMMsgStatusHasNoInvitation );
4355  return;
4356 }
4357 
4358 //-----------------------------------------------------------------------------
4359 void KMMessage::updateAttachmentState( DwBodyPart* part )
4360 {
4361  if ( !part )
4362  part = getFirstDwBodyPart();
4363 
4364  if ( !part )
4365  {
4366  // kdDebug(5006) << "updateAttachmentState - no part!" << endl;
4367  setStatus( KMMsgStatusHasNoAttach );
4368  return;
4369  }
4370 
4371  bool filenameEmpty = true;
4372  if ( part->hasHeaders() ) {
4373  if ( part->Headers().HasContentDisposition() ) {
4374  DwDispositionType cd = part->Headers().ContentDisposition();
4375  filenameEmpty = cd.Filename().empty();
4376  if ( filenameEmpty ) {
4377  // let's try if it is rfc 2231 encoded which mimelib can't handle
4378  filenameEmpty = KMMsgBase::decodeRFC2231String( KMMsgBase::extractRFC2231HeaderField( cd.AsString().c_str(), "filename" ) ).isEmpty();
4379  }
4380  }
4381 
4382  // Filename still empty? Check if the content-type has a "name" parameter and try to use that as
4383  // the attachment name
4384  if ( filenameEmpty && part->Headers().HasContentType() ) {
4385  DwMediaType contentType = part->Headers().ContentType();
4386  filenameEmpty = contentType.Name().empty();
4387  if ( filenameEmpty ) {
4388  // let's try if it is rfc 2231 encoded which mimelib can't handle
4389  filenameEmpty = KMMsgBase::decodeRFC2231String( KMMsgBase::extractRFC2231HeaderField(
4390  contentType.AsString().c_str(), "name" ) ).isEmpty();
4391  }
4392  }
4393  }
4394 
4395  if ( part->hasHeaders() &&
4396  ( ( part->Headers().HasContentDisposition() &&
4397  !part->Headers().ContentDisposition().Filename().empty() ) ||
4398  ( part->Headers().HasContentType() &&
4399  !filenameEmpty ) ) )
4400  {
4401  // now blacklist certain ContentTypes
4402  if ( !part->Headers().HasContentType() ||
4403  ( part->Headers().HasContentType() &&
4404  part->Headers().ContentType().Subtype() != DwMime::kSubtypePgpSignature &&
4405  part->Headers().ContentType().Subtype() != DwMime::kSubtypePkcs7Signature ) )
4406  {
4407  setStatus( KMMsgStatusHasAttach );
4408  }
4409  return;
4410  }
4411 
4412  // multipart
4413  if ( part->hasHeaders() &&
4414  part->Headers().HasContentType() &&
4415  part->Body().FirstBodyPart() &&
4416  (DwMime::kTypeMultipart == part->Headers().ContentType().Type() ) )
4417  {
4418  updateAttachmentState( part->Body().FirstBodyPart() );
4419  }
4420 
4421  // encapsulated message
4422  if ( part->Body().Message() &&
4423  part->Body().Message()->Body().FirstBodyPart() )
4424  {
4425  updateAttachmentState( part->Body().Message()->Body().FirstBodyPart() );
4426  }
4427 
4428  // next part
4429  if ( part->Next() )
4430  updateAttachmentState( part->Next() );
4431  else if ( attachmentState() == KMMsgAttachmentUnknown )
4432  setStatus( KMMsgStatusHasNoAttach );
4433 }
4434 
4435 void KMMessage::setBodyFromUnicode( const TQString &str, DwEntity *entity )
4436 {
4437  TQCString encoding = KMMsgBase::autoDetectCharset( charset(), KMMessage::preferredCharsets(), str );
4438  if ( encoding.isEmpty() )
4439  encoding = "utf-8";
4440  const TQTextCodec * codec = KMMsgBase::codecForName( encoding );
4441  assert( codec );
4442  TQValueList<int> dummy;
4443  setCharset( encoding, entity );
4444  setBodyAndGuessCte( codec->fromUnicode( str ), dummy, false /* no 8bit */,
4445  false, entity );
4446 }
4447 
4448 const TQTextCodec * KMMessage::codec() const {
4449  const TQTextCodec * c = mOverrideCodec;
4450  if ( !c )
4451  // no override-codec set for this message, try the CT charset parameter:
4452  c = KMMsgBase::codecForName( charset() );
4453  if ( !c ) {
4454  // Ok, no override and nothing in the message, let's use the fallback
4455  // the user configured
4456  c = KMMsgBase::codecForName( GlobalSettings::self()->fallbackCharacterEncoding().latin1() );
4457  }
4458  if ( !c )
4459  // no charset means us-ascii (RFC 2045), so using local encoding should
4460  // be okay
4461  c = kmkernel->networkCodec();
4462  assert( c );
4463  return c;
4464 }
4465 
4466 TQString KMMessage::bodyToUnicode(const TQTextCodec* codec) const {
4467  if ( !codec )
4468  // No codec was given, so try the charset in the mail
4469  codec = this->codec();
4470  assert( codec );
4471 
4472  return codec->toUnicode( bodyDecoded() );
4473 }
4474 
4475 //-----------------------------------------------------------------------------
4477 {
4478  TQCString str( KPIM::getFirstEmailAddress( rawHeaderField("From") ) );
4479  if ( str.isEmpty() )
4480  str = "unknown@unknown.invalid";
4481  TQCString dateStr( dateShortStr() );
4482  if ( dateStr.isEmpty() ) {
4483  time_t t = ::time( 0 );
4484  dateStr = ctime( &t );
4485  const int len = dateStr.length();
4486  if ( dateStr[len-1] == '\n' )
4487  dateStr.truncate( len - 1 );
4488  }
4489  return "From " + str + " " + dateStr + "\n";
4490 }
4491 
4493 {
4494  sPendingDeletes << this;
4495 }
4496 
4497 DwBodyPart* KMMessage::findPart( int index )
4498 {
4499  int accu = 0;
4500  return findPartInternal( getTopLevelPart(), index, accu );
4501 }
4502 
4503 DwBodyPart* KMMessage::findPartInternal(DwEntity * root, int index, int & accu)
4504 {
4505  accu++;
4506  if ( index < accu ) // should not happen
4507  return 0;
4508  DwBodyPart *current = dynamic_cast<DwBodyPart*>( root );
4509  if ( index == accu )
4510  return current;
4511  DwBodyPart *rv = 0;
4512  if ( root->Body().FirstBodyPart() )
4513  rv = findPartInternal( root->Body().FirstBodyPart(), index, accu );
4514  if ( !rv && current && current->Next() )
4515  rv = findPartInternal( current->Next(), index, accu );
4516  if ( !rv && root->Body().Message() )
4517  rv = findPartInternal( root->Body().Message(), index, accu );
4518  return rv;
4519 }
4520