kmail

kmmessage.cpp

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