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