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