kmail Library API Documentation

kmmessage.cpp

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