kmail

kmmessage.cpp

00001 // -*- mode: C++; c-file-style: "gnu" -*-
00002 // kmmessage.cpp
00003 
00004 // if you do not want GUI elements in here then set ALLOW_GUI to 0.
00005 #include <config.h>
00006 // needed temporarily until KMime is replacing the partNode helper class:
00007 #include "partNode.h"
00008 
00009 
00010 #define ALLOW_GUI 1
00011 #include "kmkernel.h"
00012 #include "kmmessage.h"
00013 #include "mailinglist-magic.h"
00014 #include "messageproperty.h"
00015 using KMail::MessageProperty;
00016 #include "objecttreeparser.h"
00017 using KMail::ObjectTreeParser;
00018 #include "kmfolderindex.h"
00019 #include "undostack.h"
00020 #include "kmversion.h"
00021 #include "headerstrategy.h"
00022 #include "globalsettings.h"
00023 using KMail::HeaderStrategy;
00024 #include "kmaddrbook.h"
00025 #include "kcursorsaver.h"
00026 #include "templateparser.h"
00027 
00028 #include <libkpimidentities/identity.h>
00029 #include <libkpimidentities/identitymanager.h>
00030 #include <libemailfunctions/email.h>
00031 
00032 #include <kasciistringtools.h>
00033 
00034 #include <kpgpblock.h>
00035 #include <kaddrbook.h>
00036 
00037 #include <kapplication.h>
00038 #include <kglobalsettings.h>
00039 #include <kdebug.h>
00040 #include <kconfig.h>
00041 #include <khtml_part.h>
00042 #include <kuser.h>
00043 #include <kidna.h>
00044 #include <kasciistricmp.h>
00045 
00046 #include <tqcursor.h>
00047 #include <tqtextcodec.h>
00048 #include <tqmessagebox.h>
00049 #include <kmime_util.h>
00050 #include <kmime_charfreq.h>
00051 
00052 #include <kmime_header_parsing.h>
00053 using KMime::HeaderParsing::parseAddressList;
00054 using namespace KMime::Types;
00055 
00056 #include <mimelib/body.h>
00057 #include <mimelib/field.h>
00058 #include <mimelib/mimepp.h>
00059 #include <mimelib/string.h>
00060 #include <assert.h>
00061 #include <sys/time.h>
00062 #include <time.h>
00063 #include <klocale.h>
00064 #include <stdlib.h>
00065 #include <unistd.h>
00066 #include "util.h"
00067 
00068 #if ALLOW_GUI
00069 #include <kmessagebox.h>
00070 #endif
00071 
00072 using namespace KMime;
00073 
00074 static DwString emptyString("");
00075 
00076 // Values that are set from the config file with KMMessage::readConfig()
00077 static TQString sReplyLanguage, sReplyStr, sReplyAllStr, sIndentPrefixStr;
00078 static bool sSmartQuote,
00079   sWordWrap;
00080 static int sWrapCol;
00081 static TQStringList sPrefCharsets;
00082 
00083 TQString KMMessage::sForwardStr;
00084 const HeaderStrategy * KMMessage::sHeaderStrategy = HeaderStrategy::rich();
00085 //helper
00086 static void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart );
00087 
00088 TQValueList<KMMessage*> KMMessage::sPendingDeletes;
00089 
00090 //-----------------------------------------------------------------------------
00091 KMMessage::KMMessage(DwMessage* aMsg)
00092   : KMMsgBase()
00093 {
00094   init( aMsg );
00095   // aMsg might need assembly
00096   mNeedsAssembly = true;
00097 }
00098 
00099 //-----------------------------------------------------------------------------
00100 KMMessage::KMMessage(KMFolder* parent): KMMsgBase(parent)
00101 {
00102   init();
00103 }
00104 
00105 
00106 //-----------------------------------------------------------------------------
00107 KMMessage::KMMessage(KMMsgInfo& msgInfo): KMMsgBase()
00108 {
00109   init();
00110   // now overwrite a few from the msgInfo
00111   mMsgSize = msgInfo.msgSize();
00112   mFolderOffset = msgInfo.folderOffset();
00113   mStatus = msgInfo.status();
00114   mEncryptionState = msgInfo.encryptionState();
00115   mSignatureState = msgInfo.signatureState();
00116   mMDNSentState = msgInfo.mdnSentState();
00117   mDate = msgInfo.date();
00118   mFileName = msgInfo.fileName();
00119   KMMsgBase::assign(&msgInfo);
00120 }
00121 
00122 
00123 //-----------------------------------------------------------------------------
00124 KMMessage::KMMessage(const KMMessage& other) :
00125     KMMsgBase( other ),
00126     ISubject(),
00127     mMsg(0)
00128 {
00129   init(); // to be safe
00130   assign( other );
00131 }
00132 
00133 void KMMessage::init( DwMessage* aMsg )
00134 {
00135   mNeedsAssembly = false;
00136   if ( aMsg ) {
00137     mMsg = aMsg;
00138   } else {
00139     mMsg = new DwMessage;
00140   }
00141   mOverrideCodec = 0;
00142   mDecodeHTML = false;
00143   mComplete = true;
00144   mReadyToShow = true;
00145   mMsgSize = 0;
00146   mMsgLength = 0;
00147   mFolderOffset = 0;
00148   mStatus  = KMMsgStatusNew;
00149   mEncryptionState = KMMsgEncryptionStateUnknown;
00150   mSignatureState = KMMsgSignatureStateUnknown;
00151   mMDNSentState = KMMsgMDNStateUnknown;
00152   mDate    = 0;
00153   mUnencryptedMsg = 0;
00154   mLastUpdated = 0;
00155   mCursorPos = 0;
00156   mMsgInfo = 0;
00157   mIsParsed = false;
00158 }
00159 
00160 void KMMessage::assign( const KMMessage& other )
00161 {
00162   MessageProperty::forget( this );
00163   delete mMsg;
00164   delete mUnencryptedMsg;
00165 
00166   mNeedsAssembly = true;//other.mNeedsAssembly;
00167   if( other.mMsg )
00168     mMsg = new DwMessage( *(other.mMsg) );
00169   else
00170     mMsg = 0;
00171   mOverrideCodec = other.mOverrideCodec;
00172   mDecodeHTML = other.mDecodeHTML;
00173   mMsgSize = other.mMsgSize;
00174   mMsgLength = other.mMsgLength;
00175   mFolderOffset = other.mFolderOffset;
00176   mStatus  = other.mStatus;
00177   mEncryptionState = other.mEncryptionState;
00178   mSignatureState = other.mSignatureState;
00179   mMDNSentState = other.mMDNSentState;
00180   mIsParsed = other.mIsParsed;
00181   mDate    = other.mDate;
00182   if( other.hasUnencryptedMsg() )
00183     mUnencryptedMsg = new KMMessage( *other.unencryptedMsg() );
00184   else
00185     mUnencryptedMsg = 0;
00186   setDrafts( other.drafts() );
00187   setTemplates( other.templates() );
00188   //mFileName = ""; // we might not want to copy the other messages filename (?)
00189   //KMMsgBase::assign( &other );
00190 }
00191 
00192 //-----------------------------------------------------------------------------
00193 KMMessage::~KMMessage()
00194 {
00195   delete mMsgInfo;
00196   delete mMsg;
00197   kmkernel->undoStack()->msgDestroyed( this );
00198 }
00199 
00200 
00201 //-----------------------------------------------------------------------------
00202 void KMMessage::setReferences(const TQCString& aStr)
00203 {
00204   if (!aStr) return;
00205   mMsg->Headers().References().FromString(aStr);
00206   mNeedsAssembly = true;
00207 }
00208 
00209 
00210 //-----------------------------------------------------------------------------
00211 TQCString KMMessage::id() const
00212 {
00213   DwHeaders& header = mMsg->Headers();
00214   if (header.HasMessageId())
00215     return KMail::Util::CString( header.MessageId().AsString() );
00216   else
00217     return "";
00218 }
00219 
00220 
00221 //-----------------------------------------------------------------------------
00222 //WARNING: This method updates the memory resident cache of serial numbers
00223 //WARNING: held in MessageProperty, but it does not update the persistent
00224 //WARNING: store of serial numbers on the file system that is managed by
00225 //WARNING: KMMsgDict
00226 void KMMessage::setMsgSerNum(unsigned long newMsgSerNum)
00227 {
00228   MessageProperty::setSerialCache( this, newMsgSerNum );
00229 }
00230 
00231 
00232 //-----------------------------------------------------------------------------
00233 bool KMMessage::isMessage() const
00234 {
00235   return true;
00236 }
00237 
00238 //-----------------------------------------------------------------------------
00239 bool KMMessage::transferInProgress() const
00240 {
00241   return MessageProperty::transferInProgress( getMsgSerNum() );
00242 }
00243 
00244 
00245 //-----------------------------------------------------------------------------
00246 void KMMessage::setTransferInProgress(bool value, bool force)
00247 {
00248   MessageProperty::setTransferInProgress( getMsgSerNum(), value, force );
00249   if ( !transferInProgress() && sPendingDeletes.contains( this ) ) {
00250     sPendingDeletes.remove( this );
00251     if ( parent() ) {
00252       int idx = parent()->find( this );
00253       if ( idx > 0 ) {
00254         parent()->removeMsg( idx );
00255       }
00256     }
00257   }
00258 }
00259 
00260 
00261 
00262 bool KMMessage::isUrgent() const {
00263   return headerField( "Priority" ).contains( "urgent", false )
00264     || headerField( "X-Priority" ).startsWith( "2" );
00265 }
00266 
00267 //-----------------------------------------------------------------------------
00268 void KMMessage::setUnencryptedMsg( KMMessage* unencrypted )
00269 {
00270   delete mUnencryptedMsg;
00271   mUnencryptedMsg = unencrypted;
00272 }
00273 
00274 //-----------------------------------------------------------------------------
00275 //FIXME: move to libemailfunctions
00276 KPIM::EmailParseResult KMMessage::isValidEmailAddressList( const TQString& aStr,
00277                                                            TQString& brokenAddress )
00278 {
00279   if ( aStr.isEmpty() ) {
00280      return KPIM::AddressEmpty;
00281   }
00282 
00283   TQStringList list = KPIM::splitEmailAddrList( aStr );
00284   for( TQStringList::const_iterator it = list.begin(); it != list.end(); ++it ) {
00285     KPIM::EmailParseResult errorCode = KPIM::isValidEmailAddress( *it );
00286       if ( errorCode != KPIM::AddressOk ) {
00287       brokenAddress = ( *it );
00288       return errorCode;
00289     }
00290   }
00291   return KPIM::AddressOk;
00292 }
00293 
00294 //-----------------------------------------------------------------------------
00295 const DwString& KMMessage::asDwString() const
00296 {
00297   if (mNeedsAssembly)
00298   {
00299     mNeedsAssembly = false;
00300     mMsg->Assemble();
00301   }
00302   return mMsg->AsString();
00303 }
00304 
00305 //-----------------------------------------------------------------------------
00306 const DwMessage* KMMessage::asDwMessage()
00307 {
00308   if (mNeedsAssembly)
00309   {
00310     mNeedsAssembly = false;
00311     mMsg->Assemble();
00312   }
00313   return mMsg;
00314 }
00315 
00316 //-----------------------------------------------------------------------------
00317 TQCString KMMessage::asString() const {
00318   return KMail::Util::CString( asDwString() );
00319 }
00320 
00321 
00322 TQByteArray KMMessage::asSendableString() const
00323 {
00324   KMMessage msg( new DwMessage( *this->mMsg ) );
00325   msg.removePrivateHeaderFields();
00326   msg.removeHeaderField("Bcc");
00327   return KMail::Util::ByteArray( msg.asDwString() ); // and another copy again!
00328 }
00329 
00330 TQCString KMMessage::headerAsSendableString() const
00331 {
00332   KMMessage msg( new DwMessage( *this->mMsg ) );
00333   msg.removePrivateHeaderFields();
00334   msg.removeHeaderField("Bcc");
00335   return msg.headerAsString().latin1();
00336 }
00337 
00338 void KMMessage::removePrivateHeaderFields() {
00339   removeHeaderField("Status");
00340   removeHeaderField("X-Status");
00341   removeHeaderField("X-KMail-EncryptionState");
00342   removeHeaderField("X-KMail-SignatureState");
00343   removeHeaderField("X-KMail-MDN-Sent");
00344   removeHeaderField("X-KMail-Transport");
00345   removeHeaderField("X-KMail-Identity");
00346   removeHeaderField("X-KMail-Fcc");
00347   removeHeaderField("X-KMail-Redirect-From");
00348   removeHeaderField("X-KMail-Link-Message");
00349   removeHeaderField("X-KMail-Link-Type");
00350   removeHeaderField( "X-KMail-Markup" );
00351 }
00352 
00353 //-----------------------------------------------------------------------------
00354 void KMMessage::setStatusFields()
00355 {
00356   char str[2] = { 0, 0 };
00357 
00358   setHeaderField("Status", status() & KMMsgStatusNew ? "R" : "RO");
00359   setHeaderField("X-Status", statusToStr(status()));
00360 
00361   str[0] = (char)encryptionState();
00362   setHeaderField("X-KMail-EncryptionState", str);
00363 
00364   str[0] = (char)signatureState();
00365   //kdDebug(5006) << "Setting SignatureState header field to " << str[0] << endl;
00366   setHeaderField("X-KMail-SignatureState", str);
00367 
00368   str[0] = static_cast<char>( mdnSentState() );
00369   setHeaderField("X-KMail-MDN-Sent", str);
00370 
00371   // We better do the assembling ourselves now to prevent the
00372   // mimelib from changing the message *body*.  (khz, 10.8.2002)
00373   mNeedsAssembly = false;
00374   mMsg->Headers().Assemble();
00375   mMsg->Assemble( mMsg->Headers(),
00376                   mMsg->Body() );
00377 }
00378 
00379 
00380 //----------------------------------------------------------------------------
00381 TQString KMMessage::headerAsString() const
00382 {
00383   DwHeaders& header = mMsg->Headers();
00384   header.Assemble();
00385   if ( header.AsString().empty() )
00386     return TQString::null;
00387   return TQString::fromLatin1( header.AsString().c_str() );
00388 }
00389 
00390 
00391 //-----------------------------------------------------------------------------
00392 DwMediaType& KMMessage::dwContentType()
00393 {
00394   return mMsg->Headers().ContentType();
00395 }
00396 
00397 void KMMessage::fromByteArray( const TQByteArray & ba, bool setStatus ) {
00398   return fromDwString( DwString( ba.data(), ba.size() ), setStatus );
00399 }
00400 
00401 void KMMessage::fromString( const TQCString & str, bool aSetStatus ) {
00402   return fromDwString( KMail::Util::dwString( str ), aSetStatus );
00403 }
00404 
00405 void KMMessage::fromDwString(const DwString& str, bool aSetStatus)
00406 {
00407   delete mMsg;
00408   mMsg = new DwMessage;
00409   mMsg->FromString( str );
00410   mMsg->Parse();
00411 
00412   if (aSetStatus) {
00413     setStatus(headerField("Status").latin1(), headerField("X-Status").latin1());
00414     setEncryptionStateChar( headerField("X-KMail-EncryptionState").at(0) );
00415     setSignatureStateChar(  headerField("X-KMail-SignatureState").at(0) );
00416     setMDNSentState( static_cast<KMMsgMDNSentState>( headerField("X-KMail-MDN-Sent").at(0).latin1() ) );
00417   }
00418   if ( invitationState() == KMMsgInvitationUnknown && readyToShow() )
00419     updateInvitationState();
00420   if ( attachmentState() == KMMsgAttachmentUnknown && readyToShow() )
00421     updateAttachmentState();
00422 
00423   mNeedsAssembly = false;
00424   mDate = date();
00425 }
00426 
00427 
00428 //-----------------------------------------------------------------------------
00429 TQString KMMessage::formatString(const TQString& aStr) const
00430 {
00431   TQString result, str;
00432   TQChar ch;
00433   uint j;
00434 
00435   if (aStr.isEmpty())
00436     return aStr;
00437 
00438   unsigned int strLength(aStr.length());
00439   for (uint i=0; i<strLength;) {
00440     ch = aStr[i++];
00441     if (ch == '%') {
00442       ch = aStr[i++];
00443       switch ((char)ch) {
00444       case 'D':
00445     /* I'm not too sure about this change. Is it not possible
00446        to have a long form of the date used? I don't
00447        like this change to a short XX/XX/YY date format.
00448        At least not for the default. -sanders */
00449     result += KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized,
00450                             date(), sReplyLanguage, false );
00451         break;
00452       case 'e':
00453         result += from();
00454         break;
00455       case 'F':
00456         result += fromStrip();
00457         break;
00458       case 'f':
00459         {
00460         str = fromStrip();
00461 
00462         for (j=0; str[j]>' '; j++)
00463           ;
00464         unsigned int strLength(str.length());
00465         for (; j < strLength && str[j] <= ' '; j++)
00466           ;
00467         result += str[0];
00468         if (str[j]>' ')
00469           result += str[j];
00470         else
00471           if (str[1]>' ')
00472             result += str[1];
00473         }
00474         break;
00475       case 'T':
00476         result += toStrip();
00477         break;
00478       case 't':
00479         result += to();
00480         break;
00481       case 'C':
00482         result += ccStrip();
00483         break;
00484       case 'c':
00485         result += cc();
00486         break;
00487       case 'S':
00488         result += subject();
00489         break;
00490       case '_':
00491         result += ' ';
00492         break;
00493       case 'L':
00494         result += "\n";
00495         break;
00496       case '%':
00497         result += '%';
00498         break;
00499       default:
00500         result += '%';
00501         result += ch;
00502         break;
00503       }
00504     } else
00505       result += ch;
00506   }
00507   return result;
00508 }
00509 
00510 static void removeTrailingSpace( TQString &line )
00511 {
00512    int i = line.length()-1;
00513    while( (i >= 0) && ((line[i] == ' ') || (line[i] == '\t')))
00514       i--;
00515    line.truncate( i+1);
00516 }
00517 
00518 static TQString splitLine( TQString &line)
00519 {
00520     removeTrailingSpace( line );
00521     int i = 0;
00522     int j = -1;
00523     int l = line.length();
00524 
00525     // TODO: Replace tabs with spaces first.
00526 
00527     while(i < l)
00528     {
00529        TQChar c = line[i];
00530        if ((c == '>') || (c == ':') || (c == '|'))
00531           j = i+1;
00532        else if ((c != ' ') && (c != '\t'))
00533           break;
00534        i++;
00535     }
00536 
00537     if ( j <= 0 )
00538     {
00539        return "";
00540     }
00541     if ( i == l )
00542     {
00543        TQString result = line.left(j);
00544        line = TQString::null;
00545        return result;
00546     }
00547 
00548     TQString result = line.left(j);
00549     line = line.mid(j);
00550     return result;
00551 }
00552 
00553 static TQString flowText(TQString &text, const TQString& indent, int maxLength)
00554 {
00555    maxLength--;
00556    if (text.isEmpty())
00557    {
00558       return indent+"<NULL>\n";
00559    }
00560    TQString result;
00561    while (1)
00562    {
00563       int i;
00564       if ((int) text.length() > maxLength)
00565       {
00566          i = maxLength;
00567          while( (i >= 0) && (text[i] != ' '))
00568             i--;
00569          if (i <= 0)
00570          {
00571             // Couldn't break before maxLength.
00572             i = maxLength;
00573 //            while( (i < (int) text.length()) && (text[i] != ' '))
00574 //               i++;
00575          }
00576       }
00577       else
00578       {
00579          i = text.length();
00580       }
00581 
00582       TQString line = text.left(i);
00583       if (i < (int) text.length())
00584          text = text.mid(i);
00585       else
00586          text = TQString::null;
00587 
00588       result += indent + line + '\n';
00589 
00590       if (text.isEmpty())
00591          return result;
00592    }
00593 }
00594 
00595 static bool flushPart(TQString &msg, TQStringList &part,
00596                       const TQString &indent, int maxLength)
00597 {
00598    maxLength -= indent.length();
00599    if (maxLength < 20) maxLength = 20;
00600 
00601    // Remove empty lines at end of quote
00602    while ((part.begin() != part.end()) && part.last().isEmpty())
00603    {
00604       part.remove(part.fromLast());
00605    }
00606 
00607    TQString text;
00608    for(TQStringList::Iterator it2 = part.begin();
00609        it2 != part.end();
00610        it2++)
00611    {
00612       TQString line = (*it2);
00613 
00614       if (line.isEmpty())
00615       {
00616          if (!text.isEmpty())
00617             msg += flowText(text, indent, maxLength);
00618          msg += indent + '\n';
00619       }
00620       else
00621       {
00622          if (text.isEmpty())
00623             text = line;
00624          else
00625             text += ' '+line.stripWhiteSpace();
00626 
00627          if (((int) text.length() < maxLength) || ((int) line.length() < (maxLength-10)))
00628             msg += flowText(text, indent, maxLength);
00629       }
00630    }
00631    if (!text.isEmpty())
00632       msg += flowText(text, indent, maxLength);
00633 
00634    bool appendEmptyLine = true;
00635    if (!part.count())
00636      appendEmptyLine = false;
00637 
00638    part.clear();
00639    return appendEmptyLine;
00640 }
00641 
00642 static TQString stripSignature( const TQString & msg, bool clearSigned ) {
00643   if ( clearSigned )
00644     return msg.left( msg.findRev( TQRegExp( "\n--\\s?\n" ) ) );
00645   else
00646     return msg.left( msg.findRev( "\n-- \n" ) );
00647 }
00648 
00649 TQString KMMessage::smartQuote( const TQString & msg, int maxLineLength )
00650 {
00651   TQStringList part;
00652   TQString oldIndent;
00653   bool firstPart = true;
00654 
00655 
00656   const TQStringList lines = TQStringList::split('\n', msg, true);
00657 
00658   TQString result;
00659   for(TQStringList::const_iterator it = lines.begin();
00660       it != lines.end();
00661       ++it)
00662   {
00663      TQString line = *it;
00664 
00665      const TQString indent = splitLine( line );
00666 
00667      if ( line.isEmpty())
00668      {
00669         if (!firstPart)
00670            part.append(TQString::null);
00671         continue;
00672      };
00673 
00674      if (firstPart)
00675      {
00676         oldIndent = indent;
00677         firstPart = false;
00678      }
00679 
00680      if (oldIndent != indent)
00681      {
00682         TQString fromLine;
00683         // Search if the last non-blank line could be "From" line
00684         if (part.count() && (oldIndent.length() < indent.length()))
00685         {
00686            TQStringList::Iterator it2 = part.fromLast();
00687            while( (it2 != part.end()) && (*it2).isEmpty())
00688              --it2;
00689 
00690            if ((it2 != part.end()) && ((*it2).endsWith(":")))
00691            {
00692               fromLine = oldIndent + (*it2) + '\n';
00693               part.remove(it2);
00694            }
00695         }
00696         if (flushPart( result, part, oldIndent, maxLineLength))
00697         {
00698            if (oldIndent.length() > indent.length())
00699               result += indent + '\n';
00700            else
00701               result += oldIndent + '\n';
00702         }
00703         if (!fromLine.isEmpty())
00704         {
00705            result += fromLine;
00706         }
00707         oldIndent = indent;
00708      }
00709      part.append(line);
00710   }
00711   flushPart( result, part, oldIndent, maxLineLength);
00712   return result;
00713 }
00714 
00715 
00716 //-----------------------------------------------------------------------------
00717 void KMMessage::parseTextStringFromDwPart( partNode * root,
00718                                            TQCString& parsedString,
00719                                            const TQTextCodec*& codec,
00720                                            bool& isHTML ) const
00721 {
00722   if ( !root ) return;
00723 
00724   isHTML = false;
00725   partNode * curNode = root->findType( DwMime::kTypeText,
00726                                DwMime::kSubtypeUnknown,
00727                                true,
00728                                false );
00729   kdDebug(5006) << "\n\n======= KMMessage::parseTextStringFromDwPart()   -    "
00730                 << ( curNode ? "text part found!\n" : "sorry, no text node!\n" ) << endl;
00731   if( curNode ) {
00732     isHTML = DwMime::kSubtypeHtml == curNode->subType();
00733     // now parse the TEXT message part we want to quote
00734     ObjectTreeParser otp( 0, 0, true, false, true );
00735     otp.parseObjectTree( curNode );
00736     parsedString = otp.rawReplyString();
00737     codec = curNode->msgPart().codec();
00738   }
00739 }
00740 
00741 //-----------------------------------------------------------------------------
00742 
00743 TQString KMMessage::asPlainTextFromObjectTree( partNode *root, bool aStripSignature,
00744                                               bool allowDecryption ) const
00745 {
00746   Q_ASSERT( root );
00747   Q_ASSERT( root->processed() );
00748 
00749   TQCString parsedString;
00750   bool isHTML = false;
00751   const TQTextCodec * codec = 0;
00752 
00753   if ( !root ) return TQString::null;
00754   parseTextStringFromDwPart( root, parsedString, codec, isHTML );
00755 
00756   if ( mOverrideCodec || !codec )
00757     codec = this->codec();
00758 
00759   if ( parsedString.isEmpty() )
00760     return TQString::null;
00761 
00762   bool clearSigned = false;
00763   TQString result;
00764 
00765   // decrypt
00766   if ( allowDecryption ) {
00767     TQPtrList<Kpgp::Block> pgpBlocks;
00768     TQStrList nonPgpBlocks;
00769     if ( Kpgp::Module::prepareMessageForDecryption( parsedString,
00770                 pgpBlocks,
00771                 nonPgpBlocks ) ) {
00772       // Only decrypt/strip off the signature if there is only one OpenPGP
00773       // block in the message
00774       if ( pgpBlocks.count() == 1 ) {
00775         Kpgp::Block * block = pgpBlocks.first();
00776         if ( block->type() == Kpgp::PgpMessageBlock ||
00777              block->type() == Kpgp::ClearsignedBlock ) {
00778           if ( block->type() == Kpgp::PgpMessageBlock ) {
00779             // try to decrypt this OpenPGP block
00780             block->decrypt();
00781           } else {
00782             // strip off the signature
00783             block->verify();
00784             clearSigned = true;
00785           }
00786 
00787           result = codec->toUnicode( nonPgpBlocks.first() )
00788                  + codec->toUnicode( block->text() )
00789                  + codec->toUnicode( nonPgpBlocks.last() );
00790         }
00791       }
00792     }
00793   }
00794 
00795   if ( result.isEmpty() ) {
00796     result = codec->toUnicode( parsedString );
00797     if ( result.isEmpty() )
00798       return result;
00799   }
00800 
00801   // html -> plaintext conversion, if necessary:
00802   if ( isHTML && mDecodeHTML ) {
00803     KHTMLPart htmlPart;
00804     htmlPart.setOnlyLocalReferences( true );
00805     htmlPart.setMetaRefreshEnabled( false );
00806     htmlPart.setPluginsEnabled( false );
00807     htmlPart.setJScriptEnabled( false );
00808     htmlPart.setJavaEnabled( false );
00809     htmlPart.begin();
00810     htmlPart.write( result );
00811     htmlPart.end();
00812     htmlPart.selectAll();
00813     result = htmlPart.selectedText();
00814   }
00815 
00816   // strip the signature (footer):
00817   if ( aStripSignature )
00818     return stripSignature( result, clearSigned );
00819   else
00820     return result;
00821 }
00822 
00823 //-----------------------------------------------------------------------------
00824 
00825 TQString KMMessage::asPlainText( bool aStripSignature, bool allowDecryption ) const
00826 {
00827   partNode *root = partNode::fromMessage( this );
00828   if ( !root )
00829     return TQString::null;
00830 
00831   ObjectTreeParser otp;
00832   otp.parseObjectTree( root );
00833   TQString result = asPlainTextFromObjectTree( root, aStripSignature, allowDecryption );
00834   delete root;
00835   return result;
00836 }
00837 
00838 TQString KMMessage::asQuotedString( const TQString& aHeaderStr,
00839                    const TQString& aIndentStr,
00840                    const TQString& selection /* = TQString::null */,
00841                    bool aStripSignature /* = true */,
00842                    bool allowDecryption /* = true */) const
00843 {
00844   TQString content = selection.isEmpty() ?
00845     asPlainText( aStripSignature, allowDecryption ) : selection ;
00846 
00847   // Remove blank lines at the beginning:
00848   const int firstNonWS = content.find( TQRegExp( "\\S" ) );
00849   const int lineStart = content.findRev( '\n', firstNonWS );
00850   if ( lineStart >= 0 )
00851     content.remove( 0, static_cast<unsigned int>( lineStart ) );
00852 
00853   const TQString indentStr = formatString( aIndentStr );
00854 
00855   content.replace( '\n', '\n' + indentStr );
00856   content.prepend( indentStr );
00857   content += '\n';
00858 
00859   const TQString headerStr = formatString( aHeaderStr );
00860   if ( sSmartQuote && sWordWrap )
00861     return headerStr + smartQuote( content, sWrapCol );
00862   return headerStr + content;
00863 }
00864 
00865 //-----------------------------------------------------------------------------
00866 KMMessage* KMMessage::createReply( KMail::ReplyStrategy replyStrategy,
00867                                    TQString selection /* = TQString::null */,
00868                                    bool noQuote /* = false */,
00869                                    bool allowDecryption /* = true */,
00870                                    const TQString &tmpl /* = TQString::null */ )
00871 {
00872   KMMessage* msg = new KMMessage;
00873   TQString mailingListStr, replyToStr, toStr;
00874   TQStringList mailingListAddresses;
00875   TQCString refStr, headerName;
00876   bool replyAll = true;
00877 
00878   msg->initFromMessage(this);
00879 
00880   MailingList::name(this, headerName, mailingListStr);
00881   replyToStr = replyTo();
00882 
00883   msg->setCharset("utf-8");
00884 
00885   // determine the mailing list posting address
00886   if ( parent() && parent()->isMailingListEnabled() &&
00887        !parent()->mailingListPostAddress().isEmpty() ) {
00888     mailingListAddresses << parent()->mailingListPostAddress();
00889   }
00890   if ( headerField("List-Post").find( "mailto:", 0, false ) != -1 ) {
00891     TQString listPost = headerField("List-Post");
00892     TQRegExp rx( "<mailto:([^@>]+)@([^>]+)>", false );
00893     if ( rx.search( listPost, 0 ) != -1 ) // matched
00894       mailingListAddresses << rx.cap(1) + '@' + rx.cap(2);
00895   }
00896 
00897   // use the "On ... Joe User wrote:" header by default
00898   switch( replyStrategy ) {
00899   case KMail::ReplySmart : {
00900     if ( !headerField( "Mail-Followup-To" ).isEmpty() ) {
00901       toStr = headerField( "Mail-Followup-To" );
00902     }
00903     else if ( !replyToStr.isEmpty() ) {
00904       // assume a Reply-To header mangling mailing list
00905       toStr = replyToStr;
00906     }
00907     else if ( !mailingListAddresses.isEmpty() ) {
00908       toStr = mailingListAddresses[0];
00909     }
00910     else {
00911       // doesn't seem to be a mailing list, reply to From: address
00912       toStr = from();
00913       //replyStr = sReplyStr; // reply to author, so use "On ... you wrote:"
00914       replyAll = false;
00915     }
00916     // strip all my addresses from the list of recipients
00917     TQStringList recipients = KPIM::splitEmailAddrList( toStr );
00918     toStr = stripMyAddressesFromAddressList( recipients ).join(", ");
00919     // ... unless the list contains only my addresses (reply to self)
00920     if ( toStr.isEmpty() && !recipients.isEmpty() )
00921       toStr = recipients[0];
00922 
00923     break;
00924   }
00925   case KMail::ReplyList : {
00926     if ( !headerField( "Mail-Followup-To" ).isEmpty() ) {
00927       toStr = headerField( "Mail-Followup-To" );
00928     }
00929     else if ( !mailingListAddresses.isEmpty() ) {
00930       toStr = mailingListAddresses[0];
00931     }
00932     else if ( !replyToStr.isEmpty() ) {
00933       // assume a Reply-To header mangling mailing list
00934       toStr = replyToStr;
00935     }
00936     // strip all my addresses from the list of recipients
00937     TQStringList recipients = KPIM::splitEmailAddrList( toStr );
00938     toStr = stripMyAddressesFromAddressList( recipients ).join(", ");
00939 
00940     break;
00941   }
00942   case KMail::ReplyAll : {
00943     TQStringList recipients;
00944     TQStringList ccRecipients;
00945 
00946     // add addresses from the Reply-To header to the list of recipients
00947     if( !replyToStr.isEmpty() ) {
00948       recipients += KPIM::splitEmailAddrList( replyToStr );
00949       // strip all possible mailing list addresses from the list of Reply-To
00950       // addresses
00951       for ( TQStringList::const_iterator it = mailingListAddresses.begin();
00952             it != mailingListAddresses.end();
00953             ++it ) {
00954         recipients = stripAddressFromAddressList( *it, recipients );
00955       }
00956     }
00957 
00958     if ( !mailingListAddresses.isEmpty() ) {
00959       // this is a mailing list message
00960       if ( recipients.isEmpty() && !from().isEmpty() ) {
00961         // The sender didn't set a Reply-to address, so we add the From
00962         // address to the list of CC recipients.
00963         ccRecipients += from();
00964         kdDebug(5006) << "Added " << from() << " to the list of CC recipients"
00965                       << endl;
00966       }
00967       // if it is a mailing list, add the posting address
00968       recipients.prepend( mailingListAddresses[0] );
00969     }
00970     else {
00971       // this is a normal message
00972       if ( recipients.isEmpty() && !from().isEmpty() ) {
00973         // in case of replying to a normal message only then add the From
00974         // address to the list of recipients if there was no Reply-to address
00975         recipients += from();
00976         kdDebug(5006) << "Added " << from() << " to the list of recipients"
00977                       << endl;
00978       }
00979     }
00980 
00981     // strip all my addresses from the list of recipients
00982     toStr = stripMyAddressesFromAddressList( recipients ).join(", ");
00983 
00984     // merge To header and CC header into a list of CC recipients
00985     if( !cc().isEmpty() || !to().isEmpty() ) {
00986       TQStringList list;
00987       if (!to().isEmpty())
00988         list += KPIM::splitEmailAddrList(to());
00989       if (!cc().isEmpty())
00990         list += KPIM::splitEmailAddrList(cc());
00991       for( TQStringList::Iterator it = list.begin(); it != list.end(); ++it ) {
00992         if(    !addressIsInAddressList( *it, recipients )
00993             && !addressIsInAddressList( *it, ccRecipients ) ) {
00994           ccRecipients += *it;
00995           kdDebug(5006) << "Added " << *it << " to the list of CC recipients"
00996                         << endl;
00997         }
00998       }
00999     }
01000 
01001     if ( !ccRecipients.isEmpty() ) {
01002       // strip all my addresses from the list of CC recipients
01003       ccRecipients = stripMyAddressesFromAddressList( ccRecipients );
01004 
01005       // in case of a reply to self toStr might be empty. if that's the case
01006       // then propagate a cc recipient to To: (if there is any).
01007       if ( toStr.isEmpty() && !ccRecipients.isEmpty() ) {
01008         toStr = ccRecipients[0];
01009         ccRecipients.pop_front();
01010       }
01011 
01012       msg->setCc( ccRecipients.join(", ") );
01013     }
01014 
01015     if ( toStr.isEmpty() && !recipients.isEmpty() ) {
01016       // reply to self without other recipients
01017       toStr = recipients[0];
01018     }
01019     break;
01020   }
01021   case KMail::ReplyAuthor : {
01022     if ( !replyToStr.isEmpty() ) {
01023       TQStringList recipients = KPIM::splitEmailAddrList( replyToStr );
01024       // strip the mailing list post address from the list of Reply-To
01025       // addresses since we want to reply in private
01026       for ( TQStringList::const_iterator it = mailingListAddresses.begin();
01027             it != mailingListAddresses.end();
01028             ++it ) {
01029         recipients = stripAddressFromAddressList( *it, recipients );
01030       }
01031       if ( !recipients.isEmpty() ) {
01032         toStr = recipients.join(", ");
01033       }
01034       else {
01035         // there was only the mailing list post address in the Reply-To header,
01036         // so use the From address instead
01037         toStr = from();
01038       }
01039     }
01040     else if ( !from().isEmpty() ) {
01041       toStr = from();
01042     }
01043     replyAll = false;
01044     break;
01045   }
01046   case KMail::ReplyNone : {
01047     // the addressees will be set by the caller
01048   }
01049   }
01050 
01051   msg->setTo(toStr);
01052 
01053   refStr = getRefStr();
01054   if (!refStr.isEmpty())
01055     msg->setReferences(refStr);
01056   //In-Reply-To = original msg-id
01057   msg->setReplyToId(msgId());
01058 
01059 //   if (!noQuote) {
01060 //     if( selectionIsBody ){
01061 //       TQCString cStr = selection.latin1();
01062 //       msg->setBody( cStr );
01063 //     }else{
01064 //       msg->setBody(asQuotedString(replyStr + "\n", sIndentPrefixStr, selection,
01065 //                sSmartQuote, allowDecryption).utf8());
01066 //     }
01067 //   }
01068 
01069   msg->setSubject( replySubject() );
01070   msg->setHeaderField( "X-KMail-QuotePrefix",
01071                                  formatString( GlobalSettings::self()->quoteString() ) );
01072   if( !noQuote ) {
01073      TemplateParser parser( msg, ( replyAll ? TemplateParser::ReplyAll : TemplateParser::Reply ) );
01074      parser.setAllowDecryption( allowDecryption );
01075      if ( GlobalSettings::quoteSelectionOnly() ) {
01076        parser.setSelection( selection );
01077      }
01078      if ( !tmpl.isEmpty() ) {
01079        parser.process( tmpl, this );
01080      } else {
01081        parser.process( this );
01082      }
01083   }
01084   // setStatus(KMMsgStatusReplied);
01085   msg->link(this, KMMsgStatusReplied);
01086 
01087   if ( parent() && parent()->putRepliesInSameFolder() )
01088     msg->setFcc( parent()->idString() );
01089 
01090   // replies to an encrypted message should be encrypted as well
01091   if ( encryptionState() == KMMsgPartiallyEncrypted ||
01092        encryptionState() == KMMsgFullyEncrypted ) {
01093     msg->setEncryptionState( KMMsgFullyEncrypted );
01094   }
01095 
01096   return msg;
01097 }
01098 
01099 
01100 //-----------------------------------------------------------------------------
01101 TQCString KMMessage::getRefStr() const
01102 {
01103   TQCString firstRef, lastRef, refStr, retRefStr;
01104   int i, j;
01105 
01106   refStr = headerField("References").stripWhiteSpace().latin1();
01107 
01108   if (refStr.isEmpty())
01109     return headerField("Message-Id").latin1();
01110 
01111   i = refStr.find('<');
01112   j = refStr.find('>');
01113   firstRef = refStr.mid(i, j-i+1);
01114   if (!firstRef.isEmpty())
01115     retRefStr = firstRef + ' ';
01116 
01117   i = refStr.findRev('<');
01118   j = refStr.findRev('>');
01119 
01120   lastRef = refStr.mid(i, j-i+1);
01121   if (!lastRef.isEmpty() && lastRef != firstRef)
01122     retRefStr += lastRef + ' ';
01123 
01124   retRefStr += headerField("Message-Id").latin1();
01125   return retRefStr;
01126 }
01127 
01128 
01129 KMMessage* KMMessage::createRedirect( const TQString &toStr )
01130 {
01131   // copy the message 1:1
01132   KMMessage* msg = new KMMessage( new DwMessage( *this->mMsg ) );
01133   KMMessagePart msgPart;
01134 
01135   uint id = 0;
01136   TQString strId = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace();
01137   if ( !strId.isEmpty())
01138     id = strId.toUInt();
01139   const KPIM::Identity & ident =
01140     kmkernel->identityManager()->identityForUoidOrDefault( id );
01141 
01142   // X-KMail-Redirect-From: content
01143   TQString strByWayOf = TQString("%1 (by way of %2 <%3>)")
01144     .arg( from() )
01145     .arg( ident.fullName() )
01146     .arg( ident.primaryEmailAddress() );
01147 
01148   // Resent-From: content
01149   TQString strFrom = TQString("%1 <%2>")
01150     .arg( ident.fullName() )
01151     .arg( ident.primaryEmailAddress() );
01152 
01153   // format the current date to be used in Resent-Date:
01154   TQString origDate = msg->headerField( "Date" );
01155   msg->setDateToday();
01156   TQString newDate = msg->headerField( "Date" );
01157   // make sure the Date: header is valid
01158   if ( origDate.isEmpty() )
01159     msg->removeHeaderField( "Date" );
01160   else
01161     msg->setHeaderField( "Date", origDate );
01162 
01163   // prepend Resent-*: headers (c.f. RFC2822 3.6.6)
01164   msg->setHeaderField( "Resent-Message-ID", generateMessageId( msg->sender() ),
01165                        Structured, true);
01166   msg->setHeaderField( "Resent-Date", newDate, Structured, true );
01167   msg->setHeaderField( "Resent-To",   toStr,   Address, true );
01168   msg->setHeaderField( "Resent-From", strFrom, Address, true );
01169 
01170   msg->setHeaderField( "X-KMail-Redirect-From", strByWayOf );
01171   msg->setHeaderField( "X-KMail-Recipients", toStr, Address );
01172 
01173   msg->link(this, KMMsgStatusForwarded);
01174 
01175   return msg;
01176 }
01177 
01178 
01179 //-----------------------------------------------------------------------------
01180 TQCString KMMessage::createForwardBody()
01181 {
01182   TQString s;
01183   TQCString str;
01184 
01185   if (sHeaderStrategy == HeaderStrategy::all()) {
01186     s = "\n\n----------  " + sForwardStr + "  ----------\n\n";
01187     s += headerAsString();
01188     str = asQuotedString(s, "", TQString::null, false, false).utf8();
01189     str += "\n-------------------------------------------------------\n";
01190   } else {
01191     s = "\n\n----------  " + sForwardStr + "  ----------\n\n";
01192     s += "Subject: " + subject() + "\n";
01193     s += "Date: "
01194          + KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized,
01195                                              date(), sReplyLanguage, false )
01196          + "\n";
01197     s += "From: " + from() + "\n";
01198     s += "To: " + to() + "\n";
01199     if (!cc().isEmpty()) s += "Cc: " + cc() + "\n";
01200     s += "\n";
01201     str = asQuotedString(s, "", TQString::null, false, false).utf8();
01202     str += "\n-------------------------------------------------------\n";
01203   }
01204 
01205   return str;
01206 }
01207 
01208 void KMMessage::sanitizeHeaders( const TQStringList& whiteList )
01209 {
01210    // Strip out all headers apart from the content description and other
01211    // whitelisted ones, because we don't want to inherit them.
01212    DwHeaders& header = mMsg->Headers();
01213    DwField* field = header.FirstField();
01214    DwField* nextField;
01215    while (field)
01216    {
01217      nextField = field->Next();
01218      if ( field->FieldNameStr().find( "ontent" ) == DwString::npos
01219              && !whiteList.contains( TQString::fromLatin1( field->FieldNameStr().c_str() ) ) )
01220        header.RemoveField(field);
01221      field = nextField;
01222    }
01223    mMsg->Assemble();
01224 }
01225 
01226 //-----------------------------------------------------------------------------
01227 KMMessage* KMMessage::createForward( const TQString &tmpl /* = TQString::null */ )
01228 {
01229   KMMessage* msg = new KMMessage();
01230 
01231   // If this is a multipart mail or if the main part is only the text part,
01232   // Make an identical copy of the mail, minus headers, so attachments are
01233   // preserved
01234   if ( type() == DwMime::kTypeMultipart ||
01235      ( type() == DwMime::kTypeText && subtype() == DwMime::kSubtypePlain ) ) {
01236     // ## slow, we could probably use: delete msg->mMsg; msg->mMsg = new DwMessage( this->mMsg );
01237     msg->fromDwString( this->asDwString() );
01238     // remember the type and subtype, initFromMessage sets the contents type to
01239     // text/plain, via initHeader, for unclear reasons
01240     DwMediaType oldContentType = msg->mMsg->Headers().ContentType();
01241 
01242     msg->sanitizeHeaders();
01243 
01244     // strip blacklisted parts
01245     TQStringList blacklist = GlobalSettings::self()->mimetypesToStripWhenInlineForwarding();
01246     for ( TQStringList::Iterator it = blacklist.begin(); it != blacklist.end(); ++it ) {
01247       TQString entry = (*it);
01248       int sep = entry.find( '/' );
01249       TQCString type = entry.left( sep ).latin1();
01250       TQCString subtype = entry.mid( sep+1 ).latin1();
01251       kdDebug( 5006 ) << "Looking for blacklisted type: " << type << "/" << subtype << endl;
01252       while ( DwBodyPart * part = msg->findDwBodyPart( type, subtype ) ) {
01253         msg->mMsg->Body().RemoveBodyPart( part );
01254       }
01255     }
01256     msg->mMsg->Assemble();
01257     msg->initFromMessage( this );
01258 
01259     //restore type
01260     msg->mMsg->Headers().ContentType().FromString( oldContentType.AsString() );
01261     msg->mMsg->Headers().ContentType().Parse();
01262     msg->mMsg->Assemble();
01263   }
01264   else if( type() == DwMime::kTypeText && subtype() == DwMime::kSubtypeHtml ) {
01265     // This is non-multipart html mail. Let`s make it text/plain and allow
01266     // template parser do the hard job.
01267     msg->initFromMessage( this );
01268     msg->setType( DwMime::kTypeText );
01269     msg->setSubtype( DwMime::kSubtypeHtml );
01270     msg->mNeedsAssembly = true;
01271     msg->cleanupHeader();
01272   }
01273   else {
01274     // This is a non-multipart, non-text mail (e.g. text/calendar). Construct
01275     // a multipart/mixed mail and add the original body as an attachment.
01276     msg->initFromMessage( this );
01277     msg->removeHeaderField("Content-Type");
01278     msg->removeHeaderField("Content-Transfer-Encoding");
01279     // Modify the ContentType directly (replaces setAutomaticFields(true))
01280     DwHeaders & header = msg->mMsg->Headers();
01281     header.MimeVersion().FromString("1.0");
01282     DwMediaType & contentType = msg->dwContentType();
01283     contentType.SetType( DwMime::kTypeMultipart );
01284     contentType.SetSubtype( DwMime::kSubtypeMixed );
01285     contentType.CreateBoundary(0);
01286     contentType.Assemble();
01287 
01288     // empty text part
01289     KMMessagePart msgPart;
01290     bodyPart( 0, &msgPart );
01291     msg->addBodyPart(&msgPart);
01292     // the old contents of the mail
01293     KMMessagePart secondPart;
01294     secondPart.setType( type() );
01295     secondPart.setSubtype( subtype() );
01296     secondPart.setBody( mMsg->Body().AsString() );
01297     // use the headers of the original mail
01298     applyHeadersToMessagePart( mMsg->Headers(), &secondPart );
01299     msg->addBodyPart(&secondPart);
01300     msg->mNeedsAssembly = true;
01301     msg->cleanupHeader();
01302   }
01303   // TQString st = TQString::fromUtf8(createForwardBody());
01304 
01305   msg->setSubject( forwardSubject() );
01306 
01307   TemplateParser parser( msg, TemplateParser::Forward );
01308   if ( !tmpl.isEmpty() ) {
01309     parser.process( tmpl, this );
01310   } else {
01311     parser.process( this );
01312   }
01313 
01314   // TQCString encoding = autoDetectCharset(charset(), sPrefCharsets, msg->body());
01315   // if (encoding.isEmpty()) encoding = "utf-8";
01316   // msg->setCharset(encoding);
01317 
01318   // force utf-8
01319   // msg->setCharset( "utf-8" );
01320 
01321   msg->link(this, KMMsgStatusForwarded);
01322   return msg;
01323 }
01324 
01325 static const struct {
01326   const char * dontAskAgainID;
01327   bool         canDeny;
01328   const char * text;
01329 } mdnMessageBoxes[] = {
01330   { "mdnNormalAsk", true,
01331     I18N_NOOP("This message contains a request to return a notification "
01332           "about your reception of the message.\n"
01333           "You can either ignore the request or let KMail send a "
01334           "\"denied\" or normal response.") },
01335   { "mdnUnknownOption", false,
01336     I18N_NOOP("This message contains a request to send a notification "
01337           "about your reception of the message.\n"
01338           "It contains a processing instruction that is marked as "
01339           "\"required\", but which is unknown to KMail.\n"
01340           "You can either ignore the request or let KMail send a "
01341           "\"failed\" response.") },
01342   { "mdnMultipleAddressesInReceiptTo", true,
01343     I18N_NOOP("This message contains a request to send a notification "
01344           "about your reception of the message,\n"
01345           "but it is requested to send the notification to more "
01346           "than one address.\n"
01347           "You can either ignore the request or let KMail send a "
01348           "\"denied\" or normal response.") },
01349   { "mdnReturnPathEmpty", true,
01350     I18N_NOOP("This message contains a request to send a notification "
01351           "about your reception of the message,\n"
01352           "but there is no return-path set.\n"
01353           "You can either ignore the request or let KMail send a "
01354           "\"denied\" or normal response.") },
01355   { "mdnReturnPathNotInReceiptTo", true,
01356     I18N_NOOP("This message contains a request to send a notification "
01357           "about your reception of the message,\n"
01358           "but the return-path address differs from the address "
01359           "the notification was requested to be sent to.\n"
01360           "You can either ignore the request or let KMail send a "
01361           "\"denied\" or normal response.") },
01362 };
01363 
01364 static const int numMdnMessageBoxes
01365       = sizeof mdnMessageBoxes / sizeof *mdnMessageBoxes;
01366 
01367 
01368 static int requestAdviceOnMDN( const char * what ) {
01369   for ( int i = 0 ; i < numMdnMessageBoxes ; ++i ) {
01370     if ( !qstrcmp( what, mdnMessageBoxes[i].dontAskAgainID ) ) {
01371       if ( mdnMessageBoxes[i].canDeny ) {
01372         const KCursorSaver saver( TQCursor::ArrowCursor );
01373         int answer = TQMessageBox::information( 0,
01374                                                i18n("Message Disposition Notification Request"),
01375                                                i18n( mdnMessageBoxes[i].text ),
01376                                                i18n("&Ignore"), i18n("Send \"&denied\""), i18n("&Send") );
01377         return answer ? answer + 1 : 0 ; // map to "mode" in createMDN
01378       } else {
01379         const KCursorSaver saver( TQCursor::ArrowCursor );
01380         int answer = TQMessageBox::information( 0,
01381                                                i18n("Message Disposition Notification Request"),
01382                                                i18n( mdnMessageBoxes[i].text ),
01383                                                i18n("&Ignore"), i18n("&Send") );
01384         return answer ? answer + 2 : 0 ; // map to "mode" in createMDN
01385       }
01386     }
01387   }
01388   kdWarning(5006) << "didn't find data for message box \""
01389                   << what << "\"" << endl;
01390   return 0;
01391 }
01392 
01393 KMMessage* KMMessage::createMDN( MDN::ActionMode a,
01394                  MDN::DispositionType d,
01395                  bool allowGUI,
01396                  TQValueList<MDN::DispositionModifier> m )
01397 {
01398   // RFC 2298: At most one MDN may be issued on behalf of each
01399   // particular recipient by their user agent.  That is, once an MDN
01400   // has been issued on behalf of a recipient, no further MDNs may be
01401   // issued on behalf of that recipient, even if another disposition
01402   // is performed on the message.
01403 //#define MDN_DEBUG 1
01404 #ifndef MDN_DEBUG
01405   if ( mdnSentState() != KMMsgMDNStateUnknown &&
01406        mdnSentState() != KMMsgMDNNone )
01407     return 0;
01408 #else
01409   char st[2]; st[0] = (char)mdnSentState(); st[1] = 0;
01410   kdDebug(5006) << "mdnSentState() == '" << st << "'" << endl;
01411 #endif
01412 
01413   // RFC 2298: An MDN MUST NOT be generated in response to an MDN.
01414   if ( findDwBodyPart( DwMime::kTypeMessage,
01415                DwMime::kSubtypeDispositionNotification ) ) {
01416     setMDNSentState( KMMsgMDNIgnore );
01417     return 0;
01418   }
01419 
01420   // extract where to send to:
01421   TQString receiptTo = headerField("Disposition-Notification-To");
01422   if ( receiptTo.stripWhiteSpace().isEmpty() ) return 0;
01423   receiptTo.remove( '\n' );
01424 
01425 
01426   MDN::SendingMode s = MDN::SentAutomatically; // set to manual if asked user
01427   TQString special; // fill in case of error, warning or failure
01428   KConfigGroup mdnConfig( KMKernel::config(), "MDN" );
01429 
01430   // default:
01431   int mode = mdnConfig.readNumEntry( "default-policy", 0 );
01432   if ( !mode || mode < 0 || mode > 3 ) {
01433     // early out for ignore:
01434     setMDNSentState( KMMsgMDNIgnore );
01435     return 0;
01436   }
01437 
01438   // RFC 2298: An importance of "required" indicates that
01439   // interpretation of the parameter is necessary for proper
01440   // generation of an MDN in response to this request.  If a UA does
01441   // not understand the meaning of the parameter, it MUST NOT generate
01442   // an MDN with any disposition type other than "failed" in response
01443   // to the request.
01444   TQString notificationOptions = headerField("Disposition-Notification-Options");
01445   if ( notificationOptions.contains( "required", false ) ) {
01446     // ### hacky; should parse...
01447     // There is a required option that we don't understand. We need to
01448     // ask the user what we should do:
01449     if ( !allowGUI ) return 0; // don't setMDNSentState here!
01450     mode = requestAdviceOnMDN( "mdnUnknownOption" );
01451     s = MDN::SentManually;
01452 
01453     special = i18n("Header \"Disposition-Notification-Options\" contained "
01454            "required, but unknown parameter");
01455     d = MDN::Failed;
01456     m.clear(); // clear modifiers
01457   }
01458 
01459   // RFC 2298: [ Confirmation from the user SHOULD be obtained (or no
01460   // MDN sent) ] if there is more than one distinct address in the
01461   // Disposition-Notification-To header.
01462   kdDebug(5006) << "KPIM::splitEmailAddrList(receiptTo): "
01463         << KPIM::splitEmailAddrList(receiptTo).join("\n") << endl;
01464   if ( KPIM::splitEmailAddrList(receiptTo).count() > 1 ) {
01465     if ( !allowGUI ) return 0; // don't setMDNSentState here!
01466     mode = requestAdviceOnMDN( "mdnMultipleAddressesInReceiptTo" );
01467     s = MDN::SentManually;
01468   }
01469 
01470   // RFC 2298: MDNs SHOULD NOT be sent automatically if the address in
01471   // the Disposition-Notification-To header differs from the address
01472   // in the Return-Path header. [...] Confirmation from the user
01473   // SHOULD be obtained (or no MDN sent) if there is no Return-Path
01474   // header in the message [...]
01475   AddrSpecList returnPathList = extractAddrSpecs("Return-Path");
01476   TQString returnPath = returnPathList.isEmpty() ? TQString::null
01477     : returnPathList.front().localPart + '@' + returnPathList.front().domain ;
01478   kdDebug(5006) << "clean return path: " << returnPath << endl;
01479   if ( returnPath.isEmpty() || !receiptTo.contains( returnPath, false ) ) {
01480     if ( !allowGUI ) return 0; // don't setMDNSentState here!
01481     mode = requestAdviceOnMDN( returnPath.isEmpty() ?
01482                    "mdnReturnPathEmpty" :
01483                    "mdnReturnPathNotInReceiptTo" );
01484     s = MDN::SentManually;
01485   }
01486 
01487   if ( a != KMime::MDN::AutomaticAction ) {
01488     //TODO: only ingore user settings for AutomaticAction if requested
01489     if ( mode == 1 ) { // ask
01490       if ( !allowGUI ) return 0; // don't setMDNSentState here!
01491       mode = requestAdviceOnMDN( "mdnNormalAsk" );
01492       s = MDN::SentManually; // asked user
01493     }
01494 
01495     switch ( mode ) {
01496       case 0: // ignore:
01497         setMDNSentState( KMMsgMDNIgnore );
01498         return 0;
01499       default:
01500       case 1:
01501         kdFatal(5006) << "KMMessage::createMDN(): The \"ask\" mode should "
01502                                                   << "never appear here!" << endl;
01503         break;
01504       case 2: // deny
01505         d = MDN::Denied;
01506         m.clear();
01507         break;
01508       case 3:
01509         break;
01510     }
01511   }
01512 
01513 
01514   // extract where to send from:
01515   TQString finalRecipient = kmkernel->identityManager()
01516     ->identityForUoidOrDefault( identityUoid() ).fullEmailAddr();
01517 
01518   //
01519   // Generate message:
01520   //
01521 
01522   KMMessage * receipt = new KMMessage();
01523   receipt->initFromMessage( this );
01524   receipt->removeHeaderField("Content-Type");
01525   receipt->removeHeaderField("Content-Transfer-Encoding");
01526   // Modify the ContentType directly (replaces setAutomaticFields(true))
01527   DwHeaders & header = receipt->mMsg->Headers();
01528   header.MimeVersion().FromString("1.0");
01529   DwMediaType & contentType = receipt->dwContentType();
01530   contentType.SetType( DwMime::kTypeMultipart );
01531   contentType.SetSubtype( DwMime::kSubtypeReport );
01532   contentType.CreateBoundary(0);
01533   receipt->mNeedsAssembly = true;
01534   receipt->setContentTypeParam( "report-type", "disposition-notification" );
01535 
01536   TQString description = replaceHeadersInString( MDN::descriptionFor( d, m ) );
01537 
01538   // text/plain part:
01539   KMMessagePart firstMsgPart;
01540   firstMsgPart.setTypeStr( "text" );
01541   firstMsgPart.setSubtypeStr( "plain" );
01542   firstMsgPart.setBodyFromUnicode( description );
01543   receipt->addBodyPart( &firstMsgPart );
01544 
01545   // message/disposition-notification part:
01546   KMMessagePart secondMsgPart;
01547   secondMsgPart.setType( DwMime::kTypeMessage );
01548   secondMsgPart.setSubtype( DwMime::kSubtypeDispositionNotification );
01549   //secondMsgPart.setCharset( "us-ascii" );
01550   //secondMsgPart.setCteStr( "7bit" );
01551   secondMsgPart.setBodyEncoded( MDN::dispositionNotificationBodyContent(
01552                         finalRecipient,
01553                 rawHeaderField("Original-Recipient"),
01554                 id(), /* Message-ID */
01555                 d, a, s, m, special ) );
01556   receipt->addBodyPart( &secondMsgPart );
01557 
01558   // message/rfc822 or text/rfc822-headers body part:
01559   int num = mdnConfig.readNumEntry( "quote-message", 0 );
01560   if ( num < 0 || num > 2 ) num = 0;
01561   MDN::ReturnContent returnContent = static_cast<MDN::ReturnContent>( num );
01562 
01563   KMMessagePart thirdMsgPart;
01564   switch ( returnContent ) {
01565   case MDN::All:
01566     thirdMsgPart.setTypeStr( "message" );
01567     thirdMsgPart.setSubtypeStr( "rfc822" );
01568     thirdMsgPart.setBody( asSendableString() );
01569     receipt->addBodyPart( &thirdMsgPart );
01570     break;
01571   case MDN::HeadersOnly:
01572     thirdMsgPart.setTypeStr( "text" );
01573     thirdMsgPart.setSubtypeStr( "rfc822-headers" );
01574     thirdMsgPart.setBody( headerAsSendableString() );
01575     receipt->addBodyPart( &thirdMsgPart );
01576     break;
01577   case MDN::Nothing:
01578   default:
01579     break;
01580   };
01581 
01582   receipt->setTo( receiptTo );
01583   receipt->setSubject( "Message Disposition Notification" );
01584   receipt->setReplyToId( msgId() );
01585   receipt->setReferences( getRefStr() );
01586 
01587   receipt->cleanupHeader();
01588 
01589   kdDebug(5006) << "final message:\n" + receipt->asString() << endl;
01590 
01591   //
01592   // Set "MDN sent" status:
01593   //
01594   KMMsgMDNSentState state = KMMsgMDNStateUnknown;
01595   switch ( d ) {
01596   case MDN::Displayed:   state = KMMsgMDNDisplayed;  break;
01597   case MDN::Deleted:     state = KMMsgMDNDeleted;    break;
01598   case MDN::Dispatched:  state = KMMsgMDNDispatched; break;
01599   case MDN::Processed:   state = KMMsgMDNProcessed;  break;
01600   case MDN::Denied:      state = KMMsgMDNDenied;     break;
01601   case MDN::Failed:      state = KMMsgMDNFailed;     break;
01602   };
01603   setMDNSentState( state );
01604 
01605   return receipt;
01606 }
01607 
01608 TQString KMMessage::replaceHeadersInString( const TQString & s ) const {
01609   TQString result = s;
01610   TQRegExp rx( "\\$\\{([a-z0-9-]+)\\}", false );
01611   Q_ASSERT( rx.isValid() );
01612 
01613   TQRegExp rxDate( "\\$\\{date\\}" );
01614   Q_ASSERT( rxDate.isValid() );
01615 
01616   TQString sDate = KMime::DateFormatter::formatDate(
01617                       KMime::DateFormatter::Localized, date() );
01618 
01619   int idx = 0;
01620   if( ( idx = rxDate.search( result, idx ) ) != -1  ) {
01621     result.replace( idx, rxDate.matchedLength(), sDate );
01622   }
01623 
01624   idx = 0;
01625   while ( ( idx = rx.search( result, idx ) ) != -1 ) {
01626     TQString replacement = headerField( rx.cap(1).latin1() );
01627     result.replace( idx, rx.matchedLength(), replacement );
01628     idx += replacement.length();
01629   }
01630   return result;
01631 }
01632 
01633 KMMessage* KMMessage::createDeliveryReceipt() const
01634 {
01635   TQString str, receiptTo;
01636   KMMessage *receipt;
01637 
01638   receiptTo = headerField("Disposition-Notification-To");
01639   if ( receiptTo.stripWhiteSpace().isEmpty() ) return 0;
01640   receiptTo.remove( '\n' );
01641 
01642   receipt = new KMMessage;
01643   receipt->initFromMessage(this);
01644   receipt->setTo(receiptTo);
01645   receipt->setSubject(i18n("Receipt: ") + subject());
01646 
01647   str  = "Your message was successfully delivered.";
01648   str += "\n\n---------- Message header follows ----------\n";
01649   str += headerAsString();
01650   str += "--------------------------------------------\n";
01651   // Conversion to latin1 is correct here as Mail headers should contain
01652   // ascii only
01653   receipt->setBody(str.latin1());
01654   receipt->setAutomaticFields();
01655 
01656   return receipt;
01657 }
01658 
01659 
01660 void KMMessage::applyIdentity( uint id )
01661 {
01662   const KPIM::Identity & ident =
01663     kmkernel->identityManager()->identityForUoidOrDefault( id );
01664 
01665   if(ident.fullEmailAddr().isEmpty())
01666     setFrom("");
01667   else
01668     setFrom(ident.fullEmailAddr());
01669 
01670   if(ident.replyToAddr().isEmpty())
01671     setReplyTo("");
01672   else
01673     setReplyTo(ident.replyToAddr());
01674 
01675   if(ident.bcc().isEmpty())
01676     setBcc("");
01677   else
01678     setBcc(ident.bcc());
01679 
01680   if (ident.organization().isEmpty())
01681     removeHeaderField("Organization");
01682   else
01683     setHeaderField("Organization", ident.organization());
01684 
01685   if (ident.isDefault())
01686     removeHeaderField("X-KMail-Identity");
01687   else
01688     setHeaderField("X-KMail-Identity", TQString::number( ident.uoid() ));
01689 
01690   if ( ident.transport().isEmpty() )
01691     removeHeaderField( "X-KMail-Transport" );
01692   else
01693     setHeaderField( "X-KMail-Transport", ident.transport() );
01694 
01695   if ( ident.fcc().isEmpty() )
01696     setFcc( TQString::null );
01697   else
01698     setFcc( ident.fcc() );
01699 
01700   if ( ident.drafts().isEmpty() )
01701     setDrafts( TQString::null );
01702   else
01703     setDrafts( ident.drafts() );
01704 
01705   if ( ident.templates().isEmpty() )
01706     setTemplates( TQString::null );
01707   else
01708     setTemplates( ident.templates() );
01709 
01710 }
01711 
01712 //-----------------------------------------------------------------------------
01713 void KMMessage::initHeader( uint id )
01714 {
01715   applyIdentity( id );
01716   setTo("");
01717   setSubject("");
01718   setDateToday();
01719 
01720   setHeaderField("User-Agent", "KMail/" KMAIL_VERSION );
01721   // This will allow to change Content-Type:
01722   setHeaderField("Content-Type","text/plain");
01723 }
01724 
01725 uint KMMessage::identityUoid() const {
01726   TQString idString = headerField("X-KMail-Identity").stripWhiteSpace();
01727   bool ok = false;
01728   int id = idString.toUInt( &ok );
01729 
01730   if ( !ok || id == 0 )
01731     id = kmkernel->identityManager()->identityForAddress( to() + ", " + cc() ).uoid();
01732   if ( id == 0 && parent() )
01733     id = parent()->identity();
01734 
01735   return id;
01736 }
01737 
01738 
01739 //-----------------------------------------------------------------------------
01740 void KMMessage::initFromMessage(const KMMessage *msg, bool idHeaders)
01741 {
01742   uint id = msg->identityUoid();
01743 
01744   if ( idHeaders ) initHeader(id);
01745   else setHeaderField("X-KMail-Identity", TQString::number(id));
01746   if (!msg->headerField("X-KMail-Transport").isEmpty())
01747     setHeaderField("X-KMail-Transport", msg->headerField("X-KMail-Transport"));
01748 }
01749 
01750 
01751 //-----------------------------------------------------------------------------
01752 void KMMessage::cleanupHeader()
01753 {
01754   DwHeaders& header = mMsg->Headers();
01755   DwField* field = header.FirstField();
01756   DwField* nextField;
01757 
01758   if (mNeedsAssembly) mMsg->Assemble();
01759   mNeedsAssembly = false;
01760 
01761   while (field)
01762   {
01763     nextField = field->Next();
01764     if (field->FieldBody()->AsString().empty())
01765     {
01766       header.RemoveField(field);
01767       mNeedsAssembly = true;
01768     }
01769     field = nextField;
01770   }
01771 }
01772 
01773 
01774 //-----------------------------------------------------------------------------
01775 void KMMessage::setAutomaticFields(bool aIsMulti)
01776 {
01777   DwHeaders& header = mMsg->Headers();
01778   header.MimeVersion().FromString("1.0");
01779 
01780   if (aIsMulti || numBodyParts() > 1)
01781   {
01782     // Set the type to 'Multipart' and the subtype to 'Mixed'
01783     DwMediaType& contentType = dwContentType();
01784     contentType.SetType(   DwMime::kTypeMultipart);
01785     contentType.SetSubtype(DwMime::kSubtypeMixed );
01786 
01787     // Create a random printable string and set it as the boundary parameter
01788     contentType.CreateBoundary(0);
01789   }
01790   mNeedsAssembly = true;
01791 }
01792 
01793 
01794 //-----------------------------------------------------------------------------
01795 TQString KMMessage::dateStr() const
01796 {
01797   KConfigGroup general( KMKernel::config(), "General" );
01798   DwHeaders& header = mMsg->Headers();
01799   time_t unixTime;
01800 
01801   if (!header.HasDate()) return "";
01802   unixTime = header.Date().AsUnixTime();
01803 
01804   //kdDebug(5006)<<"####  Date = "<<header.Date().AsString().c_str()<<endl;
01805 
01806   return KMime::DateFormatter::formatDate(
01807       static_cast<KMime::DateFormatter::FormatType>(general.readNumEntry( "dateFormat", KMime::DateFormatter::Fancy )),
01808       unixTime, general.readEntry( "customDateFormat" ));
01809 }
01810 
01811 
01812 //-----------------------------------------------------------------------------
01813 TQCString KMMessage::dateShortStr() const
01814 {
01815   DwHeaders& header = mMsg->Headers();
01816   time_t unixTime;
01817 
01818   if (!header.HasDate()) return "";
01819   unixTime = header.Date().AsUnixTime();
01820 
01821   TQCString result = ctime(&unixTime);
01822   int len = result.length();
01823   if (result[len-1]=='\n')
01824     result.truncate(len-1);
01825 
01826   return result;
01827 }
01828 
01829 
01830 //-----------------------------------------------------------------------------
01831 TQString KMMessage::dateIsoStr() const
01832 {
01833   DwHeaders& header = mMsg->Headers();
01834   time_t unixTime;
01835 
01836   if (!header.HasDate()) return "";
01837   unixTime = header.Date().AsUnixTime();
01838 
01839   char cstr[64];
01840   strftime(cstr, 63, "%Y-%m-%d %H:%M:%S", localtime(&unixTime));
01841   return TQString(cstr);
01842 }
01843 
01844 
01845 //-----------------------------------------------------------------------------
01846 time_t KMMessage::date() const
01847 {
01848   time_t res = ( time_t )-1;
01849   DwHeaders& header = mMsg->Headers();
01850   if (header.HasDate())
01851     res = header.Date().AsUnixTime();
01852   return res;
01853 }
01854 
01855 
01856 //-----------------------------------------------------------------------------
01857 void KMMessage::setDateToday()
01858 {
01859   struct timeval tval;
01860   gettimeofday(&tval, 0);
01861   setDate((time_t)tval.tv_sec);
01862 }
01863 
01864 
01865 //-----------------------------------------------------------------------------
01866 void KMMessage::setDate(time_t aDate)
01867 {
01868   mDate = aDate;
01869   mMsg->Headers().Date().FromCalendarTime(aDate);
01870   mMsg->Headers().Date().Assemble();
01871   mNeedsAssembly = true;
01872   mDirty = true;
01873 }
01874 
01875 
01876 //-----------------------------------------------------------------------------
01877 void KMMessage::setDate(const TQCString& aStr)
01878 {
01879   DwHeaders& header = mMsg->Headers();
01880 
01881   header.Date().FromString(aStr);
01882   header.Date().Parse();
01883   mNeedsAssembly = true;
01884   mDirty = true;
01885 
01886   if (header.HasDate())
01887     mDate = header.Date().AsUnixTime();
01888 }
01889 
01890 
01891 //-----------------------------------------------------------------------------
01892 TQString KMMessage::to() const
01893 {
01894   // handle To same as Cc below, bug 80747
01895   TQValueList<TQCString> rawHeaders = rawHeaderFields( "To" );
01896   TQStringList headers;
01897   for ( TQValueList<TQCString>::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) {
01898     headers << *it;
01899   }
01900   return KPIM::normalizeAddressesAndDecodeIDNs( headers.join( ", " ) );
01901 }
01902 
01903 
01904 //-----------------------------------------------------------------------------
01905 void KMMessage::setTo(const TQString& aStr)
01906 {
01907   setHeaderField( "To", aStr, Address );
01908 }
01909 
01910 //-----------------------------------------------------------------------------
01911 TQString KMMessage::toStrip() const
01912 {
01913   return stripEmailAddr( to() );
01914 }
01915 
01916 //-----------------------------------------------------------------------------
01917 TQString KMMessage::replyTo() const
01918 {
01919   return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("Reply-To") );
01920 }
01921 
01922 
01923 //-----------------------------------------------------------------------------
01924 void KMMessage::setReplyTo(const TQString& aStr)
01925 {
01926   setHeaderField( "Reply-To", aStr, Address );
01927 }
01928 
01929 
01930 //-----------------------------------------------------------------------------
01931 void KMMessage::setReplyTo(KMMessage* aMsg)
01932 {
01933   setHeaderField( "Reply-To", aMsg->from(), Address );
01934 }
01935 
01936 
01937 //-----------------------------------------------------------------------------
01938 TQString KMMessage::cc() const
01939 {
01940   // get the combined contents of all Cc headers (as workaround for invalid
01941   // messages with multiple Cc headers)
01942   TQValueList<TQCString> rawHeaders = rawHeaderFields( "Cc" );
01943   TQStringList headers;
01944   for ( TQValueList<TQCString>::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) {
01945     headers << *it;
01946   }
01947   return KPIM::normalizeAddressesAndDecodeIDNs( headers.join( ", " ) );
01948 }
01949 
01950 
01951 //-----------------------------------------------------------------------------
01952 void KMMessage::setCc(const TQString& aStr)
01953 {
01954   setHeaderField( "Cc", aStr, Address );
01955 }
01956 
01957 
01958 //-----------------------------------------------------------------------------
01959 TQString KMMessage::ccStrip() const
01960 {
01961   return stripEmailAddr( cc() );
01962 }
01963 
01964 
01965 //-----------------------------------------------------------------------------
01966 TQString KMMessage::bcc() const
01967 {
01968   return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("Bcc") );
01969 }
01970 
01971 
01972 //-----------------------------------------------------------------------------
01973 void KMMessage::setBcc(const TQString& aStr)
01974 {
01975   setHeaderField( "Bcc", aStr, Address );
01976 }
01977 
01978 //-----------------------------------------------------------------------------
01979 TQString KMMessage::fcc() const
01980 {
01981   return headerField( "X-KMail-Fcc" );
01982 }
01983 
01984 
01985 //-----------------------------------------------------------------------------
01986 void KMMessage::setFcc( const TQString &aStr )
01987 {
01988   setHeaderField( "X-KMail-Fcc", aStr );
01989 }
01990 
01991 //-----------------------------------------------------------------------------
01992 void KMMessage::setDrafts( const TQString &aStr )
01993 {
01994   mDrafts = aStr;
01995 }
01996 
01997 //-----------------------------------------------------------------------------
01998 void KMMessage::setTemplates( const TQString &aStr )
01999 {
02000   mTemplates = aStr;
02001 }
02002 
02003 //-----------------------------------------------------------------------------
02004 TQString KMMessage::who() const
02005 {
02006   if (mParent)
02007     return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField(mParent->whoField().utf8()) );
02008   return from();
02009 }
02010 
02011 
02012 //-----------------------------------------------------------------------------
02013 TQString KMMessage::from() const
02014 {
02015   return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("From") );
02016 }
02017 
02018 
02019 //-----------------------------------------------------------------------------
02020 void KMMessage::setFrom(const TQString& bStr)
02021 {
02022   TQString aStr = bStr;
02023   if (aStr.isNull())
02024     aStr = "";
02025   setHeaderField( "From", aStr, Address );
02026   mDirty = true;
02027 }
02028 
02029 
02030 //-----------------------------------------------------------------------------
02031 TQString KMMessage::fromStrip() const
02032 {
02033   return stripEmailAddr( from() );
02034 }
02035 
02036 //-----------------------------------------------------------------------------
02037 TQString KMMessage::sender() const {
02038   AddrSpecList asl = extractAddrSpecs( "Sender" );
02039   if ( asl.empty() )
02040     asl = extractAddrSpecs( "From" );
02041   if ( asl.empty() )
02042     return TQString::null;
02043   return asl.front().asString();
02044 }
02045 
02046 //-----------------------------------------------------------------------------
02047 TQString KMMessage::subject() const
02048 {
02049   return headerField("Subject");
02050 }
02051 
02052 
02053 //-----------------------------------------------------------------------------
02054 void KMMessage::setSubject(const TQString& aStr)
02055 {
02056   setHeaderField("Subject",aStr);
02057   mDirty = true;
02058 }
02059 
02060 
02061 //-----------------------------------------------------------------------------
02062 TQString KMMessage::xmark() const
02063 {
02064   return headerField("X-KMail-Mark");
02065 }
02066 
02067 
02068 //-----------------------------------------------------------------------------
02069 void KMMessage::setXMark(const TQString& aStr)
02070 {
02071   setHeaderField("X-KMail-Mark", aStr);
02072   mDirty = true;
02073 }
02074 
02075 
02076 //-----------------------------------------------------------------------------
02077 TQString KMMessage::replyToId() const
02078 {
02079   int leftAngle, rightAngle;
02080   TQString replyTo, references;
02081 
02082   replyTo = headerField("In-Reply-To");
02083   // search the end of the (first) message id in the In-Reply-To header
02084   rightAngle = replyTo.find( '>' );
02085   if (rightAngle != -1)
02086     replyTo.truncate( rightAngle + 1 );
02087   // now search the start of the message id
02088   leftAngle = replyTo.findRev( '<' );
02089   if (leftAngle != -1)
02090     replyTo = replyTo.mid( leftAngle );
02091 
02092   // if we have found a good message id we can return immediately
02093   // We ignore mangled In-Reply-To headers which are created by a
02094   // misconfigured Mutt. They look like this <"from foo"@bar.baz>, i.e.
02095   // they contain double quotes and spaces. We only check for '"'.
02096   if (!replyTo.isEmpty() && (replyTo[0] == '<') &&
02097       ( -1 == replyTo.find( '"' ) ) )
02098     return replyTo;
02099 
02100   references = headerField("References");
02101   leftAngle = references.findRev( '<' );
02102   if (leftAngle != -1)
02103     references = references.mid( leftAngle );
02104   rightAngle = references.find( '>' );
02105   if (rightAngle != -1)
02106     references.truncate( rightAngle + 1 );
02107 
02108   // if we found a good message id in the References header return it
02109   if (!references.isEmpty() && references[0] == '<')
02110     return references;
02111   // else return the broken message id we found in the In-Reply-To header
02112   else
02113     return replyTo;
02114 }
02115 
02116 
02117 //-----------------------------------------------------------------------------
02118 TQString KMMessage::replyToIdMD5() const {
02119   return base64EncodedMD5( replyToId() );
02120 }
02121 
02122 //-----------------------------------------------------------------------------
02123 TQString KMMessage::references() const
02124 {
02125   int leftAngle, rightAngle;
02126   TQString references = headerField( "References" );
02127 
02128   // keep the last two entries for threading
02129   leftAngle = references.findRev( '<' );
02130   leftAngle = references.findRev( '<', leftAngle - 1 );
02131   if( leftAngle != -1 )
02132     references = references.mid( leftAngle );
02133   rightAngle = references.findRev( '>' );
02134   if( rightAngle != -1 )
02135     references.truncate( rightAngle + 1 );
02136 
02137   if( !references.isEmpty() && references[0] == '<' )
02138     return references;
02139   else
02140     return TQString::null;
02141 }
02142 
02143 //-----------------------------------------------------------------------------
02144 TQString KMMessage::replyToAuxIdMD5() const
02145 {
02146   TQString result = references();
02147   // references contains two items, use the first one
02148   // (the second to last reference)
02149   const int rightAngle = result.find( '>' );
02150   if( rightAngle != -1 )
02151     result.truncate( rightAngle + 1 );
02152 
02153   return base64EncodedMD5( result );
02154 }
02155 
02156 //-----------------------------------------------------------------------------
02157 TQString KMMessage::strippedSubjectMD5() const {
02158   return base64EncodedMD5( stripOffPrefixes( subject() ), true /*utf8*/ );
02159 }
02160 
02161 //-----------------------------------------------------------------------------
02162 TQString KMMessage::subjectMD5() const {
02163   return base64EncodedMD5( subject(), true /*utf8*/ );
02164 }
02165 
02166 //-----------------------------------------------------------------------------
02167 bool KMMessage::subjectIsPrefixed() const {
02168   return subjectMD5() != strippedSubjectMD5();
02169 }
02170 
02171 //-----------------------------------------------------------------------------
02172 void KMMessage::setReplyToId(const TQString& aStr)
02173 {
02174   setHeaderField("In-Reply-To", aStr);
02175   mDirty = true;
02176 }
02177 
02178 
02179 //-----------------------------------------------------------------------------
02180 TQString KMMessage::msgId() const
02181 {
02182   TQString msgId = headerField("Message-Id");
02183 
02184   // search the end of the message id
02185   const int rightAngle = msgId.find( '>' );
02186   if (rightAngle != -1)
02187     msgId.truncate( rightAngle + 1 );
02188   // now search the start of the message id
02189   const int leftAngle = msgId.findRev( '<' );
02190   if (leftAngle != -1)
02191     msgId = msgId.mid( leftAngle );
02192   return msgId;
02193 }
02194 
02195 
02196 //-----------------------------------------------------------------------------
02197 TQString KMMessage::msgIdMD5() const {
02198   return base64EncodedMD5( msgId() );
02199 }
02200 
02201 
02202 //-----------------------------------------------------------------------------
02203 void KMMessage::setMsgId(const TQString& aStr)
02204 {
02205   setHeaderField("Message-Id", aStr);
02206   mDirty = true;
02207 }
02208 
02209 //-----------------------------------------------------------------------------
02210 size_t KMMessage::msgSizeServer() const {
02211   return headerField( "X-Length" ).toULong();
02212 }
02213 
02214 
02215 //-----------------------------------------------------------------------------
02216 void KMMessage::setMsgSizeServer(size_t size)
02217 {
02218   setHeaderField("X-Length", TQCString().setNum(size));
02219   mDirty = true;
02220 }
02221 
02222 //-----------------------------------------------------------------------------
02223 ulong KMMessage::UID() const {
02224   return headerField( "X-UID" ).toULong();
02225 }
02226 
02227 
02228 //-----------------------------------------------------------------------------
02229 void KMMessage::setUID(ulong uid)
02230 {
02231   setHeaderField("X-UID", TQCString().setNum(uid));
02232   mDirty = true;
02233 }
02234 
02235 //-----------------------------------------------------------------------------
02236 AddressList KMMessage::splitAddrField( const TQCString & str )
02237 {
02238   AddressList result;
02239   const char * scursor = str.begin();
02240   if ( !scursor )
02241     return AddressList();
02242   const char * const send = str.begin() + str.length();
02243   if ( !parseAddressList( scursor, send, result ) )
02244     kdDebug(5006) << "Error in address splitting: parseAddressList returned false!"
02245                   << endl;
02246   return result;
02247 }
02248 
02249 AddressList KMMessage::headerAddrField( const TQCString & aName ) const {
02250   return KMMessage::splitAddrField( rawHeaderField( aName ) );
02251 }
02252 
02253 AddrSpecList KMMessage::extractAddrSpecs( const TQCString & header ) const {
02254   AddressList al = headerAddrField( header );
02255   AddrSpecList result;
02256   for ( AddressList::const_iterator ait = al.begin() ; ait != al.end() ; ++ait )
02257     for ( MailboxList::const_iterator mit = (*ait).mailboxList.begin() ; mit != (*ait).mailboxList.end() ; ++mit )
02258       result.push_back( (*mit).addrSpec );
02259   return result;
02260 }
02261 
02262 TQCString KMMessage::rawHeaderField( const TQCString & name ) const {
02263   if ( name.isEmpty() ) return TQCString();
02264 
02265   DwHeaders & header = mMsg->Headers();
02266   DwField * field = header.FindField( name );
02267 
02268   if ( !field ) return TQCString();
02269 
02270   return header.FieldBody( name.data() ).AsString().c_str();
02271 }
02272 
02273 TQValueList<TQCString> KMMessage::rawHeaderFields( const TQCString& field ) const
02274 {
02275   if ( field.isEmpty() || !mMsg->Headers().FindField( field ) )
02276     return TQValueList<TQCString>();
02277 
02278   std::vector<DwFieldBody*> v = mMsg->Headers().AllFieldBodies( field.data() );
02279   TQValueList<TQCString> headerFields;
02280   for ( uint i = 0; i < v.size(); ++i ) {
02281     headerFields.append( v[i]->AsString().c_str() );
02282   }
02283 
02284   return headerFields;
02285 }
02286 
02287 TQString KMMessage::headerField(const TQCString& aName) const
02288 {
02289   if ( aName.isEmpty() )
02290     return TQString::null;
02291 
02292   if ( !mMsg->Headers().FindField( aName ) )
02293     return TQString::null;
02294 
02295   return decodeRFC2047String( mMsg->Headers().FieldBody( aName.data() ).AsString().c_str(),
02296                               charset() );
02297 
02298 }
02299 
02300 TQStringList KMMessage::headerFields( const TQCString& field ) const
02301 {
02302   if ( field.isEmpty() || !mMsg->Headers().FindField( field ) )
02303     return TQStringList();
02304 
02305   std::vector<DwFieldBody*> v = mMsg->Headers().AllFieldBodies( field.data() );
02306   TQStringList headerFields;
02307   for ( uint i = 0; i < v.size(); ++i ) {
02308     headerFields.append( decodeRFC2047String( v[i]->AsString().c_str(), charset() ) );
02309   }
02310 
02311   return headerFields;
02312 }
02313 
02314 //-----------------------------------------------------------------------------
02315 void KMMessage::removeHeaderField(const TQCString& aName)
02316 {
02317   DwHeaders & header = mMsg->Headers();
02318   DwField * field = header.FindField(aName);
02319   if (!field) return;
02320 
02321   header.RemoveField(field);
02322   mNeedsAssembly = true;
02323 }
02324 
02325 //-----------------------------------------------------------------------------
02326 void KMMessage::removeHeaderFields(const TQCString& aName)
02327 {
02328   DwHeaders & header = mMsg->Headers();
02329   while ( DwField * field = header.FindField(aName) ) {
02330     header.RemoveField(field);
02331     mNeedsAssembly = true;
02332   }
02333 }
02334 
02335 
02336 //-----------------------------------------------------------------------------
02337 void KMMessage::setHeaderField( const TQCString& aName, const TQString& bValue,
02338                                 HeaderFieldType type, bool prepend )
02339 {
02340 #if 0
02341   if ( type != Unstructured )
02342     kdDebug(5006) << "KMMessage::setHeaderField( \"" << aName << "\", \""
02343                 << bValue << "\", " << type << " )" << endl;
02344 #endif
02345   if (aName.isEmpty()) return;
02346 
02347   DwHeaders& header = mMsg->Headers();
02348 
02349   DwString str;
02350   DwField* field;
02351   TQCString aValue;
02352   if (!bValue.isEmpty())
02353   {
02354     TQString value = bValue;
02355     if ( type == Address )
02356       value = KPIM::normalizeAddressesAndEncodeIDNs( value );
02357 #if 0
02358     if ( type != Unstructured )
02359       kdDebug(5006) << "value: \"" << value << "\"" << endl;
02360 #endif
02361     TQCString encoding = autoDetectCharset( charset(), sPrefCharsets, value );
02362     if (encoding.isEmpty())
02363        encoding = "utf-8";
02364     aValue = encodeRFC2047String( value, encoding );
02365 #if 0
02366     if ( type != Unstructured )
02367       kdDebug(5006) << "aValue: \"" << aValue << "\"" << endl;
02368 #endif
02369   }
02370   str = aName;
02371   if (str[str.length()-1] != ':') str += ": ";
02372   else str += ' ';
02373   if ( !aValue.isEmpty() )
02374     str += aValue;
02375   if (str[str.length()-1] != '\n') str += '\n';
02376 
02377   field = new DwField(str, mMsg);
02378   field->Parse();
02379 
02380   if ( prepend )
02381     header.AddFieldAt( 1, field );
02382   else
02383     header.AddOrReplaceField( field );
02384   mNeedsAssembly = true;
02385 }
02386 
02387 
02388 //-----------------------------------------------------------------------------
02389 TQCString KMMessage::typeStr() const
02390 {
02391   DwHeaders& header = mMsg->Headers();
02392   if (header.HasContentType()) return header.ContentType().TypeStr().c_str();
02393   else return "";
02394 }
02395 
02396 
02397 //-----------------------------------------------------------------------------
02398 int KMMessage::type() const
02399 {
02400   DwHeaders& header = mMsg->Headers();
02401   if (header.HasContentType()) return header.ContentType().Type();
02402   else return DwMime::kTypeNull;
02403 }
02404 
02405 
02406 //-----------------------------------------------------------------------------
02407 void KMMessage::setTypeStr(const TQCString& aStr)
02408 {
02409   dwContentType().SetTypeStr(DwString(aStr));
02410   dwContentType().Parse();
02411   mNeedsAssembly = true;
02412 }
02413 
02414 
02415 //-----------------------------------------------------------------------------
02416 void KMMessage::setType(int aType)
02417 {
02418   dwContentType().SetType(aType);
02419   dwContentType().Assemble();
02420   mNeedsAssembly = true;
02421 }
02422 
02423 
02424 
02425 //-----------------------------------------------------------------------------
02426 TQCString KMMessage::subtypeStr() const
02427 {
02428   DwHeaders& header = mMsg->Headers();
02429   if (header.HasContentType()) return header.ContentType().SubtypeStr().c_str();
02430   else return "";
02431 }
02432 
02433 
02434 //-----------------------------------------------------------------------------
02435 int KMMessage::subtype() const
02436 {
02437   DwHeaders& header = mMsg->Headers();
02438   if (header.HasContentType()) return header.ContentType().Subtype();
02439   else return DwMime::kSubtypeNull;
02440 }
02441 
02442 
02443 //-----------------------------------------------------------------------------
02444 void KMMessage::setSubtypeStr(const TQCString& aStr)
02445 {
02446   dwContentType().SetSubtypeStr(DwString(aStr));
02447   dwContentType().Parse();
02448   mNeedsAssembly = true;
02449 }
02450 
02451 
02452 //-----------------------------------------------------------------------------
02453 void KMMessage::setSubtype(int aSubtype)
02454 {
02455   dwContentType().SetSubtype(aSubtype);
02456   dwContentType().Assemble();
02457   mNeedsAssembly = true;
02458 }
02459 
02460 
02461 //-----------------------------------------------------------------------------
02462 void KMMessage::setDwMediaTypeParam( DwMediaType &mType,
02463                                      const TQCString& attr,
02464                                      const TQCString& val )
02465 {
02466   mType.Parse();
02467   DwParameter *param = mType.FirstParameter();
02468   while(param) {
02469     if (!kasciistricmp(param->Attribute().c_str(), attr))
02470       break;
02471     else
02472       param = param->Next();
02473   }
02474   if (!param){
02475     param = new DwParameter;
02476     param->SetAttribute(DwString( attr ));
02477     mType.AddParameter( param );
02478   }
02479   else
02480     mType.SetModified();
02481   param->SetValue(DwString( val ));
02482   mType.Assemble();
02483 }
02484 
02485 
02486 //-----------------------------------------------------------------------------
02487 void KMMessage::setContentTypeParam(const TQCString& attr, const TQCString& val)
02488 {
02489   if (mNeedsAssembly) mMsg->Assemble();
02490   mNeedsAssembly = false;
02491   setDwMediaTypeParam( dwContentType(), attr, val );
02492   mNeedsAssembly = true;
02493 }
02494 
02495 
02496 //-----------------------------------------------------------------------------
02497 TQCString KMMessage::contentTransferEncodingStr() const
02498 {
02499   DwHeaders& header = mMsg->Headers();
02500   if (header.HasContentTransferEncoding())
02501     return header.ContentTransferEncoding().AsString().c_str();
02502   else return "";
02503 }
02504 
02505 
02506 //-----------------------------------------------------------------------------
02507 int KMMessage::contentTransferEncoding( DwEntity *entity ) const
02508 {
02509   if ( !entity )
02510     entity = mMsg;
02511 
02512   DwHeaders& header = entity->Headers();
02513   if ( header.HasContentTransferEncoding() )
02514     return header.ContentTransferEncoding().AsEnum();
02515   else return DwMime::kCteNull;
02516 }
02517 
02518 
02519 //-----------------------------------------------------------------------------
02520 void KMMessage::setContentTransferEncodingStr( const TQCString& cteString,
02521                                                DwEntity *entity )
02522 {
02523   if ( !entity )
02524     entity = mMsg;
02525 
02526   entity->Headers().ContentTransferEncoding().FromString( cteString );
02527   entity->Headers().ContentTransferEncoding().Parse();
02528   mNeedsAssembly = true;
02529 }
02530 
02531 
02532 //-----------------------------------------------------------------------------
02533 void KMMessage::setContentTransferEncoding( int cte, DwEntity *entity )
02534 {
02535   if ( !entity )
02536     entity = mMsg;
02537 
02538   entity->Headers().ContentTransferEncoding().FromEnum( cte );
02539   mNeedsAssembly = true;
02540 }
02541 
02542 
02543 //-----------------------------------------------------------------------------
02544 DwHeaders& KMMessage::headers() const
02545 {
02546   return mMsg->Headers();
02547 }
02548 
02549 
02550 //-----------------------------------------------------------------------------
02551 void KMMessage::setNeedsAssembly()
02552 {
02553   mNeedsAssembly = true;
02554 }
02555 
02556 //-----------------------------------------------------------------------------
02557 void KMMessage::assembleIfNeeded()
02558 {
02559   Q_ASSERT( mMsg );
02560 
02561   if ( mNeedsAssembly ) {
02562     mMsg->Assemble();
02563     mNeedsAssembly = false;
02564   }
02565 }
02566 
02567 //-----------------------------------------------------------------------------
02568 TQCString KMMessage::body() const
02569 {
02570   const DwString& body = mMsg->Body().AsString();
02571   TQCString str = KMail::Util::CString( body );
02572   // Calls length() -> slow
02573   //kdWarning( str.length() != body.length(), 5006 )
02574   //  << "KMMessage::body(): body is binary but used as text!" << endl;
02575   return str;
02576 }
02577 
02578 
02579 //-----------------------------------------------------------------------------
02580 TQByteArray KMMessage::bodyDecodedBinary() const
02581 {
02582   DwString dwstr;
02583   const DwString& dwsrc = mMsg->Body().AsString();
02584 
02585   switch (cte())
02586   {
02587   case DwMime::kCteBase64:
02588     DwDecodeBase64(dwsrc, dwstr);
02589     break;
02590   case DwMime::kCteQuotedPrintable:
02591     DwDecodeQuotedPrintable(dwsrc, dwstr);
02592     break;
02593   default:
02594     dwstr = dwsrc;
02595     break;
02596   }
02597 
02598   int len = dwstr.size();
02599   TQByteArray ba(len);
02600   memcpy(ba.data(),dwstr.data(),len);
02601   return ba;
02602 }
02603 
02604 
02605 //-----------------------------------------------------------------------------
02606 TQCString KMMessage::bodyDecoded() const
02607 {
02608   DwString dwstr;
02609   DwString dwsrc = mMsg->Body().AsString();
02610 
02611   switch (cte())
02612   {
02613   case DwMime::kCteBase64:
02614     DwDecodeBase64(dwsrc, dwstr);
02615     break;
02616   case DwMime::kCteQuotedPrintable:
02617     DwDecodeQuotedPrintable(dwsrc, dwstr);
02618     break;
02619   default:
02620     dwstr = dwsrc;
02621     break;
02622   }
02623 
02624   return KMail::Util::CString( dwstr );
02625 
02626   // Calling TQCString::length() is slow
02627   //TQCString result = KMail::Util::CString( dwstr );
02628   //kdWarning(result.length() != len, 5006)
02629   //  << "KMMessage::bodyDecoded(): body is binary but used as text!" << endl;
02630   //return result;
02631 }
02632 
02633 
02634 //-----------------------------------------------------------------------------
02635 TQValueList<int> KMMessage::determineAllowedCtes( const CharFreq& cf,
02636                                                  bool allow8Bit,
02637                                                  bool willBeSigned )
02638 {
02639   TQValueList<int> allowedCtes;
02640 
02641   switch ( cf.type() ) {
02642   case CharFreq::SevenBitText:
02643     allowedCtes << DwMime::kCte7bit;
02644   case CharFreq::EightBitText:
02645     if ( allow8Bit )
02646       allowedCtes << DwMime::kCte8bit;
02647   case CharFreq::SevenBitData:
02648     if ( cf.printableRatio() > 5.0/6.0 ) {
02649       // let n the length of data and p the number of printable chars.
02650       // Then base64 \approx 4n/3; qp \approx p + 3(n-p)
02651       // => qp < base64 iff p > 5n/6.
02652       allowedCtes << DwMime::kCteQp;
02653       allowedCtes << DwMime::kCteBase64;
02654     } else {
02655       allowedCtes << DwMime::kCteBase64;
02656       allowedCtes << DwMime::kCteQp;
02657     }
02658     break;
02659   case CharFreq::EightBitData:
02660     allowedCtes << DwMime::kCteBase64;
02661     break;
02662   case CharFreq::None:
02663   default:
02664     // just nothing (avoid compiler warning)
02665     ;
02666   }
02667 
02668   // In the following cases only QP and Base64 are allowed:
02669   // - the buffer will be OpenPGP/MIME signed and it contains trailing
02670   //   whitespace (cf. RFC 3156)
02671   // - a line starts with "From "
02672   if ( ( willBeSigned && cf.hasTrailingWhitespace() ) ||
02673        cf.hasLeadingFrom() ) {
02674     allowedCtes.remove( DwMime::kCte8bit );
02675     allowedCtes.remove( DwMime::kCte7bit );
02676   }
02677 
02678   return allowedCtes;
02679 }
02680 
02681 
02682 //-----------------------------------------------------------------------------
02683 void KMMessage::setBodyAndGuessCte( const TQByteArray& aBuf,
02684                                     TQValueList<int> & allowedCte,
02685                                     bool allow8Bit,
02686                                     bool willBeSigned,
02687                                     DwEntity *entity )
02688 {
02689   if ( !entity )
02690     entity = mMsg;
02691 
02692   CharFreq cf( aBuf ); // it's safe to pass null arrays
02693   allowedCte = determineAllowedCtes( cf, allow8Bit, willBeSigned );
02694   setCte( allowedCte[0], entity ); // choose best fitting
02695   setBodyEncodedBinary( aBuf, entity );
02696 }
02697 
02698 
02699 //-----------------------------------------------------------------------------
02700 void KMMessage::setBodyAndGuessCte( const TQCString& aBuf,
02701                                     TQValueList<int> & allowedCte,
02702                                     bool allow8Bit,
02703                                     bool willBeSigned,
02704                                     DwEntity *entity )
02705 {
02706   if ( !entity )
02707     entity = mMsg;
02708 
02709   CharFreq cf( aBuf.data(), aBuf.size()-1 ); // it's safe to pass null strings
02710   allowedCte = determineAllowedCtes( cf, allow8Bit, willBeSigned );
02711   setCte( allowedCte[0], entity ); // choose best fitting
02712   setBodyEncoded( aBuf, entity );
02713 }
02714 
02715 
02716 //-----------------------------------------------------------------------------
02717 void KMMessage::setBodyEncoded(const TQCString& aStr, DwEntity *entity )
02718 {
02719   if ( !entity )
02720     entity = mMsg;
02721 
02722   DwString dwSrc(aStr.data(), aStr.size()-1 /* not the trailing NUL */);
02723   DwString dwResult;
02724 
02725   switch (cte( entity ))
02726   {
02727   case DwMime::kCteBase64:
02728     DwEncodeBase64(dwSrc, dwResult);
02729     break;
02730   case DwMime::kCteQuotedPrintable:
02731     DwEncodeQuotedPrintable(dwSrc, dwResult);
02732     break;
02733   default:
02734     dwResult = dwSrc;
02735     break;
02736   }
02737 
02738   entity->Body().FromString(dwResult);
02739   mNeedsAssembly = true;
02740 }
02741 
02742 //-----------------------------------------------------------------------------
02743 void KMMessage::setBodyEncodedBinary( const TQByteArray& aStr, DwEntity *entity )
02744 {
02745   if ( !entity )
02746     entity = mMsg;
02747 
02748   DwString dwSrc(aStr.data(), aStr.size());
02749   DwString dwResult;
02750 
02751   switch ( cte( entity ) )
02752   {
02753   case DwMime::kCteBase64:
02754     DwEncodeBase64( dwSrc, dwResult );
02755     break;
02756   case DwMime::kCteQuotedPrintable:
02757     DwEncodeQuotedPrintable( dwSrc, dwResult );
02758     break;
02759   default:
02760     dwResult = dwSrc;
02761     break;
02762   }
02763 
02764   entity->Body().FromString( dwResult );
02765   entity->Body().Parse();
02766 
02767   mNeedsAssembly = true;
02768 }
02769 
02770 
02771 //-----------------------------------------------------------------------------
02772 void KMMessage::setBody(const TQCString& aStr)
02773 {
02774   mMsg->Body().FromString(KMail::Util::dwString(aStr));
02775   mNeedsAssembly = true;
02776 }
02777 void KMMessage::setBody(const DwString& aStr)
02778 {
02779   mMsg->Body().FromString(aStr);
02780   mNeedsAssembly = true;
02781 }
02782 void KMMessage::setBody(const char* aStr)
02783 {
02784   mMsg->Body().FromString(aStr);
02785   mNeedsAssembly = true;
02786 }
02787 
02788 //-----------------------------------------------------------------------------
02789 void KMMessage::setMultiPartBody( const TQCString & aStr ) {
02790   setBody( aStr );
02791   mMsg->Body().Parse();
02792   mNeedsAssembly = true;
02793 }
02794 
02795 
02796 // Patched by Daniel Moisset <dmoisset@grulic.org.ar>
02797 // modified numbodyparts, bodypart to take nested body parts as
02798 // a linear sequence.
02799 // third revision, Sep 26 2000
02800 
02801 // this is support structure for traversing tree without recursion
02802 
02803 //-----------------------------------------------------------------------------
02804 int KMMessage::numBodyParts() const
02805 {
02806   int count = 0;
02807   DwBodyPart* part = getFirstDwBodyPart();
02808   TQPtrList< DwBodyPart > parts;
02809 
02810   while (part)
02811   {
02812     //dive into multipart messages
02813     while (    part
02814             && part->hasHeaders()
02815             && part->Headers().HasContentType()
02816             && part->Body().FirstBodyPart()
02817             && (DwMime::kTypeMultipart == part->Headers().ContentType().Type()) )
02818     {
02819       parts.append( part );
02820       part = part->Body().FirstBodyPart();
02821     }
02822     // this is where currPart->msgPart contains a leaf message part
02823     count++;
02824     // go up in the tree until reaching a node with next
02825     // (or the last top-level node)
02826     while (part && !(part->Next()) && !(parts.isEmpty()))
02827     {
02828       part = parts.getLast();
02829       parts.removeLast();
02830     }
02831 
02832     if (part && part->Body().Message() &&
02833         part->Body().Message()->Body().FirstBodyPart())
02834     {
02835       part = part->Body().Message()->Body().FirstBodyPart();
02836     } else if (part) {
02837       part = part->Next();
02838     }
02839   }
02840 
02841   return count;
02842 }
02843 
02844 
02845 //-----------------------------------------------------------------------------
02846 DwBodyPart * KMMessage::getFirstDwBodyPart() const
02847 {
02848   return mMsg->Body().FirstBodyPart();
02849 }
02850 
02851 
02852 //-----------------------------------------------------------------------------
02853 int KMMessage::partNumber( DwBodyPart * aDwBodyPart ) const
02854 {
02855   DwBodyPart *curpart;
02856   TQPtrList< DwBodyPart > parts;
02857   int curIdx = 0;
02858   int idx = 0;
02859   // Get the DwBodyPart for this index
02860 
02861   curpart = getFirstDwBodyPart();
02862 
02863   while (curpart && !idx) {
02864     //dive into multipart messages
02865     while(    curpart
02866            && curpart->hasHeaders()
02867            && curpart->Headers().HasContentType()
02868            && curpart->Body().FirstBodyPart()
02869            && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) )
02870     {
02871       parts.append( curpart );
02872       curpart = curpart->Body().FirstBodyPart();
02873     }
02874     // this is where currPart->msgPart contains a leaf message part
02875     if (curpart == aDwBodyPart)
02876       idx = curIdx;
02877     curIdx++;
02878     // go up in the tree until reaching a node with next
02879     // (or the last top-level node)
02880     while (curpart && !(curpart->Next()) && !(parts.isEmpty()))
02881     {
02882       curpart = parts.getLast();
02883       parts.removeLast();
02884     } ;
02885     if (curpart)
02886       curpart = curpart->Next();
02887   }
02888   return idx;
02889 }
02890 
02891 
02892 //-----------------------------------------------------------------------------
02893 DwBodyPart * KMMessage::dwBodyPart( int aIdx ) const
02894 {
02895   DwBodyPart *part, *curpart;
02896   TQPtrList< DwBodyPart > parts;
02897   int curIdx = 0;
02898   // Get the DwBodyPart for this index
02899 
02900   curpart = getFirstDwBodyPart();
02901   part = 0;
02902 
02903   while (curpart && !part) {
02904     //dive into multipart messages
02905     while(    curpart
02906            && curpart->hasHeaders()
02907            && curpart->Headers().HasContentType()
02908            && curpart->Body().FirstBodyPart()
02909            && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) )
02910     {
02911       parts.append( curpart );
02912       curpart = curpart->Body().FirstBodyPart();
02913     }
02914     // this is where currPart->msgPart contains a leaf message part
02915     if (curIdx==aIdx)
02916         part = curpart;
02917     curIdx++;
02918     // go up in the tree until reaching a node with next
02919     // (or the last top-level node)
02920     while (curpart && !(curpart->Next()) && !(parts.isEmpty()))
02921     {
02922       curpart = parts.getLast();
02923       parts.removeLast();
02924     }
02925     if (curpart)
02926       curpart = curpart->Next();
02927   }
02928   return part;
02929 }
02930 
02931 
02932 //-----------------------------------------------------------------------------
02933 DwBodyPart * KMMessage::findDwBodyPart( int type, int subtype ) const
02934 {
02935   DwBodyPart *part, *curpart;
02936   TQPtrList< DwBodyPart > parts;
02937   // Get the DwBodyPart for this index
02938 
02939   curpart = getFirstDwBodyPart();
02940   part = 0;
02941 
02942   while (curpart && !part) {
02943     //dive into multipart messages
02944     while(curpart
02945       && curpart->hasHeaders()
02946       && curpart->Headers().HasContentType()
02947       && curpart->Body().FirstBodyPart()
02948       && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) {
02949     parts.append( curpart );
02950     curpart = curpart->Body().FirstBodyPart();
02951     }
02952     // this is where curPart->msgPart contains a leaf message part
02953 
02954     // pending(khz): Find out WHY this look does not travel down *into* an
02955     //               embedded "Message/RfF822" message containing a "Multipart/Mixed"
02956     if ( curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() ) {
02957       kdDebug(5006) << curpart->Headers().ContentType().TypeStr().c_str()
02958         << "  " << curpart->Headers().ContentType().SubtypeStr().c_str() << endl;
02959     }
02960 
02961     if (curpart &&
02962     curpart->hasHeaders() &&
02963         curpart->Headers().HasContentType() &&
02964     curpart->Headers().ContentType().Type() == type &&
02965     curpart->Headers().ContentType().Subtype() == subtype) {
02966     part = curpart;
02967     } else {
02968       // go up in the tree until reaching a node with next
02969       // (or the last top-level node)
02970       while (curpart && !(curpart->Next()) && !(parts.isEmpty())) {
02971     curpart = parts.getLast();
02972     parts.removeLast();
02973       } ;
02974       if (curpart)
02975     curpart = curpart->Next();
02976     }
02977   }
02978   return part;
02979 }
02980 
02981 //-----------------------------------------------------------------------------
02982 DwBodyPart * KMMessage::findDwBodyPart( const TQCString& type, const TQCString&  subtype ) const
02983 {
02984   DwBodyPart *part, *curpart;
02985   TQPtrList< DwBodyPart > parts;
02986   // Get the DwBodyPart for this index
02987 
02988   curpart = getFirstDwBodyPart();
02989   part = 0;
02990 
02991   while (curpart && !part) {
02992     //dive into multipart messages
02993     while(curpart
02994       && curpart->hasHeaders()
02995       && curpart->Headers().HasContentType()
02996       && curpart->Body().FirstBodyPart()
02997       && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) {
02998     parts.append( curpart );
02999     curpart = curpart->Body().FirstBodyPart();
03000     }
03001     // this is where curPart->msgPart contains a leaf message part
03002 
03003     // pending(khz): Find out WHY this look does not travel down *into* an
03004     //               embedded "Message/RfF822" message containing a "Multipart/Mixed"
03005     if (curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() ) {
03006       kdDebug(5006) << curpart->Headers().ContentType().TypeStr().c_str()
03007             << "  " << curpart->Headers().ContentType().SubtypeStr().c_str() << endl;
03008     }
03009 
03010     if (curpart &&
03011     curpart->hasHeaders() &&
03012         curpart->Headers().HasContentType() &&
03013     curpart->Headers().ContentType().TypeStr().c_str() == type &&
03014     curpart->Headers().ContentType().SubtypeStr().c_str() == subtype) {
03015     part = curpart;
03016     } else {
03017       // go up in the tree until reaching a node with next
03018       // (or the last top-level node)
03019       while (curpart && !(curpart->Next()) && !(parts.isEmpty())) {
03020     curpart = parts.getLast();
03021     parts.removeLast();
03022       } ;
03023       if (curpart)
03024     curpart = curpart->Next();
03025     }
03026   }
03027   return part;
03028 }
03029 
03030 
03031 void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart )
03032 {
03033   // TODO: Instead of manually implementing RFC2231 header encoding (i.e.
03034   //       possibly multiple values given as paramname*0=..; parmaname*1=..;...
03035   //       or par as paramname*0*=..; parmaname*1*=..;..., which should be
03036   //       concatenated), use a generic method to decode the header, using RFC
03037   //       2047 or 2231, or whatever future RFC might be appropriate!
03038   //       Right now, some fields are decoded, while others are not. E.g.
03039   //       Content-Disposition is not decoded here, rather only on demand in
03040   //       KMMsgPart::fileName; Name however is decoded here and stored as a
03041   //       decoded String in KMMsgPart...
03042   // Content-type
03043   TQCString additionalCTypeParams;
03044   if (headers.HasContentType())
03045   {
03046     DwMediaType& ct = headers.ContentType();
03047     aPart->setOriginalContentTypeStr( ct.AsString().c_str() );
03048     aPart->setTypeStr(ct.TypeStr().c_str());
03049     aPart->setSubtypeStr(ct.SubtypeStr().c_str());
03050     DwParameter *param = ct.FirstParameter();
03051     while(param)
03052     {
03053       if (!qstricmp(param->Attribute().c_str(), "charset"))
03054         aPart->setCharset(TQCString(param->Value().c_str()).lower());
03055       else if (!qstrnicmp(param->Attribute().c_str(), "name*", 5))
03056         aPart->setName(KMMsgBase::decodeRFC2231String(KMMsgBase::extractRFC2231HeaderField( param->Value().c_str(), "name" )));
03057       else {
03058         additionalCTypeParams += ';';
03059         additionalCTypeParams += param->AsString().c_str();
03060       }
03061       param=param->Next();
03062     }
03063   }
03064   else
03065   {
03066     aPart->setTypeStr("text");      // Set to defaults
03067     aPart->setSubtypeStr("plain");
03068   }
03069   aPart->setAdditionalCTypeParamStr( additionalCTypeParams );
03070   // Modification by Markus
03071   if (aPart->name().isEmpty())
03072   {
03073     if (headers.HasContentType() && !headers.ContentType().Name().empty()) {
03074       aPart->setName(KMMsgBase::decodeRFC2047String(headers.
03075             ContentType().Name().c_str()) );
03076     } else if (headers.HasSubject() && !headers.Subject().AsString().empty()) {
03077       aPart->setName( KMMsgBase::decodeRFC2047String(headers.
03078             Subject().AsString().c_str()) );
03079     }
03080   }
03081 
03082   // Content-transfer-encoding
03083   if (headers.HasContentTransferEncoding())
03084     aPart->setCteStr(headers.ContentTransferEncoding().AsString().c_str());
03085   else
03086     aPart->setCteStr("7bit");
03087 
03088   // Content-description
03089   if (headers.HasContentDescription())
03090     aPart->setContentDescription( KMMsgBase::decodeRFC2047String(
03091                                     headers.ContentDescription().AsString().c_str() ) );
03092   else
03093     aPart->setContentDescription("");
03094 
03095   // Content-disposition
03096   if (headers.HasContentDisposition())
03097     aPart->setContentDisposition(headers.ContentDisposition().AsString().c_str());
03098   else
03099     aPart->setContentDisposition("");
03100 }
03101 
03102 //-----------------------------------------------------------------------------
03103 void KMMessage::bodyPart(DwBodyPart* aDwBodyPart, KMMessagePart* aPart,
03104              bool withBody)
03105 {
03106   if ( !aPart )
03107     return;
03108 
03109   aPart->clear();
03110 
03111   if( aDwBodyPart && aDwBodyPart->hasHeaders()  ) {
03112     // This must not be an empty string, because we'll get a
03113     // spurious empty Subject: line in some of the parts.
03114     //aPart->setName(" ");
03115     // partSpecifier
03116     TQString partId( aDwBodyPart->partId() );
03117     aPart->setPartSpecifier( partId );
03118 
03119     DwHeaders& headers = aDwBodyPart->Headers();
03120     applyHeadersToMessagePart( headers, aPart );
03121 
03122     // Body
03123     if (withBody)
03124       aPart->setBody( aDwBodyPart->Body().AsString() );
03125     else
03126       aPart->setBody( TQCString("") );
03127 
03128     // Content-id
03129     if ( headers.HasContentId() ) {
03130       const TQCString contentId = headers.ContentId().AsString().c_str();
03131       // ignore leading '<' and trailing '>'
03132       aPart->setContentId( contentId.mid( 1, contentId.length() - 2 ) );
03133     }
03134   }
03135   // If no valid body part was given,
03136   // set all MultipartBodyPart attributes to empty values.
03137   else
03138   {
03139     aPart->setTypeStr("");
03140     aPart->setSubtypeStr("");
03141     aPart->setCteStr("");
03142     // This must not be an empty string, because we'll get a
03143     // spurious empty Subject: line in some of the parts.
03144     //aPart->setName(" ");
03145     aPart->setContentDescription("");
03146     aPart->setContentDisposition("");
03147     aPart->setBody(TQCString(""));
03148     aPart->setContentId("");
03149   }
03150 }
03151 
03152 
03153 //-----------------------------------------------------------------------------
03154 void KMMessage::bodyPart(int aIdx, KMMessagePart* aPart) const
03155 {
03156   if ( !aPart )
03157     return;
03158 
03159   // If the DwBodyPart was found get the header fields and body
03160   if ( DwBodyPart *part = dwBodyPart( aIdx ) ) {
03161     KMMessage::bodyPart(part, aPart);
03162     if( aPart->name().isEmpty() )
03163       aPart->setName( i18n("Attachment: %1").arg( aIdx ) );
03164   }
03165 }
03166 
03167 
03168 //-----------------------------------------------------------------------------
03169 void KMMessage::deleteBodyParts()
03170 {
03171   mMsg->Body().DeleteBodyParts();
03172 }
03173 
03174 //-----------------------------------------------------------------------------
03175 
03176 bool KMMessage::deleteBodyPart( int partIndex )
03177 {
03178   KMMessagePart part;
03179   DwBodyPart *dwpart = findPart( partIndex );
03180   if ( !dwpart )
03181     return false;
03182   KMMessage::bodyPart( dwpart, &part, true );
03183   if ( !part.isComplete() )
03184      return false;
03185 
03186   DwBody *parentNode = dynamic_cast<DwBody*>( dwpart->Parent() );
03187   if ( !parentNode )
03188     return false;
03189   parentNode->RemoveBodyPart( dwpart );
03190 
03191   // add dummy part to show that a attachment has been deleted
03192   KMMessagePart dummyPart;
03193   dummyPart.duplicate( part );
03194   TQString comment = i18n("This attachment has been deleted.");
03195   if ( !part.fileName().isEmpty() )
03196     comment = i18n( "The attachment '%1' has been deleted." ).arg( part.fileName() );
03197   dummyPart.setContentDescription( comment );
03198   dummyPart.setBodyEncodedBinary( TQByteArray() );
03199   TQCString cd = dummyPart.contentDisposition();
03200   if ( cd.find( "inline", 0, false ) == 0 ) {
03201     cd.replace( 0, 10, "attachment" );
03202     dummyPart.setContentDisposition( cd );
03203   } else if ( cd.isEmpty() ) {
03204     dummyPart.setContentDisposition( "attachment" );
03205   }
03206   DwBodyPart* newDwPart = createDWBodyPart( &dummyPart );
03207   parentNode->AddBodyPart( newDwPart );
03208   getTopLevelPart()->Assemble();
03209   return true;
03210 }
03211 
03212 //-----------------------------------------------------------------------------
03213 DwBodyPart* KMMessage::createDWBodyPart(const KMMessagePart* aPart)
03214 {
03215   DwBodyPart* part = DwBodyPart::NewBodyPart(emptyString, 0);
03216 
03217   if ( !aPart )
03218     return part;
03219 
03220   TQCString charset  = aPart->charset();
03221   TQCString type     = aPart->typeStr();
03222   TQCString subtype  = aPart->subtypeStr();
03223   TQCString cte      = aPart->cteStr();
03224   TQCString contDesc = aPart->contentDescriptionEncoded();
03225   TQCString contDisp = aPart->contentDisposition();
03226   TQCString name     = KMMsgBase::encodeRFC2231StringAutoDetectCharset( aPart->name(), charset );
03227   bool RFC2231encoded = aPart->name() != TQString(name);
03228   TQCString paramAttr  = aPart->parameterAttribute();
03229 
03230   DwHeaders& headers = part->Headers();
03231 
03232   DwMediaType& ct = headers.ContentType();
03233   if (!type.isEmpty() && !subtype.isEmpty())
03234   {
03235     ct.SetTypeStr(type.data());
03236     ct.SetSubtypeStr(subtype.data());
03237     if (!charset.isEmpty()){
03238       DwParameter *param;
03239       param=new DwParameter;
03240       param->SetAttribute("charset");
03241       param->SetValue(charset.data());
03242       ct.AddParameter(param);
03243     }
03244   }
03245 
03246   TQCString additionalParam = aPart->additionalCTypeParamStr();
03247   if( !additionalParam.isEmpty() )
03248   {
03249     TQCString parAV;
03250     DwString parA, parV;
03251     int iL, i1, i2, iM;
03252     iL = additionalParam.length();
03253     i1 = 0;
03254     i2 = additionalParam.find(';', i1, false);
03255     while ( i1 < iL )
03256     {
03257       if( -1 == i2 )
03258     i2 = iL;
03259       if( i1+1 < i2 ) {
03260     parAV = additionalParam.mid( i1, (i2-i1) );
03261     iM = parAV.find('=');
03262     if( -1 < iM )
03263         {
03264       parA = parAV.left( iM );
03265       parV = parAV.right( parAV.length() - iM - 1 );
03266       if( ('"' == parV.at(0)) && ('"' == parV.at(parV.length()-1)) )
03267           {
03268         parV.erase( 0,  1);
03269         parV.erase( parV.length()-1 );
03270       }
03271     }
03272     else
03273         {
03274       parA = parAV;
03275       parV = "";
03276     }
03277     DwParameter *param;
03278     param = new DwParameter;
03279     param->SetAttribute( parA );
03280     param->SetValue(     parV );
03281     ct.AddParameter( param );
03282       }
03283       i1 = i2+1;
03284       i2 = additionalParam.find(';', i1, false);
03285     }
03286   }
03287 
03288   if ( !name.isEmpty() ) {
03289     if (RFC2231encoded)
03290     {
03291       DwParameter *nameParam;
03292       nameParam = new DwParameter;
03293       nameParam->SetAttribute("name*");
03294       nameParam->SetValue(name.data(),true);
03295       ct.AddParameter(nameParam);
03296     } else {
03297       ct.SetName(name.data());
03298     }
03299   }
03300 
03301   if (!paramAttr.isEmpty())
03302   {
03303     TQCString paramValue;
03304     paramValue = KMMsgBase::encodeRFC2231StringAutoDetectCharset( aPart->parameterValue(), charset );
03305     DwParameter *param = new DwParameter;
03306     if (aPart->parameterValue() != TQString(paramValue))
03307     {
03308       param->SetAttribute((paramAttr + '*').data());
03309       param->SetValue(paramValue.data(),true);
03310     } else {
03311       param->SetAttribute(paramAttr.data());
03312       param->SetValue(paramValue.data());
03313     }
03314     ct.AddParameter(param);
03315   }
03316 
03317   if (!cte.isEmpty())
03318     headers.Cte().FromString(cte);
03319 
03320   if (!contDesc.isEmpty())
03321     headers.ContentDescription().FromString(contDesc);
03322 
03323   if (!contDisp.isEmpty())
03324     headers.ContentDisposition().FromString(contDisp);
03325 
03326   const DwString bodyStr = aPart->dwBody();
03327   if (!bodyStr.empty())
03328     part->Body().FromString(bodyStr);
03329   else
03330     part->Body().FromString("");
03331 
03332   if (!aPart->partSpecifier().isNull())
03333     part->SetPartId( aPart->partSpecifier().latin1() );
03334 
03335   if (aPart->decodedSize() > 0)
03336     part->SetBodySize( aPart->decodedSize() );
03337 
03338   return part;
03339 }
03340 
03341 
03342 //-----------------------------------------------------------------------------
03343 void KMMessage::addDwBodyPart(DwBodyPart * aDwPart)
03344 {
03345   mMsg->Body().AddBodyPart( aDwPart );
03346   mNeedsAssembly = true;
03347 }
03348 
03349 
03350 //-----------------------------------------------------------------------------
03351 void KMMessage::addBodyPart(const KMMessagePart* aPart)
03352 {
03353   DwBodyPart* part = createDWBodyPart( aPart );
03354   addDwBodyPart( part );
03355 }
03356 
03357 
03358 //-----------------------------------------------------------------------------
03359 TQString KMMessage::generateMessageId( const TQString& addr )
03360 {
03361   TQDateTime datetime = TQDateTime::currentDateTime();
03362   TQString msgIdStr;
03363 
03364   msgIdStr = '<' + datetime.toString( "yyyyMMddhhmm.sszzz" );
03365 
03366   TQString msgIdSuffix;
03367   KConfigGroup general( KMKernel::config(), "General" );
03368 
03369   if( general.readBoolEntry( "useCustomMessageIdSuffix", false ) )
03370     msgIdSuffix = general.readEntry( "myMessageIdSuffix" );
03371 
03372   if( !msgIdSuffix.isEmpty() )
03373     msgIdStr += '@' + msgIdSuffix;
03374   else
03375     msgIdStr += '.' + KPIM::encodeIDN( addr );
03376 
03377   msgIdStr += '>';
03378 
03379   return msgIdStr;
03380 }
03381 
03382 
03383 //-----------------------------------------------------------------------------
03384 TQCString KMMessage::html2source( const TQCString & src )
03385 {
03386   TQCString result( 1 + 6*(src.size()-1) );  // maximal possible length
03387 
03388   TQCString::ConstIterator s = src.begin();
03389   TQCString::Iterator d = result.begin();
03390   while ( *s ) {
03391     switch ( *s ) {
03392     case '<': {
03393         *d++ = '&';
03394         *d++ = 'l';
03395         *d++ = 't';
03396         *d++ = ';';
03397         ++s;
03398       }
03399       break;
03400     case '\r': {
03401         ++s;
03402       }
03403       break;
03404     case '\n': {
03405         *d++ = '<';
03406         *d++ = 'b';
03407         *d++ = 'r';
03408         *d++ = '>';
03409         ++s;
03410       }
03411       break;
03412     case '>': {
03413         *d++ = '&';
03414         *d++ = 'g';
03415         *d++ = 't';
03416         *d++ = ';';
03417         ++s;
03418       }
03419       break;
03420     case '&': {
03421         *d++ = '&';
03422         *d++ = 'a';
03423         *d++ = 'm';
03424         *d++ = 'p';
03425         *d++ = ';';
03426         ++s;
03427       }
03428       break;
03429     case '"': {
03430         *d++ = '&';
03431         *d++ = 'q';
03432         *d++ = 'u';
03433         *d++ = 'o';
03434         *d++ = 't';
03435         *d++ = ';';
03436         ++s;
03437       }
03438       break;
03439     case '\'': {
03440         *d++ = '&';
03441     *d++ = 'a';
03442     *d++ = 'p';
03443     *d++ = 's';
03444     *d++ = ';';
03445     ++s;
03446       }
03447       break;
03448     default:
03449         *d++ = *s++;
03450     }
03451   }
03452   result.truncate( d - result.begin() ); // adds trailing NUL
03453   return result;
03454 }
03455 
03456 //-----------------------------------------------------------------------------
03457 TQString KMMessage::encodeMailtoUrl( const TQString& str )
03458 {
03459   TQString result;
03460   result = TQString::fromLatin1( KMMsgBase::encodeRFC2047String( str,
03461                                                                 "utf-8" ) );
03462   result = KURL::encode_string( result );
03463   return result;
03464 }
03465 
03466 
03467 //-----------------------------------------------------------------------------
03468 TQString KMMessage::decodeMailtoUrl( const TQString& url )
03469 {
03470   TQString result;
03471   result = KURL::decode_string( url );
03472   result = KMMsgBase::decodeRFC2047String( result.latin1() );
03473   return result;
03474 }
03475 
03476 
03477 //-----------------------------------------------------------------------------
03478 TQCString KMMessage::stripEmailAddr( const TQCString& aStr )
03479 {
03480   //kdDebug(5006) << "KMMessage::stripEmailAddr( " << aStr << " )" << endl;
03481 
03482   if ( aStr.isEmpty() )
03483     return TQCString();
03484 
03485   TQCString result;
03486 
03487   // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
03488   // The purpose is to extract a displayable string from the mailboxes.
03489   // Comments in the addr-spec are not handled. No error checking is done.
03490 
03491   TQCString name;
03492   TQCString comment;
03493   TQCString angleAddress;
03494   enum { TopLevel, InComment, InAngleAddress } context = TopLevel;
03495   bool inQuotedString = false;
03496   int commentLevel = 0;
03497 
03498   for ( char* p = aStr.data(); *p; ++p ) {
03499     switch ( context ) {
03500     case TopLevel : {
03501       switch ( *p ) {
03502       case '"' : inQuotedString = !inQuotedString;
03503                  break;
03504       case '(' : if ( !inQuotedString ) {
03505                    context = InComment;
03506                    commentLevel = 1;
03507                  }
03508                  else
03509                    name += *p;
03510                  break;
03511       case '<' : if ( !inQuotedString ) {
03512                    context = InAngleAddress;
03513                  }
03514                  else
03515                    name += *p;
03516                  break;
03517       case '\\' : // quoted character
03518                  ++p; // skip the '\'
03519                  if ( *p )
03520                    name += *p;
03521                  break;
03522       case ',' : if ( !inQuotedString ) {
03523                    // next email address
03524                    if ( !result.isEmpty() )
03525                      result += ", ";
03526                    name = name.stripWhiteSpace();
03527                    comment = comment.stripWhiteSpace();
03528                    angleAddress = angleAddress.stripWhiteSpace();
03529                    /*
03530                    kdDebug(5006) << "Name    : \"" << name
03531                                  << "\"" << endl;
03532                    kdDebug(5006) << "Comment : \"" << comment
03533                                  << "\"" << endl;
03534                    kdDebug(5006) << "Address : \"" << angleAddress
03535                                  << "\"" << endl;
03536                    */
03537                    if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
03538                      // handle Outlook-style addresses like
03539                      // john.doe@invalid (John Doe)
03540                      result += comment;
03541                    }
03542                    else if ( !name.isEmpty() ) {
03543                      result += name;
03544                    }
03545                    else if ( !comment.isEmpty() ) {
03546                      result += comment;
03547                    }
03548                    else if ( !angleAddress.isEmpty() ) {
03549                      result += angleAddress;
03550                    }
03551                    name = TQCString();
03552                    comment = TQCString();
03553                    angleAddress = TQCString();
03554                  }
03555                  else
03556                    name += *p;
03557                  break;
03558       default :  name += *p;
03559       }
03560       break;
03561     }
03562     case InComment : {
03563       switch ( *p ) {
03564       case '(' : ++commentLevel;
03565                  comment += *p;
03566                  break;
03567       case ')' : --commentLevel;
03568                  if ( commentLevel == 0 ) {
03569                    context = TopLevel;
03570                    comment += ' '; // separate the text of several comments
03571                  }
03572                  else
03573                    comment += *p;
03574                  break;
03575       case '\\' : // quoted character
03576                  ++p; // skip the '\'
03577                  if ( *p )
03578                    comment += *p;
03579                  break;
03580       default :  comment += *p;
03581       }
03582       break;
03583     }
03584     case InAngleAddress : {
03585       switch ( *p ) {
03586       case '"' : inQuotedString = !inQuotedString;
03587                  angleAddress += *p;
03588                  break;
03589       case '>' : if ( !inQuotedString ) {
03590                    context = TopLevel;
03591                  }
03592                  else
03593                    angleAddress += *p;
03594                  break;
03595       case '\\' : // quoted character
03596                  ++p; // skip the '\'
03597                  if ( *p )
03598                    angleAddress += *p;
03599                  break;
03600       default :  angleAddress += *p;
03601       }
03602       break;
03603     }
03604     } // switch ( context )
03605   }
03606   if ( !result.isEmpty() )
03607     result += ", ";
03608   name = name.stripWhiteSpace();
03609   comment = comment.stripWhiteSpace();
03610   angleAddress = angleAddress.stripWhiteSpace();
03611   /*
03612   kdDebug(5006) << "Name    : \"" << name << "\"" << endl;
03613   kdDebug(5006) << "Comment : \"" << comment << "\"" << endl;
03614   kdDebug(5006) << "Address : \"" << angleAddress << "\"" << endl;
03615   */
03616   if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
03617     // handle Outlook-style addresses like
03618     // john.doe@invalid (John Doe)
03619     result += comment;
03620   }
03621   else if ( !name.isEmpty() ) {
03622     result += name;
03623   }
03624   else if ( !comment.isEmpty() ) {
03625     result += comment;
03626   }
03627   else if ( !angleAddress.isEmpty() ) {
03628     result += angleAddress;
03629   }
03630 
03631   //kdDebug(5006) << "KMMessage::stripEmailAddr(...) returns \"" << result
03632   //              << "\"" << endl;
03633   return result;
03634 }
03635 
03636 //-----------------------------------------------------------------------------
03637 TQString KMMessage::stripEmailAddr( const TQString& aStr )
03638 {
03639   //kdDebug(5006) << "KMMessage::stripEmailAddr( " << aStr << " )" << endl;
03640 
03641   if ( aStr.isEmpty() )
03642     return TQString::null;
03643 
03644   TQString result;
03645 
03646   // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
03647   // The purpose is to extract a displayable string from the mailboxes.
03648   // Comments in the addr-spec are not handled. No error checking is done.
03649 
03650   TQString name;
03651   TQString comment;
03652   TQString angleAddress;
03653   enum { TopLevel, InComment, InAngleAddress } context = TopLevel;
03654   bool inQuotedString = false;
03655   int commentLevel = 0;
03656 
03657   TQChar ch;
03658   unsigned int strLength(aStr.length());
03659   for ( uint index = 0; index < strLength; ++index ) {
03660     ch = aStr[index];
03661     switch ( context ) {
03662     case TopLevel : {
03663       switch ( ch.latin1() ) {
03664       case '"' : inQuotedString = !inQuotedString;
03665                  break;
03666       case '(' : if ( !inQuotedString ) {
03667                    context = InComment;
03668                    commentLevel = 1;
03669                  }
03670                  else
03671                    name += ch;
03672                  break;
03673       case '<' : if ( !inQuotedString ) {
03674                    context = InAngleAddress;
03675                  }
03676                  else
03677                    name += ch;
03678                  break;
03679       case '\\' : // quoted character
03680                  ++index; // skip the '\'
03681                  if ( index < aStr.length() )
03682                    name += aStr[index];
03683                  break;
03684       case ',' : if ( !inQuotedString ) {
03685                    // next email address
03686                    if ( !result.isEmpty() )
03687                      result += ", ";
03688                    name = name.stripWhiteSpace();
03689                    comment = comment.stripWhiteSpace();
03690                    angleAddress = angleAddress.stripWhiteSpace();
03691                    /*
03692                    kdDebug(5006) << "Name    : \"" << name
03693                                  << "\"" << endl;
03694                    kdDebug(5006) << "Comment : \"" << comment
03695                                  << "\"" << endl;
03696                    kdDebug(5006) << "Address : \"" << angleAddress
03697                                  << "\"" << endl;
03698                    */
03699                    if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
03700                      // handle Outlook-style addresses like
03701                      // john.doe@invalid (John Doe)
03702                      result += comment;
03703                    }
03704                    else if ( !name.isEmpty() ) {
03705                      result += name;
03706                    }
03707                    else if ( !comment.isEmpty() ) {
03708                      result += comment;
03709                    }
03710                    else if ( !angleAddress.isEmpty() ) {
03711                      result += angleAddress;
03712                    }
03713                    name = TQString::null;
03714                    comment = TQString::null;
03715                    angleAddress = TQString::null;
03716                  }
03717                  else
03718                    name += ch;
03719                  break;
03720       default :  name += ch;
03721       }
03722       break;
03723     }
03724     case InComment : {
03725       switch ( ch.latin1() ) {
03726       case '(' : ++commentLevel;
03727                  comment += ch;
03728                  break;
03729       case ')' : --commentLevel;
03730                  if ( commentLevel == 0 ) {
03731                    context = TopLevel;
03732                    comment += ' '; // separate the text of several comments
03733                  }
03734                  else
03735                    comment += ch;
03736                  break;
03737       case '\\' : // quoted character
03738                  ++index; // skip the '\'
03739                  if ( index < aStr.length() )
03740                    comment += aStr[index];
03741                  break;
03742       default :  comment += ch;
03743       }
03744       break;
03745     }
03746     case InAngleAddress : {
03747       switch ( ch.latin1() ) {
03748       case '"' : inQuotedString = !inQuotedString;
03749                  angleAddress += ch;
03750                  break;
03751       case '>' : if ( !inQuotedString ) {
03752                    context = TopLevel;
03753                  }
03754                  else
03755                    angleAddress += ch;
03756                  break;
03757       case '\\' : // quoted character
03758                  ++index; // skip the '\'
03759                  if ( index < aStr.length() )
03760                    angleAddress += aStr[index];
03761                  break;
03762       default :  angleAddress += ch;
03763       }
03764       break;
03765     }
03766     } // switch ( context )
03767   }
03768   if ( !result.isEmpty() )
03769     result += ", ";
03770   name = name.stripWhiteSpace();
03771   comment = comment.stripWhiteSpace();
03772   angleAddress = angleAddress.stripWhiteSpace();
03773   /*
03774   kdDebug(5006) << "Name    : \"" << name << "\"" << endl;
03775   kdDebug(5006) << "Comment : \"" << comment << "\"" << endl;
03776   kdDebug(5006) << "Address : \"" << angleAddress << "\"" << endl;
03777   */
03778   if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
03779     // handle Outlook-style addresses like
03780     // john.doe@invalid (John Doe)
03781     result += comment;
03782   }
03783   else if ( !name.isEmpty() ) {
03784     result += name;
03785   }
03786   else if ( !comment.isEmpty() ) {
03787     result += comment;
03788   }
03789   else if ( !angleAddress.isEmpty() ) {
03790     result += angleAddress;
03791   }
03792 
03793   //kdDebug(5006) << "KMMessage::stripEmailAddr(...) returns \"" << result
03794   //              << "\"" << endl;
03795   return result;
03796 }
03797 
03798 //-----------------------------------------------------------------------------
03799 TQString KMMessage::quoteHtmlChars( const TQString& str, bool removeLineBreaks )
03800 {
03801   TQString result;
03802 
03803   unsigned int strLength(str.length());
03804   result.reserve( 6*strLength ); // maximal possible length
03805   for( unsigned int i = 0; i < strLength; ++i )
03806     switch ( str[i].latin1() ) {
03807     case '<':
03808       result += "&lt;";
03809       break;
03810     case '>':
03811       result += "&gt;";
03812       break;
03813     case '&':
03814       result += "&amp;";
03815       break;
03816     case '"':
03817       result += "&quot;";
03818       break;
03819     case '\n':
03820       if ( !removeLineBreaks )
03821     result += "<br>";
03822       break;
03823     case '\r':
03824       // ignore CR
03825       break;
03826     default:
03827       result += str[i];
03828     }
03829 
03830   result.squeeze();
03831   return result;
03832 }
03833 
03834 //-----------------------------------------------------------------------------
03835 TQString KMMessage::emailAddrAsAnchor(const TQString& aEmail, bool stripped, const TQString& cssStyle, bool aLink)
03836 {
03837   if( aEmail.isEmpty() )
03838     return aEmail;
03839 
03840   TQStringList addressList = KPIM::splitEmailAddrList( aEmail );
03841   TQString result;
03842 
03843   for( TQStringList::ConstIterator it = addressList.begin();
03844        ( it != addressList.end() );
03845        ++it ) {
03846     if( !(*it).isEmpty() ) {
03847 
03848       // Extract the name, mail and some pretty versions out of the mail address
03849       TQString name, mail;
03850       KPIM::getNameAndMail( *it, name, mail );
03851       TQString pretty;
03852       TQString prettyStripped;
03853       if ( name.stripWhiteSpace().isEmpty() ) {
03854         pretty = mail;
03855         prettyStripped = mail;
03856       } else {
03857         pretty = KPIM::quoteNameIfNecessary( name ) + " <" + mail + ">";
03858         prettyStripped = name;
03859       }
03860 
03861       if(aLink) {
03862         result += "<a href=\"mailto:"
03863                + KMMessage::encodeMailtoUrl( pretty )
03864                + "\" "+cssStyle+">";
03865       }
03866 
03867       if ( stripped ) {
03868         result += KMMessage::quoteHtmlChars( prettyStripped, true );
03869       }
03870       else {
03871         result += KMMessage::quoteHtmlChars( pretty, true );
03872       }
03873 
03874       if(aLink)
03875         result += "</a>, ";
03876     }
03877   }
03878   // cut of the trailing ", "
03879   if(aLink)
03880     result.truncate( result.length() - 2 );
03881 
03882   //kdDebug(5006) << "KMMessage::emailAddrAsAnchor('" << aEmail
03883   //              << "') returns:\n-->" << result << "<--" << endl;
03884   return result;
03885 }
03886 
03887 //-----------------------------------------------------------------------------
03888 //static
03889 TQStringList KMMessage::stripAddressFromAddressList( const TQString& address,
03890                                                     const TQStringList& list )
03891 {
03892   TQStringList addresses( list );
03893   TQString addrSpec( KPIM::getEmailAddress( address ) );
03894   for ( TQStringList::Iterator it = addresses.begin();
03895        it != addresses.end(); ) {
03896     if ( kasciistricmp( addrSpec.utf8().data(),
03897                         KPIM::getEmailAddress( *it ).utf8().data() ) == 0 ) {
03898       kdDebug(5006) << "Removing " << *it << " from the address list"
03899                     << endl;
03900       it = addresses.remove( it );
03901     }
03902     else
03903       ++it;
03904   }
03905   return addresses;
03906 }
03907 
03908 
03909 //-----------------------------------------------------------------------------
03910 //static
03911 TQStringList KMMessage::stripMyAddressesFromAddressList( const TQStringList& list )
03912 {
03913   TQStringList addresses = list;
03914   for( TQStringList::Iterator it = addresses.begin();
03915        it != addresses.end(); ) {
03916     kdDebug(5006) << "Check whether " << *it << " is one of my addresses"
03917                   << endl;
03918     if( kmkernel->identityManager()->thatIsMe( KPIM::getEmailAddress( *it ) ) ) {
03919       kdDebug(5006) << "Removing " << *it << " from the address list"
03920                     << endl;
03921       it = addresses.remove( it );
03922     }
03923     else
03924       ++it;
03925   }
03926   return addresses;
03927 }
03928 
03929 
03930 //-----------------------------------------------------------------------------
03931 //static
03932 bool KMMessage::addressIsInAddressList( const TQString& address,
03933                                         const TQStringList& addresses )
03934 {
03935   TQString addrSpec = KPIM::getEmailAddress( address );
03936   for( TQStringList::ConstIterator it = addresses.begin();
03937        it != addresses.end(); ++it ) {
03938     if ( kasciistricmp( addrSpec.utf8().data(),
03939                         KPIM::getEmailAddress( *it ).utf8().data() ) == 0 )
03940       return true;
03941   }
03942   return false;
03943 }
03944 
03945 
03946 //-----------------------------------------------------------------------------
03947 //static
03948 TQString KMMessage::expandAliases( const TQString& recipients )
03949 {
03950   if ( recipients.isEmpty() )
03951     return TQString();
03952 
03953   TQStringList recipientList = KPIM::splitEmailAddrList( recipients );
03954 
03955   TQString expandedRecipients;
03956   for ( TQStringList::Iterator it = recipientList.begin();
03957         it != recipientList.end(); ++it ) {
03958     if ( !expandedRecipients.isEmpty() )
03959       expandedRecipients += ", ";
03960     TQString receiver = (*it).stripWhiteSpace();
03961 
03962     // try to expand distribution list
03963     TQString expandedList = KAddrBookExternal::expandDistributionList( receiver );
03964     if ( !expandedList.isEmpty() ) {
03965       expandedRecipients += expandedList;
03966       continue;
03967     }
03968 
03969     // try to expand nick name
03970     TQString expandedNickName = KabcBridge::expandNickName( receiver );
03971     if ( !expandedNickName.isEmpty() ) {
03972       expandedRecipients += expandedNickName;
03973       continue;
03974     }
03975 
03976     // check whether the address is missing the domain part
03977     // FIXME: looking for '@' might be wrong
03978     if ( receiver.find('@') == -1 ) {
03979       KConfigGroup general( KMKernel::config(), "General" );
03980       TQString defaultdomain = general.readEntry( "Default domain" );
03981       if( !defaultdomain.isEmpty() ) {
03982         expandedRecipients += receiver + "@" + defaultdomain;
03983       }
03984       else {
03985         expandedRecipients += guessEmailAddressFromLoginName( receiver );
03986       }
03987     }
03988     else
03989       expandedRecipients += receiver;
03990   }
03991 
03992   return expandedRecipients;
03993 }
03994 
03995 
03996 //-----------------------------------------------------------------------------
03997 //static
03998 TQString KMMessage::guessEmailAddressFromLoginName( const TQString& loginName )
03999 {
04000   if ( loginName.isEmpty() )
04001     return TQString();
04002 
04003   char hostnameC[256];
04004   // null terminate this C string
04005   hostnameC[255] = '\0';
04006   // set the string to 0 length if gethostname fails
04007   if ( gethostname( hostnameC, 255 ) )
04008     hostnameC[0] = '\0';
04009   TQString address = loginName;
04010   address += '@';
04011   address += TQString::fromLocal8Bit( hostnameC );
04012 
04013   // try to determine the real name
04014   const KUser user( loginName );
04015   if ( user.isValid() ) {
04016     TQString fullName = user.fullName();
04017     if ( fullName.find( TQRegExp( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ) ) != -1 )
04018       address = '"' + fullName.replace( '\\', "\\" ).replace( '"', "\\" )
04019           + "\" <" + address + '>';
04020     else
04021       address = fullName + " <" + address + '>';
04022   }
04023 
04024   return address;
04025 }
04026 
04027 //-----------------------------------------------------------------------------
04028 void KMMessage::readConfig()
04029 {
04030   KMMsgBase::readConfig();
04031 
04032   KConfig *config=KMKernel::config();
04033   KConfigGroupSaver saver(config, "General");
04034 
04035   config->setGroup("General");
04036 
04037   int languageNr = config->readNumEntry("reply-current-language",0);
04038 
04039   { // area for config group "KMMessage #n"
04040     KConfigGroupSaver saver(config, TQString("KMMessage #%1").arg(languageNr));
04041     sReplyLanguage = config->readEntry("language",KGlobal::locale()->language());
04042     sReplyStr = config->readEntry("phrase-reply",
04043       i18n("On %D, you wrote:"));
04044     sReplyAllStr = config->readEntry("phrase-reply-all",
04045       i18n("On %D, %F wrote:"));
04046     sForwardStr = config->readEntry("phrase-forward",
04047       i18n("Forwarded Message"));
04048     sIndentPrefixStr = config->readEntry("indent-prefix",">%_");
04049   }
04050 
04051   { // area for config group "Composer"
04052     KConfigGroupSaver saver(config, "Composer");
04053     sSmartQuote = GlobalSettings::self()->smartQuote();
04054     sWordWrap = GlobalSettings::self()->wordWrap();
04055     sWrapCol = GlobalSettings::self()->lineWrapWidth();
04056     if ((sWrapCol == 0) || (sWrapCol > 78))
04057       sWrapCol = 78;
04058     if (sWrapCol < 30)
04059       sWrapCol = 30;
04060 
04061     sPrefCharsets = config->readListEntry("pref-charsets");
04062   }
04063 
04064   { // area for config group "Reader"
04065     KConfigGroupSaver saver(config, "Reader");
04066     sHeaderStrategy = HeaderStrategy::create( config->readEntry( "header-set-displayed", "rich" ) );
04067   }
04068 }
04069 
04070 TQCString KMMessage::defaultCharset()
04071 {
04072   TQCString retval;
04073 
04074   if (!sPrefCharsets.isEmpty())
04075     retval = sPrefCharsets[0].latin1();
04076 
04077   if (retval.isEmpty()  || (retval == "locale")) {
04078     retval = TQCString(kmkernel->networkCodec()->mimeName());
04079     KPIM::kAsciiToLower( retval.data() );
04080   }
04081 
04082   if (retval == "jisx0208.1983-0") retval = "iso-2022-jp";
04083   else if (retval == "ksc5601.1987-0") retval = "euc-kr";
04084   return retval;
04085 }
04086 
04087 const TQStringList &KMMessage::preferredCharsets()
04088 {
04089   return sPrefCharsets;
04090 }
04091 
04092 //-----------------------------------------------------------------------------
04093 TQCString KMMessage::charset() const
04094 {
04095   if ( mMsg->Headers().HasContentType() ) {
04096     DwMediaType &mType=mMsg->Headers().ContentType();
04097     mType.Parse();
04098     DwParameter *param=mType.FirstParameter();
04099     while(param){
04100       if (!kasciistricmp(param->Attribute().c_str(), "charset"))
04101         return param->Value().c_str();
04102       else param=param->Next();
04103     }
04104   }
04105   return ""; // us-ascii, but we don't have to specify it
04106 }
04107 
04108 //-----------------------------------------------------------------------------
04109 void KMMessage::setCharset( const TQCString &charset, DwEntity *entity )
04110 {
04111   kdWarning( type() != DwMime::kTypeText )
04112     << "KMMessage::setCharset(): trying to set a charset for a non-textual mimetype." << endl
04113     << "Fix this caller:" << endl
04114     << "====================================================================" << endl
04115     << kdBacktrace( 5 ) << endl
04116     << "====================================================================" << endl;
04117 
04118   if ( !entity )
04119     entity = mMsg;
04120 
04121   DwMediaType &mType = entity->Headers().ContentType();
04122   mType.Parse();
04123   DwParameter *param = mType.FirstParameter();
04124   while( param ) {
04125 
04126     // FIXME use the mimelib functions here for comparison.
04127     if ( !kasciistricmp( param->Attribute().c_str(), "charset" ) )
04128       break;
04129 
04130     param = param->Next();
04131   }
04132   if ( !param ) {
04133     param = new DwParameter;
04134     param->SetAttribute( "charset" );
04135     mType.AddParameter( param );
04136   }
04137   else
04138     mType.SetModified();
04139 
04140   TQCString lowerCharset = charset;
04141   KPIM::kAsciiToLower( lowerCharset.data() );
04142   param->SetValue( DwString( lowerCharset ) );
04143   mType.Assemble();
04144 }
04145 
04146 
04147 //-----------------------------------------------------------------------------
04148 void KMMessage::setStatus(const KMMsgStatus aStatus, int idx)
04149 {
04150   if (mStatus == aStatus)
04151     return;
04152   KMMsgBase::setStatus(aStatus, idx);
04153 }
04154 
04155 void KMMessage::setEncryptionState(const KMMsgEncryptionState s, int idx)
04156 {
04157     if( mEncryptionState == s )
04158         return;
04159     mEncryptionState = s;
04160     mDirty = true;
04161     KMMsgBase::setEncryptionState(s, idx);
04162 }
04163 
04164 void KMMessage::setSignatureState(KMMsgSignatureState s, int idx)
04165 {
04166     if( mSignatureState == s )
04167         return;
04168     mSignatureState = s;
04169     mDirty = true;
04170     KMMsgBase::setSignatureState(s, idx);
04171 }
04172 
04173 void KMMessage::setMDNSentState( KMMsgMDNSentState status, int idx )
04174 {
04175   if ( mMDNSentState == status )
04176     return;
04177   if ( status == 0 )
04178     status = KMMsgMDNStateUnknown;
04179   mMDNSentState = status;
04180   mDirty = true;
04181   KMMsgBase::setMDNSentState( status, idx );
04182 }
04183 
04184 //-----------------------------------------------------------------------------
04185 void KMMessage::link( const KMMessage *aMsg, KMMsgStatus aStatus )
04186 {
04187   Q_ASSERT( aStatus == KMMsgStatusReplied
04188       || aStatus == KMMsgStatusForwarded
04189       || aStatus == KMMsgStatusDeleted );
04190 
04191   TQString message = headerField( "X-KMail-Link-Message" );
04192   if ( !message.isEmpty() )
04193     message += ',';
04194   TQString type = headerField( "X-KMail-Link-Type" );
04195   if ( !type.isEmpty() )
04196     type += ',';
04197 
04198   message += TQString::number( aMsg->getMsgSerNum() );
04199   if ( aStatus == KMMsgStatusReplied )
04200     type += "reply";
04201   else if ( aStatus == KMMsgStatusForwarded )
04202     type += "forward";
04203   else if ( aStatus == KMMsgStatusDeleted )
04204     type += "deleted";
04205 
04206   setHeaderField( "X-KMail-Link-Message", message );
04207   setHeaderField( "X-KMail-Link-Type", type );
04208 }
04209 
04210 //-----------------------------------------------------------------------------
04211 void KMMessage::getLink(int n, ulong *retMsgSerNum, KMMsgStatus *retStatus) const
04212 {
04213   *retMsgSerNum = 0;
04214   *retStatus = KMMsgStatusUnknown;
04215 
04216   TQString message = headerField("X-KMail-Link-Message");
04217   TQString type = headerField("X-KMail-Link-Type");
04218   message = message.section(',', n, n);
04219   type = type.section(',', n, n);
04220 
04221   if ( !message.isEmpty() && !type.isEmpty() ) {
04222     *retMsgSerNum = message.toULong();
04223     if ( type == "reply" )
04224       *retStatus = KMMsgStatusReplied;
04225     else if ( type == "forward" )
04226       *retStatus = KMMsgStatusForwarded;
04227     else if ( type == "deleted" )
04228       *retStatus = KMMsgStatusDeleted;
04229   }
04230 }
04231 
04232 //-----------------------------------------------------------------------------
04233 DwBodyPart* KMMessage::findDwBodyPart( DwBodyPart* part, const TQString & partSpecifier )
04234 {
04235   if ( !part ) return 0;
04236   DwBodyPart* current;
04237 
04238   if ( part->partId() == partSpecifier )
04239     return part;
04240 
04241   // multipart
04242   if ( part->hasHeaders() &&
04243        part->Headers().HasContentType() &&
04244        part->Body().FirstBodyPart() &&
04245        (DwMime::kTypeMultipart == part->Headers().ContentType().Type() ) &&
04246        (current = findDwBodyPart( part->Body().FirstBodyPart(), partSpecifier )) )
04247   {
04248     return current;
04249   }
04250 
04251   // encapsulated message
04252   if ( part->Body().Message() &&
04253        part->Body().Message()->Body().FirstBodyPart() &&
04254        (current = findDwBodyPart( part->Body().Message()->Body().FirstBodyPart(),
04255                                   partSpecifier )) )
04256   {
04257     return current;
04258   }
04259 
04260   // next part
04261   return findDwBodyPart( part->Next(), partSpecifier );
04262 }
04263 
04264 //-----------------------------------------------------------------------------
04265 void KMMessage::updateBodyPart(const TQString partSpecifier, const TQByteArray & data)
04266 {
04267   if ( !data.data() || !data.size() )
04268     return;
04269 
04270   DwString content( data.data(), data.size() );
04271   if ( numBodyParts() > 0 &&
04272        partSpecifier != "0" &&
04273        partSpecifier != "TEXT" )
04274   {
04275     TQString specifier = partSpecifier;
04276     if ( partSpecifier.endsWith(".HEADER") ||
04277          partSpecifier.endsWith(".MIME") ) {
04278       // get the parent bodypart
04279       specifier = partSpecifier.section( '.', 0, -2 );
04280     }
04281 
04282     // search for the bodypart
04283     mLastUpdated = findDwBodyPart( getFirstDwBodyPart(), specifier );
04284     kdDebug(5006) << "KMMessage::updateBodyPart " << specifier << endl;
04285     if (!mLastUpdated)
04286     {
04287       kdWarning(5006) << "KMMessage::updateBodyPart - can not find part "
04288         << specifier << endl;
04289       return;
04290     }
04291     if ( partSpecifier.endsWith(".MIME") )
04292     {
04293       // update headers
04294       // get rid of EOL
04295       content.resize( QMAX( content.length(), 2 ) - 2 );
04296       // we have to delete the fields first as they might have been created by
04297       // an earlier call to DwHeaders::FieldBody
04298       mLastUpdated->Headers().DeleteAllFields();
04299       mLastUpdated->Headers().FromString( content );
04300       mLastUpdated->Headers().Parse();
04301     } else if ( partSpecifier.endsWith(".HEADER") )
04302     {
04303       // update header of embedded message
04304       mLastUpdated->Body().Message()->Headers().FromString( content );
04305       mLastUpdated->Body().Message()->Headers().Parse();
04306     } else {
04307       // update body
04308       mLastUpdated->Body().FromString( content );
04309       TQString parentSpec = partSpecifier.section( '.', 0, -2 );
04310       if ( !parentSpec.isEmpty() )
04311       {
04312         DwBodyPart* parent = findDwBodyPart( getFirstDwBodyPart(), parentSpec );
04313         if ( parent && parent->hasHeaders() && parent->Headers().HasContentType() )
04314         {
04315           const DwMediaType& contentType = parent->Headers().ContentType();
04316           if ( contentType.Type() == DwMime::kTypeMessage &&
04317                contentType.Subtype() == DwMime::kSubtypeRfc822 )
04318           {
04319             // an embedded message that is not multipart
04320             // update this directly
04321             parent->Body().Message()->Body().FromString( content );
04322           }
04323         }
04324       }
04325     }
04326 
04327   } else
04328   {
04329     // update text-only messages
04330     if ( partSpecifier == "TEXT" )
04331       deleteBodyParts(); // delete empty parts first
04332     mMsg->Body().FromString( content );
04333     mMsg->Body().Parse();
04334   }
04335   mNeedsAssembly = true;
04336   if (! partSpecifier.endsWith(".HEADER") )
04337   {
04338     // notify observers
04339     notify();
04340   }
04341 }
04342 
04343 void KMMessage::updateInvitationState()
04344 {
04345   if ( mMsg && mMsg->hasHeaders() && mMsg->Headers().HasContentType() ) {
04346     TQString cntType = mMsg->Headers().ContentType().TypeStr().c_str();
04347     cntType += '/';
04348     cntType += mMsg->Headers().ContentType().SubtypeStr().c_str();
04349     if ( cntType.lower() == "text/calendar" ) {
04350       setStatus( KMMsgStatusHasInvitation );
04351       return;
04352     }
04353   }
04354   setStatus( KMMsgStatusHasNoInvitation );
04355   return;
04356 }
04357 
04358 //-----------------------------------------------------------------------------
04359 void KMMessage::updateAttachmentState( DwBodyPart* part )
04360 {
04361   if ( !part )
04362     part = getFirstDwBodyPart();
04363 
04364   if ( !part )
04365   {
04366     // kdDebug(5006) << "updateAttachmentState - no part!" << endl;
04367     setStatus( KMMsgStatusHasNoAttach );
04368     return;
04369   }
04370 
04371   bool filenameEmpty = true;
04372   if ( part->hasHeaders() ) {
04373     if ( part->Headers().HasContentDisposition() ) {
04374       DwDispositionType cd = part->Headers().ContentDisposition();
04375       filenameEmpty = cd.Filename().empty();
04376       if ( filenameEmpty ) {
04377         // let's try if it is rfc 2231 encoded which mimelib can't handle
04378         filenameEmpty = KMMsgBase::decodeRFC2231String( KMMsgBase::extractRFC2231HeaderField( cd.AsString().c_str(), "filename" ) ).isEmpty();
04379       }
04380     }
04381 
04382     // Filename still empty? Check if the content-type has a "name" parameter and try to use that as
04383     // the attachment name
04384     if ( filenameEmpty && part->Headers().HasContentType() ) {
04385       DwMediaType contentType = part->Headers().ContentType();
04386       filenameEmpty = contentType.Name().empty();
04387       if ( filenameEmpty ) {
04388         // let's try if it is rfc 2231 encoded which mimelib can't handle
04389         filenameEmpty = KMMsgBase::decodeRFC2231String( KMMsgBase::extractRFC2231HeaderField(
04390             contentType.AsString().c_str(), "name" ) ).isEmpty();
04391       }
04392     }
04393   }
04394 
04395   if ( part->hasHeaders() &&
04396        ( ( part->Headers().HasContentDisposition() &&
04397            !part->Headers().ContentDisposition().Filename().empty() ) ||
04398          ( part->Headers().HasContentType() &&
04399            !filenameEmpty ) ) )
04400   {
04401     // now blacklist certain ContentTypes
04402     if ( !part->Headers().HasContentType() ||
04403          ( part->Headers().HasContentType() &&
04404            part->Headers().ContentType().Subtype() != DwMime::kSubtypePgpSignature &&
04405            part->Headers().ContentType().Subtype() != DwMime::kSubtypePkcs7Signature ) )
04406     {
04407       setStatus( KMMsgStatusHasAttach );
04408     }
04409     return;
04410   }
04411 
04412   // multipart
04413   if ( part->hasHeaders() &&
04414        part->Headers().HasContentType() &&
04415        part->Body().FirstBodyPart() &&
04416        (DwMime::kTypeMultipart == part->Headers().ContentType().Type() ) )
04417   {
04418     updateAttachmentState( part->Body().FirstBodyPart() );
04419   }
04420 
04421   // encapsulated message
04422   if ( part->Body().Message() &&
04423        part->Body().Message()->Body().FirstBodyPart() )
04424   {
04425     updateAttachmentState( part->Body().Message()->Body().FirstBodyPart() );
04426   }
04427 
04428   // next part
04429   if ( part->Next() )
04430     updateAttachmentState( part->Next() );
04431   else if ( attachmentState() == KMMsgAttachmentUnknown )
04432     setStatus( KMMsgStatusHasNoAttach );
04433 }
04434 
04435 void KMMessage::setBodyFromUnicode( const TQString &str, DwEntity *entity )
04436 {
04437   TQCString encoding = KMMsgBase::autoDetectCharset( charset(), KMMessage::preferredCharsets(), str );
04438   if ( encoding.isEmpty() )
04439     encoding = "utf-8";
04440   const TQTextCodec * codec = KMMsgBase::codecForName( encoding );
04441   assert( codec );
04442   TQValueList<int> dummy;
04443   setCharset( encoding, entity );
04444   setBodyAndGuessCte( codec->fromUnicode( str ), dummy, false /* no 8bit */,
04445                       false, entity );
04446 }
04447 
04448 const TQTextCodec * KMMessage::codec() const {
04449   const TQTextCodec * c = mOverrideCodec;
04450   if ( !c )
04451     // no override-codec set for this message, try the CT charset parameter:
04452     c = KMMsgBase::codecForName( charset() );
04453   if ( !c ) {
04454     // Ok, no override and nothing in the message, let's use the fallback
04455     // the user configured
04456     c = KMMsgBase::codecForName( GlobalSettings::self()->fallbackCharacterEncoding().latin1() );
04457   }
04458   if ( !c )
04459     // no charset means us-ascii (RFC 2045), so using local encoding should
04460     // be okay
04461     c = kmkernel->networkCodec();
04462   assert( c );
04463   return c;
04464 }
04465 
04466 TQString KMMessage::bodyToUnicode(const TQTextCodec* codec) const {
04467   if ( !codec )
04468     // No codec was given, so try the charset in the mail
04469     codec = this->codec();
04470   assert( codec );
04471 
04472   return codec->toUnicode( bodyDecoded() );
04473 }
04474 
04475 //-----------------------------------------------------------------------------
04476 TQCString KMMessage::mboxMessageSeparator()
04477 {
04478   TQCString str( KPIM::getFirstEmailAddress( rawHeaderField("From") ) );
04479   if ( str.isEmpty() )
04480     str = "unknown@unknown.invalid";
04481   TQCString dateStr( dateShortStr() );
04482   if ( dateStr.isEmpty() ) {
04483     time_t t = ::time( 0 );
04484     dateStr = ctime( &t );
04485     const int len = dateStr.length();
04486     if ( dateStr[len-1] == '\n' )
04487       dateStr.truncate( len - 1 );
04488   }
04489   return "From " + str + " " + dateStr + "\n";
04490 }
04491 
04492 void KMMessage::deleteWhenUnused()
04493 {
04494   sPendingDeletes << this;
04495 }
04496 
04497 DwBodyPart* KMMessage::findPart( int index )
04498 {
04499   int accu = 0;
04500   return findPartInternal( getTopLevelPart(), index, accu );
04501 }
04502 
04503 DwBodyPart* KMMessage::findPartInternal(DwEntity * root, int index, int & accu)
04504 {
04505   accu++;
04506   if ( index < accu ) // should not happen
04507     return 0;
04508   DwBodyPart *current = dynamic_cast<DwBodyPart*>( root );
04509   if ( index == accu )
04510     return current;
04511   DwBodyPart *rv = 0;
04512   if ( root->Body().FirstBodyPart() )
04513     rv = findPartInternal( root->Body().FirstBodyPart(), index, accu );
04514   if ( !rv && current && current->Next() )
04515     rv = findPartInternal( current->Next(), index, accu );
04516   if ( !rv && root->Body().Message() )
04517     rv = findPartInternal( root->Body().Message(), index, accu );
04518   return rv;
04519 }
04520