kmail Library API Documentation

kmmessage.cpp

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