kmail

kmmessage.cpp

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