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