kmail Library API Documentation

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