kmail

kmmessage.cpp

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