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