kmail

kmmessage.cpp

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