// kmmessage.cpp

// if you do not want GUI elements in here then set ALLOW_GUI to 0.
#include <config.h>
// needed temporarily until KMime is replacing the partNode helper class:
#include "partNode.h"


#define ALLOW_GUI 1
#include "kmkernel.h"
#include "kmmessage.h"
#include "mailinglist-magic.h"
#include "messageproperty.h"
using KMail::MessageProperty;
#include "objecttreeparser.h"
using KMail::ObjectTreeParser;
#include "kmfolderindex.h"
#include "undostack.h"
#include "kmversion.h"
#include "headerstrategy.h"
#include "globalsettings.h"
using KMail::HeaderStrategy;
#include "kmaddrbook.h"
#include "kcursorsaver.h"
#include "templateparser.h"

#include <libkpimidentities/identity.h>
#include <libkpimidentities/identitymanager.h>
#include <libemailfunctions/email.h>

#include <kpgpblock.h>
#include <kaddrbook.h>

#include <tdeapplication.h>
#include <tdeglobal.h>
#include <tdeglobalsettings.h>
#include <kdebug.h>
#include <tdeconfig.h>
#include <tdehtml_part.h>
#include <kuser.h>
#include <kidna.h>

#include <tqcursor.h>
#include <tqtextcodec.h>
#include <tqmessagebox.h>
#include <kmime_util.h>
#include <kmime_charfreq.h>

#include <kmime_header_parsing.h>
using KMime::HeaderParsing::parseAddressList;
using namespace KMime::Types;

#include <mimelib/body.h>
#include <mimelib/field.h>
#include <mimelib/mimepp.h>
#include <mimelib/string.h>
#include <assert.h>
#include <sys/time.h>
#include <time.h>
#include <tdelocale.h>
#include <stdlib.h>
#include <unistd.h>
#include "util.h"

#if ALLOW_GUI
#include <tdemessagebox.h>
#endif

using namespace KMime;

static DwString emptyString("");

// Values that are set from the config file with KMMessage::readConfig()
static TQString sReplyLanguage, sReplyStr, sReplyAllStr, sIndentPrefixStr;
static bool sSmartQuote,
  sWordWrap;
static int sWrapCol;
static TQStringList sPrefCharsets;

TQString KMMessage::sForwardStr;
const HeaderStrategy * KMMessage::sHeaderStrategy = HeaderStrategy::rich();
//helper
static void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart );

TQValueList<KMMessage*> KMMessage::sPendingDeletes;

//-----------------------------------------------------------------------------
KMMessage::KMMessage(DwMessage* aMsg)
  : KMMsgBase()
{
  init( aMsg );
  // aMsg might need assembly
  mNeedsAssembly = true;
}

//-----------------------------------------------------------------------------
KMMessage::KMMessage(KMFolder* parent): KMMsgBase(parent)
{
  init();
}


//-----------------------------------------------------------------------------
KMMessage::KMMessage(KMMsgInfo& msgInfo): KMMsgBase()
{
  init();
  // now overwrite a few from the msgInfo
  mMsgSize = msgInfo.msgSize();
  mFolderOffset = msgInfo.folderOffset();
  mStatus = msgInfo.status();
  mEncryptionState = msgInfo.encryptionState();
  mSignatureState = msgInfo.signatureState();
  mMDNSentState = msgInfo.mdnSentState();
  mDate = msgInfo.date();
  mFileName = msgInfo.fileName();
  KMMsgBase::assign(&msgInfo);
}


//-----------------------------------------------------------------------------
KMMessage::KMMessage(const KMMessage& other) :
    KMMsgBase( other ),
    ISubject(),
    mMsg(0)
{
  init(); // to be safe
  assign( other );
}

void KMMessage::init( DwMessage* aMsg )
{
  mNeedsAssembly = false;
  if ( aMsg ) {
    mMsg = aMsg;
  } else {
    mMsg = new DwMessage;
  }
  mOverrideCodec = 0;
  mDecodeHTML = false;
  mComplete = true;
  mReadyToShow = true;
  mMsgSize = 0;
  mMsgLength = 0;
  mFolderOffset = 0;
  mStatus  = KMMsgStatusNew;
  mEncryptionState = KMMsgEncryptionStateUnknown;
  mSignatureState = KMMsgSignatureStateUnknown;
  mMDNSentState = KMMsgMDNStateUnknown;
  mDate    = 0;
  mUnencryptedMsg = 0;
  mLastUpdated = 0;
  mCursorPos = 0;
  mMsgInfo = 0;
  mIsParsed = false;
}

void KMMessage::assign( const KMMessage& other )
{
  MessageProperty::forget( this );
  delete mMsg;
  delete mUnencryptedMsg;

  mNeedsAssembly = true;//other.mNeedsAssembly;
  if( other.mMsg )
    mMsg = new DwMessage( *(other.mMsg) );
  else
    mMsg = 0;
  mOverrideCodec = other.mOverrideCodec;
  mDecodeHTML = other.mDecodeHTML;
  mMsgSize = other.mMsgSize;
  mMsgLength = other.mMsgLength;
  mFolderOffset = other.mFolderOffset;
  mStatus  = other.mStatus;
  mEncryptionState = other.mEncryptionState;
  mSignatureState = other.mSignatureState;
  mMDNSentState = other.mMDNSentState;
  mIsParsed = other.mIsParsed;
  mDate    = other.mDate;
  if( other.hasUnencryptedMsg() )
    mUnencryptedMsg = new KMMessage( *other.unencryptedMsg() );
  else
    mUnencryptedMsg = 0;
  setDrafts( other.drafts() );
  setTemplates( other.templates() );
  //mFileName = ""; // we might not want to copy the other messages filename (?)
  //KMMsgBase::assign( &other );
}

//-----------------------------------------------------------------------------
KMMessage::~KMMessage()
{
  delete mMsgInfo;
  delete mMsg;
  kmkernel->undoStack()->msgDestroyed( this );
}


//-----------------------------------------------------------------------------
void KMMessage::setReferences(const TQCString& aStr)
{
  if (aStr.isNull()) return;
  mMsg->Headers().References().FromString(aStr);
  mNeedsAssembly = true;
}


//-----------------------------------------------------------------------------
TQCString KMMessage::id() const
{
  DwHeaders& header = mMsg->Headers();
  if (header.HasMessageId())
    return KMail::Util::CString( header.MessageId().AsString() );
  else
    return "";
}


//-----------------------------------------------------------------------------
//WARNING: This method updates the memory resident cache of serial numbers
//WARNING: held in MessageProperty, but it does not update the persistent
//WARNING: store of serial numbers on the file system that is managed by
//WARNING: KMMsgDict
void KMMessage::setMsgSerNum(unsigned long newMsgSerNum)
{
  MessageProperty::setSerialCache( this, newMsgSerNum );
}


//-----------------------------------------------------------------------------
bool KMMessage::isMessage() const
{
  return true;
}

//-----------------------------------------------------------------------------
bool KMMessage::transferInProgress() const
{
  return MessageProperty::transferInProgress( getMsgSerNum() );
}


//-----------------------------------------------------------------------------
void KMMessage::setTransferInProgress(bool value, bool force)
{
  MessageProperty::setTransferInProgress( getMsgSerNum(), value, force );
  if ( !transferInProgress() && sPendingDeletes.contains( this ) ) {
    sPendingDeletes.remove( this );
    if ( parent() ) {
      int idx = parent()->find( this );
      if ( idx > 0 ) {
        parent()->removeMsg( idx );
      }
    }
  }
}



bool KMMessage::isUrgent() const {
  return headerField( "Priority" ).contains( "urgent", false )
    || headerField( "X-Priority" ).startsWith( "2" );
}

//-----------------------------------------------------------------------------
void KMMessage::setUnencryptedMsg( KMMessage* unencrypted )
{
  delete mUnencryptedMsg;
  mUnencryptedMsg = unencrypted;
}

//-----------------------------------------------------------------------------
//FIXME: move to libemailfunctions
KPIM::EmailParseResult KMMessage::isValidEmailAddressList( const TQString& aStr,
                                                           TQString& brokenAddress )
{
  if ( aStr.isEmpty() ) {
     return KPIM::AddressEmpty;
  }

  TQStringList list = KPIM::splitEmailAddrList( aStr );
  for( TQStringList::const_iterator it = list.begin(); it != list.end(); ++it ) {
    KPIM::EmailParseResult errorCode = KPIM::isValidEmailAddress( *it );
      if ( errorCode != KPIM::AddressOk ) {
      brokenAddress = ( *it );
      return errorCode;
    }
  }
  return KPIM::AddressOk;
}

//-----------------------------------------------------------------------------
const DwString& KMMessage::asDwString() const
{
  if (mNeedsAssembly)
  {
    mNeedsAssembly = false;
    mMsg->Assemble();
  }
  return mMsg->AsString();
}

//-----------------------------------------------------------------------------
const DwMessage* KMMessage::asDwMessage()
{
  if (mNeedsAssembly)
  {
    mNeedsAssembly = false;
    mMsg->Assemble();
  }
  return mMsg;
}

//-----------------------------------------------------------------------------
TQCString KMMessage::asString() const {
  return KMail::Util::CString( asDwString() );
}


TQByteArray KMMessage::asSendableString() const
{
  KMMessage msg( new DwMessage( *this->mMsg ) );
  msg.removePrivateHeaderFields();
  msg.removeHeaderField("Bcc");
  return KMail::Util::ByteArray( msg.asDwString() ); // and another copy again!
}

TQCString KMMessage::headerAsSendableString() const
{
  KMMessage msg( new DwMessage( *this->mMsg ) );
  msg.removePrivateHeaderFields();
  msg.removeHeaderField("Bcc");
  return msg.headerAsString().latin1();
}

void KMMessage::removePrivateHeaderFields() {
  removeHeaderField("Status");
  removeHeaderField("X-Status");
  removeHeaderField("X-KMail-EncryptionState");
  removeHeaderField("X-KMail-SignatureState");
  removeHeaderField("X-KMail-MDN-Sent");
  removeHeaderField("X-KMail-Transport");
  removeHeaderField("X-KMail-Identity");
  removeHeaderField("X-KMail-Fcc");
  removeHeaderField("X-KMail-Redirect-From");
  removeHeaderField("X-KMail-Link-Message");
  removeHeaderField("X-KMail-Link-Type");
  removeHeaderField( "X-KMail-Markup" );
}

//-----------------------------------------------------------------------------
void KMMessage::setStatusFields()
{
  char str[2] = { 0, 0 };

  setHeaderField("Status", status() & KMMsgStatusNew ? "R" : "RO");
  setHeaderField("X-Status", statusToStr(status()));

  str[0] = (char)encryptionState();
  setHeaderField("X-KMail-EncryptionState", str);

  str[0] = (char)signatureState();
  //kdDebug(5006) << "Setting SignatureState header field to " << str[0] << endl;
  setHeaderField("X-KMail-SignatureState", str);

  str[0] = static_cast<char>( mdnSentState() );
  setHeaderField("X-KMail-MDN-Sent", str);

  // We better do the assembling ourselves now to prevent the
  // mimelib from changing the message *body*.  (khz, 10.8.2002)
  mNeedsAssembly = false;
  mMsg->Headers().Assemble();
  mMsg->Assemble( mMsg->Headers(),
                  mMsg->Body() );
}


//----------------------------------------------------------------------------
TQString KMMessage::headerAsString() const
{
  DwHeaders& header = mMsg->Headers();
  header.Assemble();
  if ( header.AsString().empty() )
    return TQString();
  return TQString::fromLatin1( header.AsString().c_str() );
}


//-----------------------------------------------------------------------------
DwMediaType& KMMessage::dwContentType()
{
  return mMsg->Headers().ContentType();
}

void KMMessage::fromByteArray( const TQByteArray & ba, bool setStatus ) {
  return fromDwString( DwString( ba.data(), ba.size() ), setStatus );
}

void KMMessage::fromString( const TQCString & str, bool aSeStatus ) {
  return fromDwString( KMail::Util::dwString( str ), aSeStatus );
}

void KMMessage::fromDwString(const DwString& str, bool aSeStatus)
{
  delete mMsg;
  mMsg = new DwMessage;
  mMsg->FromString( str );
  mMsg->Parse();

  if (aSeStatus) {
    setStatus(headerField("Status").latin1(), headerField("X-Status").latin1());
    setEncryptionStateChar( headerField("X-KMail-EncryptionState").at(0) );
    setSignatureStateChar(  headerField("X-KMail-SignatureState").at(0) );
    setMDNSentState( static_cast<KMMsgMDNSentState>( headerField("X-KMail-MDN-Sent").at(0).latin1() ) );
  }
  if ( invitationState() == KMMsgInvitationUnknown && readyToShow() )
    updateInvitationState();
  if ( attachmentState() == KMMsgAttachmentUnknown && readyToShow() )
    updateAttachmentState();

  mNeedsAssembly = false;
  mDate = date();
}


//-----------------------------------------------------------------------------
TQString KMMessage::formatString(const TQString& aStr) const
{
  TQString result, str;
  TQChar ch;
  uint j;

  if (aStr.isEmpty())
    return aStr;

  unsigned int strLength(aStr.length());
  for (uint i=0; i<strLength;) {
    ch = aStr[i++];
    if (ch == '%') {
      ch = aStr[i++];
      switch ((char)ch) {
      case 'D':
	/* I'm not too sure about this change. Is it not possible
	   to have a long form of the date used? I don't
	   like this change to a short XX/XX/YY date format.
	   At least not for the default. -sanders */
	result += KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized,
						    date(), sReplyLanguage, false );
        break;
      case 'e':
        result += from();
        break;
      case 'F':
        result += fromStrip();
        break;
      case 'f':
		{
        str = fromStrip();

        for (j=0; str[j]>' '; j++)
          ;
		unsigned int strLength(str.length());
        for (; j < strLength && str[j] <= ' '; j++)
          ;
        result += str[0];
        if (str[j]>' ')
          result += str[j];
        else
          if (str[1]>' ')
            result += str[1];
		}
        break;
      case 'T':
        result += toStrip();
        break;
      case 't':
        result += to();
        break;
      case 'C':
        result += ccStrip();
        break;
      case 'c':
        result += cc();
        break;
      case 'S':
        result += subject();
        break;
      case '_':
        result += ' ';
        break;
      case 'L':
        result += "\n";
        break;
      case '%':
        result += '%';
        break;
      default:
        result += '%';
        result += ch;
        break;
      }
    } else
      result += ch;
  }
  return result;
}

static void removeTrailingSpace( TQString &line )
{
   int i = line.length()-1;
   while( (i >= 0) && ((line[i] == ' ') || (line[i] == '\t')))
      i--;
   line.truncate( i+1);
}

static TQString splitLine( TQString &line)
{
    removeTrailingSpace( line );
    int i = 0;
    int j = -1;
    int l = line.length();

    // TODO: Replace tabs with spaces first.

    while(i < l)
    {
       TQChar c = line[i];
       if ((c == '>') || (c == ':') || (c == '|'))
          j = i+1;
       else if ((c != ' ') && (c != '\t'))
          break;
       i++;
    }

    if ( j <= 0 )
    {
       return "";
    }
    if ( i == l )
    {
       TQString result = line.left(j);
       line = TQString();
       return result;
    }

    TQString result = line.left(j);
    line = line.mid(j);
    return result;
}

static TQString flowText(TQString &text, const TQString& indent, int maxLength)
{
   maxLength--;
   if (text.isEmpty())
   {
      return indent+"<NULL>\n";
   }
   TQString result;
   while (1)
   {
      int i;
      if ((int) text.length() > maxLength)
      {
         i = maxLength;
         while( (i >= 0) && (text[i] != ' '))
            i--;
         if (i <= 0)
         {
            // Couldn't break before maxLength.
            i = maxLength;
//            while( (i < (int) text.length()) && (text[i] != ' '))
//               i++;
         }
      }
      else
      {
         i = text.length();
      }

      TQString line = text.left(i);
      if (i < (int) text.length())
         text = text.mid(i);
      else
         text = TQString();

      result += indent + line + '\n';

      if (text.isEmpty())
         return result;
   }
}

static bool flushPart(TQString &msg, TQStringList &part,
                      const TQString &indent, int maxLength)
{
   maxLength -= indent.length();
   if (maxLength < 20) maxLength = 20;

   // Remove empty lines at end of quote
   while ((part.begin() != part.end()) && part.last().isEmpty())
   {
      part.remove(part.fromLast());
   }

   TQString text;
   for(TQStringList::Iterator it2 = part.begin();
       it2 != part.end();
       it2++)
   {
      TQString line = (*it2);

      if (line.isEmpty())
      {
         if (!text.isEmpty())
            msg += flowText(text, indent, maxLength);
         msg += indent + '\n';
      }
      else
      {
         if (text.isEmpty())
            text = line;
         else
            text += ' '+line.stripWhiteSpace();

         if (((int) text.length() < maxLength) || ((int) line.length() < (maxLength-10)))
            msg += flowText(text, indent, maxLength);
      }
   }
   if (!text.isEmpty())
      msg += flowText(text, indent, maxLength);

   bool appendEmptyLine = true;
   if (!part.count())
     appendEmptyLine = false;

   part.clear();
   return appendEmptyLine;
}

static TQString stripSignature( const TQString & msg, bool clearSigned ) {
  if ( clearSigned )
    return msg.left( msg.findRev( TQRegExp( "\n--\\s?\n" ) ) );
  else
    return msg.left( msg.findRev( "\n-- \n" ) );
}

TQString KMMessage::smartQuote( const TQString & msg, int maxLineLength )
{
  TQStringList part;
  TQString oldIndent;
  bool firstPart = true;


  const TQStringList lines = TQStringList::split('\n', msg, true);

  TQString result;
  for(TQStringList::const_iterator it = lines.begin();
      it != lines.end();
      ++it)
  {
     TQString line = *it;

     const TQString indent = splitLine( line );

     if ( line.isEmpty())
     {
        if (!firstPart)
           part.append(TQString());
        continue;
     };

     if (firstPart)
     {
        oldIndent = indent;
        firstPart = false;
     }

     if (oldIndent != indent)
     {
        TQString fromLine;
        // Search if the last non-blank line could be "From" line
        if (part.count() && (oldIndent.length() < indent.length()))
        {
           TQStringList::Iterator it2 = part.fromLast();
           while( (it2 != part.end()) && (*it2).isEmpty())
             --it2;

           if ((it2 != part.end()) && ((*it2).endsWith(":")))
           {
              fromLine = oldIndent + (*it2) + '\n';
              part.remove(it2);
           }
        }
        if (flushPart( result, part, oldIndent, maxLineLength))
        {
           if (oldIndent.length() > indent.length())
              result += indent + '\n';
           else
              result += oldIndent + '\n';
        }
        if (!fromLine.isEmpty())
        {
           result += fromLine;
        }
        oldIndent = indent;
     }
     part.append(line);
  }
  flushPart( result, part, oldIndent, maxLineLength);
  return result;
}


//-----------------------------------------------------------------------------
void KMMessage::parseTextStringFromDwPart( partNode * root,
                                           TQCString& parsedString,
                                           const TQTextCodec*& codec,
                                           bool& isHTML ) const
{
  if ( !root ) return;

  isHTML = false;
  partNode * curNode = root->findType( DwMime::kTypeText,
                               DwMime::kSubtypeUnknown,
                               true,
                               false );
  kdDebug(5006) << "\n\n======= KMMessage::parseTextStringFromDwPart()   -    "
                << ( curNode ? "text part found!\n" : "sorry, no text node!\n" ) << endl;
  if( curNode ) {
    isHTML = DwMime::kSubtypeHtml == curNode->subType();
    // now parse the TEXT message part we want to quote
    ObjectTreeParser otp( 0, 0, true, false, true );
    otp.parseObjectTree( curNode );
    parsedString = otp.rawReplyString();
    codec = curNode->msgPart().codec();
  }
}

//-----------------------------------------------------------------------------

TQString KMMessage::asPlainTextFromObjectTree( partNode *root, bool aStripSignature,
                                              bool allowDecryption ) const
{
  Q_ASSERT( root );
  Q_ASSERT( root->processed() );

  TQCString parsedString;
  bool isHTML = false;
  const TQTextCodec * codec = 0;

  if ( !root ) return TQString();
  parseTextStringFromDwPart( root, parsedString, codec, isHTML );

  if ( mOverrideCodec || !codec )
    codec = this->codec();

  if ( parsedString.isEmpty() )
    return TQString();

  bool clearSigned = false;
  TQString result;

  // decrypt
  if ( allowDecryption ) {
    TQPtrList<Kpgp::Block> pgpBlocks;
    TQStrList nonPgpBlocks;
    if ( Kpgp::Module::prepareMessageForDecryption( parsedString,
                pgpBlocks,
                nonPgpBlocks ) ) {
      // Only decrypt/strip off the signature if there is only one OpenPGP
      // block in the message
      if ( pgpBlocks.count() == 1 ) {
        Kpgp::Block * block = pgpBlocks.first();
        if ( block->type() == Kpgp::PgpMessageBlock ||
             block->type() == Kpgp::ClearsignedBlock ) {
          if ( block->type() == Kpgp::PgpMessageBlock ) {
            // try to decrypt this OpenPGP block
            block->decrypt();
          } else {
            // strip off the signature
            block->verify();
            clearSigned = true;
          }

          result = codec->toUnicode( nonPgpBlocks.first() )
                 + codec->toUnicode( block->text() )
                 + codec->toUnicode( nonPgpBlocks.last() );
        }
      }
    }
  }

  if ( result.isEmpty() ) {
    result = codec->toUnicode( parsedString );
    if ( result.isEmpty() )
      return result;
  }

  // html -> plaintext conversion, if necessary:
  if ( isHTML && mDecodeHTML ) {
    TDEHTMLPart htmlPart;
    htmlPart.setOnlyLocalReferences( true );
    htmlPart.setMetaRefreshEnabled( false );
    htmlPart.setPluginsEnabled( false );
    htmlPart.setJScriptEnabled( false );
    htmlPart.setJavaEnabled( false );
    htmlPart.begin();
    htmlPart.write( result );
    htmlPart.end();
    htmlPart.selectAll();
    result = htmlPart.selectedText();
  }

  // strip the signature (footer):
  if ( aStripSignature )
    return stripSignature( result, clearSigned );
  else
    return result;
}

//-----------------------------------------------------------------------------

TQString KMMessage::asPlainText( bool aStripSignature, bool allowDecryption ) const
{
  partNode *root = partNode::fromMessage( this );
  if ( !root )
    return TQString();

  ObjectTreeParser otp;
  otp.parseObjectTree( root );
  TQString result = asPlainTextFromObjectTree( root, aStripSignature, allowDecryption );
  delete root;
  return result;
}

TQString KMMessage::asQuotedString( const TQString& aHeaderStr,
				   const TQString& aIndentStr,
				   const TQString& selection /* = TQString() */,
				   bool aStripSignature /* = true */,
				   bool allowDecryption /* = true */) const
{
  TQString content = selection.isEmpty() ?
    asPlainText( aStripSignature, allowDecryption ) : selection ;

  // Remove blank lines at the beginning:
  const int firstNonWS = content.find( TQRegExp( "\\S" ) );
  const int lineStart = content.findRev( '\n', firstNonWS );
  if ( lineStart >= 0 )
    content.remove( 0, static_cast<unsigned int>( lineStart ) );

  const TQString indentStr = formatString( aIndentStr );

  content.replace( '\n', '\n' + indentStr );
  content.prepend( indentStr );
  content += '\n';

  const TQString headerStr = formatString( aHeaderStr );
  if ( sSmartQuote && sWordWrap )
    return headerStr + smartQuote( content, sWrapCol );
  return headerStr + content;
}

//-----------------------------------------------------------------------------
KMMessage* KMMessage::createReply( KMail::ReplyStrategy replyStrategy,
                                   TQString selection /* = TQString() */,
                                   bool noQuote /* = false */,
                                   bool allowDecryption /* = true */,
                                   const TQString &tmpl /* = TQString() */,
                                   const TQString &originatingAccount /* = TQString() */ )
{
  KMMessage* msg = new KMMessage;
  TQString mailingListStr, replyToStr, toStr;
  TQStringList mailingListAddresses;
  TQCString refStr, headerName;
  bool replyAll = true;

  msg->initFromMessage(this);

  MailingList::name(this, headerName, mailingListStr);
  replyToStr = replyTo();

  msg->setCharset("utf-8");

  // determine the mailing list posting address
  if ( parent() && parent()->isMailingListEnabled() &&
       !parent()->mailingListPostAddress().isEmpty() ) {
    mailingListAddresses << parent()->mailingListPostAddress();
  }
  if ( headerField("List-Post").find( "mailto:", 0, false ) != -1 ) {
    TQString listPost = headerField("List-Post");
    TQRegExp rx( "<mailto:([^@>]+)@([^>]+)>", false );
    if ( rx.search( listPost, 0 ) != -1 ) // matched
      mailingListAddresses << rx.cap(1) + '@' + rx.cap(2);
  }

  // use the "On ... Joe User wrote:" header by default
  switch( replyStrategy ) {
  case KMail::ReplySmart : {
    if ( !headerField( "Mail-Followup-To" ).isEmpty() ) {
      toStr = headerField( "Mail-Followup-To" );
    }
    else if ( !replyToStr.isEmpty() ) {
      // assume a Reply-To header mangling mailing list
      toStr = replyToStr;
    }
    else if ( !mailingListAddresses.isEmpty() ) {
      toStr = mailingListAddresses[0];
    }
    else {
      // doesn't seem to be a mailing list, reply to From: address
      toStr = from();
      //replyStr = sReplyStr; // reply to author, so use "On ... you wrote:"
      replyAll = false;
    }
    // strip all my addresses from the list of recipients
    TQStringList recipients = KPIM::splitEmailAddrList( toStr );
    toStr = stripMyAddressesFromAddressList( recipients ).join(", ");
    // ... unless the list contains only my addresses (reply to self)
    if ( toStr.isEmpty() && !recipients.isEmpty() )
      toStr = recipients[0];

    break;
  }
  case KMail::ReplyList : {
    if ( !headerField( "Mail-Followup-To" ).isEmpty() ) {
      toStr = headerField( "Mail-Followup-To" );
    }
    else if ( !mailingListAddresses.isEmpty() ) {
      toStr = mailingListAddresses[0];
    }
    else if ( !replyToStr.isEmpty() ) {
      // assume a Reply-To header mangling mailing list
      toStr = replyToStr;
    }
    // strip all my addresses from the list of recipients
    TQStringList recipients = KPIM::splitEmailAddrList( toStr );
    toStr = stripMyAddressesFromAddressList( recipients ).join(", ");

    break;
  }
  case KMail::ReplyAll : {
    TQStringList recipients;
    TQStringList ccRecipients;

    // add addresses from the Reply-To header to the list of recipients
    if( !replyToStr.isEmpty() ) {
      recipients += KPIM::splitEmailAddrList( replyToStr );
      // strip all possible mailing list addresses from the list of Reply-To
      // addresses
      for ( TQStringList::const_iterator it = mailingListAddresses.begin();
            it != mailingListAddresses.end();
            ++it ) {
        recipients = stripAddressFromAddressList( *it, recipients );
      }
    }

    if ( !mailingListAddresses.isEmpty() ) {
      // this is a mailing list message
      if ( recipients.isEmpty() && !from().isEmpty() ) {
        // The sender didn't set a Reply-to address, so we add the From
        // address to the list of CC recipients.
        ccRecipients += from();
        kdDebug(5006) << "Added " << from() << " to the list of CC recipients"
                      << endl;
      }
      // if it is a mailing list, add the posting address
      recipients.prepend( mailingListAddresses[0] );
    }
    else {
      // this is a normal message
      if ( recipients.isEmpty() && !from().isEmpty() ) {
        // in case of replying to a normal message only then add the From
        // address to the list of recipients if there was no Reply-to address
        recipients += from();
        kdDebug(5006) << "Added " << from() << " to the list of recipients"
                      << endl;
      }
    }

    // strip all my addresses from the list of recipients
    toStr = stripMyAddressesFromAddressList( recipients ).join(", ");

    // merge To header and CC header into a list of CC recipients
    if( !cc().isEmpty() || !to().isEmpty() ) {
      TQStringList list;
      if (!to().isEmpty())
        list += KPIM::splitEmailAddrList(to());
      if (!cc().isEmpty())
        list += KPIM::splitEmailAddrList(cc());
      for( TQStringList::Iterator it = list.begin(); it != list.end(); ++it ) {
        if(    !addressIsInAddressList( *it, recipients )
            && !addressIsInAddressList( *it, ccRecipients ) ) {
          ccRecipients += *it;
          kdDebug(5006) << "Added " << *it << " to the list of CC recipients"
                        << endl;
        }
      }
    }

    if ( !ccRecipients.isEmpty() ) {
      // strip all my addresses from the list of CC recipients
      ccRecipients = stripMyAddressesFromAddressList( ccRecipients );

      // in case of a reply to self toStr might be empty. if that's the case
      // then propagate a cc recipient to To: (if there is any).
      if ( toStr.isEmpty() && !ccRecipients.isEmpty() ) {
        toStr = ccRecipients[0];
        ccRecipients.pop_front();
      }

      msg->setCc( ccRecipients.join(", ") );
    }

    if ( toStr.isEmpty() && !recipients.isEmpty() ) {
      // reply to self without other recipients
      toStr = recipients[0];
    }
    break;
  }
  case KMail::ReplyAuthor : {
    if ( !replyToStr.isEmpty() ) {
      TQStringList recipients = KPIM::splitEmailAddrList( replyToStr );
      // strip the mailing list post address from the list of Reply-To
      // addresses since we want to reply in private
      for ( TQStringList::const_iterator it = mailingListAddresses.begin();
            it != mailingListAddresses.end();
            ++it ) {
        recipients = stripAddressFromAddressList( *it, recipients );
      }
      if ( !recipients.isEmpty() ) {
        toStr = recipients.join(", ");
      }
      else {
        // there was only the mailing list post address in the Reply-To header,
        // so use the From address instead
        toStr = from();
      }
    }
    else if ( !from().isEmpty() ) {
      toStr = from();
    }
    replyAll = false;
    break;
  }
  case KMail::ReplyNone : {
    // the addressees will be set by the caller
  }
  }

  if (!originatingAccount.isEmpty()) {
    msg->setOriginatingAccountName(originatingAccount);
  }

  msg->setTo(toStr);

  refStr = getRefStr();
  if (!refStr.isEmpty())
    msg->setReferences(refStr);
  //In-Reply-To = original msg-id
  msg->setReplyToId(msgId());

//   if (!noQuote) {
//     if( selectionIsBody ){
//       TQCString cStr = selection.latin1();
//       msg->setBody( cStr );
//     }else{
//       msg->setBody(asQuotedString(replyStr + "\n", sIndentPrefixStr, selection,
// 				  sSmartQuote, allowDecryption).utf8());
//     }
//   }

  msg->setSubject( replySubject() );
  msg->setHeaderField( "X-KMail-QuotePrefix",
		                         formatString( GlobalSettings::self()->quoteString() ) );
  if( !noQuote ) {
     TemplateParser parser( msg, ( replyAll ? TemplateParser::ReplyAll : TemplateParser::Reply ) );
     parser.setAllowDecryption( allowDecryption );
     if ( GlobalSettings::quoteSelectionOnly() ) {
       parser.setSelection( selection );
     }
     if ( !tmpl.isEmpty() ) {
       parser.process( tmpl, this );
     } else {
       parser.process( this );
     }
  }
  // setStatus(KMMsgStatusReplied);
  msg->link(this, KMMsgStatusReplied);

  if ( parent() && parent()->putRepliesInSameFolder() )
    msg->setFcc( parent()->idString() );

  // replies to an encrypted message should be encrypted as well
  if ( encryptionState() == KMMsgPartiallyEncrypted ||
       encryptionState() == KMMsgFullyEncrypted ) {
    msg->setEncryptionState( KMMsgFullyEncrypted );
  }

  return msg;
}


//-----------------------------------------------------------------------------
TQCString KMMessage::getRefStr() const
{
  TQCString firstRef, lastRef, refStr, retRefStr;
  int i, j;

  refStr = headerField("References").stripWhiteSpace().latin1();

  if (refStr.isEmpty())
    return headerField("Message-Id").latin1();

  i = refStr.find('<');
  j = refStr.find('>');
  firstRef = refStr.mid(i, j-i+1);
  if (!firstRef.isEmpty())
    retRefStr = firstRef + ' ';

  i = refStr.findRev('<');
  j = refStr.findRev('>');

  lastRef = refStr.mid(i, j-i+1);
  if (!lastRef.isEmpty() && lastRef != firstRef)
    retRefStr += lastRef + ' ';

  retRefStr += headerField("Message-Id").latin1();
  return retRefStr;
}


KMMessage* KMMessage::createRedirect( const TQString &toStr )
{
  // copy the message 1:1
  KMMessage* msg = new KMMessage( new DwMessage( *this->mMsg ) );
  KMMessagePart msgPart;

  uint id = 0;
  TQString strId = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace();
  if ( !strId.isEmpty())
    id = strId.toUInt();
  const KPIM::Identity & ident =
    kmkernel->identityManager()->identityForUoidOrDefault( id );

  // X-KMail-Redirect-From: content
  TQString strByWayOf = TQString("%1 (by way of %2 <%3>)")
    .arg( from() )
    .arg( ident.fullName() )
    .arg( ident.primaryEmailAddress() );

  // Resent-From: content
  TQString strFrom = TQString("%1 <%2>")
    .arg( ident.fullName() )
    .arg( ident.primaryEmailAddress() );

  // format the current date to be used in Resent-Date:
  TQString origDate = msg->headerField( "Date" );
  msg->setDateToday();
  TQString newDate = msg->headerField( "Date" );
  // make sure the Date: header is valid
  if ( origDate.isEmpty() )
    msg->removeHeaderField( "Date" );
  else
    msg->setHeaderField( "Date", origDate );

  // prepend Resent-*: headers (c.f. RFC2822 3.6.6)
  msg->setHeaderField( "Resent-Message-ID", generateMessageId( msg->sender() ),
                       Structured, true);
  msg->setHeaderField( "Resent-Date", newDate, Structured, true );
  msg->setHeaderField( "Resent-To",   toStr,   Address, true );
  msg->setHeaderField( "Resent-From", strFrom, Address, true );

  msg->setHeaderField( "X-KMail-Redirect-From", strByWayOf );
  msg->setHeaderField( "X-KMail-Recipients", toStr, Address );

  msg->link(this, KMMsgStatusForwarded);

  return msg;
}


//-----------------------------------------------------------------------------
TQCString KMMessage::createForwardBody()
{
  TQString s;
  TQCString str;

  if (sHeaderStrategy == HeaderStrategy::all()) {
    s = "\n\n----------  " + sForwardStr + "  ----------\n\n";
    s += headerAsString();
    str = asQuotedString(s, "", TQString(), false, false).utf8();
    str += "\n-------------------------------------------------------\n";
  } else {
    s = "\n\n----------  " + sForwardStr + "  ----------\n\n";
    s += "Subject: " + subject() + "\n";
    s += "Date: "
         + KMime::DateFormatter::formatDate( KMime::DateFormatter::Localized,
                                             date(), sReplyLanguage, false )
         + "\n";
    s += "From: " + from() + "\n";
    s += "To: " + to() + "\n";
    if (!cc().isEmpty()) s += "Cc: " + cc() + "\n";
    s += "\n";
    str = asQuotedString(s, "", TQString(), false, false).utf8();
    str += "\n-------------------------------------------------------\n";
  }

  return str;
}

void KMMessage::sanitizeHeaders( const TQStringList& whiteList )
{
   // Strip out all headers apart from the content description and other
   // whitelisted ones, because we don't want to inherit them.
   DwHeaders& header = mMsg->Headers();
   DwField* field = header.FirstField();
   DwField* nextField;
   while (field)
   {
     nextField = field->Next();
     if ( field->FieldNameStr().find( "ontent" ) == DwString::npos
             && !whiteList.contains( TQString::fromLatin1( field->FieldNameStr().c_str() ) ) )
       header.RemoveField(field);
     field = nextField;
   }
   mMsg->Assemble();
}

//-----------------------------------------------------------------------------
KMMessage* KMMessage::createForward( const TQString &tmpl /* = TQString() */ )
{
  KMMessage* msg = new KMMessage();

  // If this is a multipart mail or if the main part is only the text part,
  // Make an identical copy of the mail, minus headers, so attachments are
  // preserved
  if ( type() == DwMime::kTypeMultipart ||
     ( type() == DwMime::kTypeText && subtype() == DwMime::kSubtypePlain ) ) {
    // ## slow, we could probably use: delete msg->mMsg; msg->mMsg = new DwMessage( this->mMsg );
    msg->fromDwString( this->asDwString() );
    // remember the type and subtype, initFromMessage sets the contents type to
    // text/plain, via initHeader, for unclear reasons
    DwMediaType oldContentType = msg->mMsg->Headers().ContentType();

    msg->sanitizeHeaders();

    // strip blacklisted parts
    TQStringList blacklist = GlobalSettings::self()->mimetypesToStripWhenInlineForwarding();
    for ( TQStringList::Iterator it = blacklist.begin(); it != blacklist.end(); ++it ) {
      TQString entry = (*it);
      int sep = entry.find( '/' );
      TQCString type = entry.left( sep ).latin1();
      TQCString subtype = entry.mid( sep+1 ).latin1();
      kdDebug( 5006 ) << "Looking for blacklisted type: " << type << "/" << subtype << endl;
      while ( DwBodyPart * part = msg->findDwBodyPart( type, subtype ) ) {
        msg->mMsg->Body().RemoveBodyPart( part );
      }
    }
    msg->mMsg->Assemble();
    msg->initFromMessage( this );

    //restore type
    msg->mMsg->Headers().ContentType().FromString( oldContentType.AsString() );
    msg->mMsg->Headers().ContentType().Parse();
    msg->mMsg->Assemble();
  }
  else if( type() == DwMime::kTypeText && subtype() == DwMime::kSubtypeHtml ) {
    // This is non-multipart html mail. Let`s make it text/plain and allow
    // template parser do the hard job.
    msg->initFromMessage( this );
    msg->setType( DwMime::kTypeText );
    msg->setSubtype( DwMime::kSubtypeHtml );
    msg->mNeedsAssembly = true;
    msg->cleanupHeader();
  }
  else {
    // This is a non-multipart, non-text mail (e.g. text/calendar). Construct
    // a multipart/mixed mail and add the original body as an attachment.
    msg->initFromMessage( this );
    msg->removeHeaderField("Content-Type");
    msg->removeHeaderField("Content-Transfer-Encoding");
    // Modify the ContentType directly (replaces setAutomaticFields(true))
    DwHeaders & header = msg->mMsg->Headers();
    header.MimeVersion().FromString("1.0");
    DwMediaType & contentType = msg->dwContentType();
    contentType.SetType( DwMime::kTypeMultipart );
    contentType.SetSubtype( DwMime::kSubtypeMixed );
    contentType.CreateBoundary(0);
    contentType.Assemble();

    // empty text part
    KMMessagePart msgPart;
    bodyPart( 0, &msgPart );
    msg->addBodyPart(&msgPart);
    // the old contents of the mail
    KMMessagePart secondPart;
    secondPart.setType( type() );
    secondPart.setSubtype( subtype() );
    secondPart.setBody( mMsg->Body().AsString() );
    // use the headers of the original mail
    applyHeadersToMessagePart( mMsg->Headers(), &secondPart );
    msg->addBodyPart(&secondPart);
    msg->mNeedsAssembly = true;
    msg->cleanupHeader();
  }
  // TQString st = TQString::fromUtf8(createForwardBody());

  msg->setSubject( forwardSubject() );

  TemplateParser parser( msg, TemplateParser::Forward );
  if ( !tmpl.isEmpty() ) {
    parser.process( tmpl, this );
  } else {
    parser.process( this );
  }

  // TQCString encoding = autoDetectCharset(charset(), sPrefCharsets, msg->body());
  // if (encoding.isEmpty()) encoding = "utf-8";
  // msg->setCharset(encoding);

  // force utf-8
  // msg->setCharset( "utf-8" );

  msg->link(this, KMMsgStatusForwarded);
  return msg;
}

static const struct {
  const char * dontAskAgainID;
  bool         canDeny;
  const char * text;
} mdnMessageBoxes[] = {
  { "mdnNormalAsk", true,
    I18N_NOOP("This message contains a request to return a notification "
	      "about your reception of the message.\n"
	      "You can either ignore the request or let KMail send a "
	      "\"denied\" or normal response.") },
  { "mdnUnknownOption", false,
    I18N_NOOP("This message contains a request to send a notification "
	      "about your reception of the message.\n"
	      "It contains a processing instruction that is marked as "
	      "\"required\", but which is unknown to KMail.\n"
	      "You can either ignore the request or let KMail send a "
	      "\"failed\" response.") },
  { "mdnMultipleAddressesInReceiptTo", true,
    I18N_NOOP("This message contains a request to send a notification "
	      "about your reception of the message,\n"
	      "but it is requested to send the notification to more "
	      "than one address.\n"
	      "You can either ignore the request or let KMail send a "
	      "\"denied\" or normal response.") },
  { "mdnReturnPathEmpty", true,
    I18N_NOOP("This message contains a request to send a notification "
	      "about your reception of the message,\n"
	      "but there is no return-path set.\n"
	      "You can either ignore the request or let KMail send a "
	      "\"denied\" or normal response.") },
  { "mdnReturnPathNotInReceiptTo", true,
    I18N_NOOP("This message contains a request to send a notification "
	      "about your reception of the message,\n"
	      "but the return-path address differs from the address "
	      "the notification was requested to be sent to.\n"
	      "You can either ignore the request or let KMail send a "
	      "\"denied\" or normal response.") },
};

static const int numMdnMessageBoxes
      = sizeof mdnMessageBoxes / sizeof *mdnMessageBoxes;


static int requestAdviceOnMDN( const char * what ) {
  for ( int i = 0 ; i < numMdnMessageBoxes ; ++i ) {
    if ( !qstrcmp( what, mdnMessageBoxes[i].dontAskAgainID ) ) {
      if ( mdnMessageBoxes[i].canDeny ) {
        const KCursorSaver saver( TQCursor::ArrowCursor );
        int answer = TQMessageBox::information( 0,
                                               i18n("Message Disposition Notification Request"),
                                               i18n( mdnMessageBoxes[i].text ),
                                               i18n("&Ignore"), i18n("Send \"&denied\""), i18n("&Send") );
        return answer ? answer + 1 : 0 ; // map to "mode" in createMDN
      } else {
        const KCursorSaver saver( TQCursor::ArrowCursor );
        int answer = TQMessageBox::information( 0,
                                               i18n("Message Disposition Notification Request"),
                                               i18n( mdnMessageBoxes[i].text ),
                                               i18n("&Ignore"), i18n("&Send") );
        return answer ? answer + 2 : 0 ; // map to "mode" in createMDN
      }
    }
  }
  kdWarning(5006) << "didn't find data for message box \""
                  << what << "\"" << endl;
  return 0;
}

KMMessage* KMMessage::createMDN( MDN::ActionMode a,
				 MDN::DispositionType d,
				 bool allowGUI,
				 TQValueList<MDN::DispositionModifier> m )
{
  // RFC 2298: At most one MDN may be issued on behalf of each
  // particular recipient by their user agent.  That is, once an MDN
  // has been issued on behalf of a recipient, no further MDNs may be
  // issued on behalf of that recipient, even if another disposition
  // is performed on the message.
//#define MDN_DEBUG 1
#ifndef MDN_DEBUG
  if ( mdnSentState() != KMMsgMDNStateUnknown &&
       mdnSentState() != KMMsgMDNNone )
    return 0;
#else
  char st[2]; st[0] = (char)mdnSentState(); st[1] = 0;
  kdDebug(5006) << "mdnSentState() == '" << st << "'" << endl;
#endif

  // RFC 2298: An MDN MUST NOT be generated in response to an MDN.
  if ( findDwBodyPart( DwMime::kTypeMessage,
		       DwMime::kSubtypeDispositionNotification ) ) {
    setMDNSentState( KMMsgMDNIgnore );
    return 0;
  }

  // extract where to send to:
  TQString receiptTo = headerField("Disposition-Notification-To");
  if ( receiptTo.stripWhiteSpace().isEmpty() ) return 0;
  receiptTo.remove( '\n' );


  MDN::SendingMode s = MDN::SentAutomatically; // set to manual if asked user
  TQString special; // fill in case of error, warning or failure
  TDEConfigGroup mdnConfig( KMKernel::config(), "MDN" );

  // default:
  int mode = mdnConfig.readNumEntry( "default-policy", 0 );
  if ( !mode || mode < 0 || mode > 3 ) {
    // early out for ignore:
    setMDNSentState( KMMsgMDNIgnore );
    return 0;
  }

  // RFC 2298: An importance of "required" indicates that
  // interpretation of the parameter is necessary for proper
  // generation of an MDN in response to this request.  If a UA does
  // not understand the meaning of the parameter, it MUST NOT generate
  // an MDN with any disposition type other than "failed" in response
  // to the request.
  TQString notificationOptions = headerField("Disposition-Notification-Options");
  if ( notificationOptions.contains( "required", false ) ) {
    // ### hacky; should parse...
    // There is a required option that we don't understand. We need to
    // ask the user what we should do:
    if ( !allowGUI ) return 0; // don't setMDNSentState here!
    mode = requestAdviceOnMDN( "mdnUnknownOption" );
    s = MDN::SentManually;

    special = i18n("Header \"Disposition-Notification-Options\" contained "
		   "required, but unknown parameter");
    d = MDN::Failed;
    m.clear(); // clear modifiers
  }

  // RFC 2298: [ Confirmation from the user SHOULD be obtained (or no
  // MDN sent) ] if there is more than one distinct address in the
  // Disposition-Notification-To header.
  kdDebug(5006) << "KPIM::splitEmailAddrList(receiptTo): "
	    << KPIM::splitEmailAddrList(receiptTo).join("\n") << endl;
  if ( KPIM::splitEmailAddrList(receiptTo).count() > 1 ) {
    if ( !allowGUI ) return 0; // don't setMDNSentState here!
    mode = requestAdviceOnMDN( "mdnMultipleAddressesInReceiptTo" );
    s = MDN::SentManually;
  }

  // RFC 2298: MDNs SHOULD NOT be sent automatically if the address in
  // the Disposition-Notification-To header differs from the address
  // in the Return-Path header. [...] Confirmation from the user
  // SHOULD be obtained (or no MDN sent) if there is no Return-Path
  // header in the message [...]
  AddrSpecList returnPathList = extractAddrSpecs("Return-Path");
  TQString returnPath = returnPathList.isEmpty() ? TQString()
    : returnPathList.front().localPart + '@' + returnPathList.front().domain ;
  kdDebug(5006) << "clean return path: " << returnPath << endl;
  if ( returnPath.isEmpty() || !receiptTo.contains( returnPath, false ) ) {
    if ( !allowGUI ) return 0; // don't setMDNSentState here!
    mode = requestAdviceOnMDN( returnPath.isEmpty() ?
			       "mdnReturnPathEmpty" :
			       "mdnReturnPathNotInReceiptTo" );
    s = MDN::SentManually;
  }

  if ( a != KMime::MDN::AutomaticAction ) {
    //TODO: only ingore user settings for AutomaticAction if requested
    if ( mode == 1 ) { // ask
      if ( !allowGUI ) return 0; // don't setMDNSentState here!
      mode = requestAdviceOnMDN( "mdnNormalAsk" );
      s = MDN::SentManually; // asked user
    }

    switch ( mode ) {
      case 0: // ignore:
        setMDNSentState( KMMsgMDNIgnore );
        return 0;
      default:
      case 1:
        kdFatal(5006) << "KMMessage::createMDN(): The \"ask\" mode should "
                                                  << "never appear here!" << endl;
        break;
      case 2: // deny
        d = MDN::Denied;
        m.clear();
        break;
      case 3:
        break;
    }
  }


  // extract where to send from:
  TQString finalRecipient = kmkernel->identityManager()
    ->identityForUoidOrDefault( identityUoid() ).fullEmailAddr();

  //
  // Generate message:
  //

  KMMessage * receipt = new KMMessage();
  receipt->initFromMessage( this );
  receipt->removeHeaderField("Content-Type");
  receipt->removeHeaderField("Content-Transfer-Encoding");
  // Modify the ContentType directly (replaces setAutomaticFields(true))
  DwHeaders & header = receipt->mMsg->Headers();
  header.MimeVersion().FromString("1.0");
  DwMediaType & contentType = receipt->dwContentType();
  contentType.SetType( DwMime::kTypeMultipart );
  contentType.SetSubtype( DwMime::kSubtypeReport );
  contentType.CreateBoundary(0);
  receipt->mNeedsAssembly = true;
  receipt->setContentTypeParam( "report-type", "disposition-notification" );

  TQString description = replaceHeadersInString( MDN::descriptionFor( d, m ) );

  // text/plain part:
  KMMessagePart firstMsgPart;
  firstMsgPart.setTypeStr( "text" );
  firstMsgPart.setSubtypeStr( "plain" );
  firstMsgPart.setBodyFromUnicode( description );
  receipt->addBodyPart( &firstMsgPart );

  // message/disposition-notification part:
  KMMessagePart secondMsgPart;
  secondMsgPart.setType( DwMime::kTypeMessage );
  secondMsgPart.setSubtype( DwMime::kSubtypeDispositionNotification );
  //secondMsgPart.setCharset( "us-ascii" );
  //secondMsgPart.setCteStr( "7bit" );
  secondMsgPart.setBodyEncoded( MDN::dispositionNotificationBodyContent(
	                    finalRecipient,
			    rawHeaderField("Original-Recipient"),
			    id(), /* Message-ID */
			    d, a, s, m, special ) );
  receipt->addBodyPart( &secondMsgPart );

  // message/rfc822 or text/rfc822-headers body part:
  int num = mdnConfig.readNumEntry( "quote-message", 0 );
  if ( num < 0 || num > 2 ) num = 0;
  MDN::ReturnContent returnContent = static_cast<MDN::ReturnContent>( num );

  KMMessagePart thirdMsgPart;
  switch ( returnContent ) {
  case MDN::All:
    thirdMsgPart.setTypeStr( "message" );
    thirdMsgPart.setSubtypeStr( "rfc822" );
    thirdMsgPart.setBody( asSendableString() );
    receipt->addBodyPart( &thirdMsgPart );
    break;
  case MDN::HeadersOnly:
    thirdMsgPart.setTypeStr( "text" );
    thirdMsgPart.setSubtypeStr( "rfc822-headers" );
    thirdMsgPart.setBody( headerAsSendableString() );
    receipt->addBodyPart( &thirdMsgPart );
    break;
  case MDN::Nothing:
  default:
    break;
  };

  receipt->setTo( receiptTo );
  receipt->setSubject( "Message Disposition Notification" );
  receipt->setReplyToId( msgId() );
  receipt->setReferences( getRefStr() );

  receipt->cleanupHeader();

  kdDebug(5006) << "final message:\n" + receipt->asString() << endl;

  //
  // Set "MDN sent" status:
  //
  KMMsgMDNSentState state = KMMsgMDNStateUnknown;
  switch ( d ) {
  case MDN::Displayed:   state = KMMsgMDNDisplayed;  break;
  case MDN::Deleted:     state = KMMsgMDNDeleted;    break;
  case MDN::Dispatched:  state = KMMsgMDNDispatched; break;
  case MDN::Processed:   state = KMMsgMDNProcessed;  break;
  case MDN::Denied:      state = KMMsgMDNDenied;     break;
  case MDN::Failed:      state = KMMsgMDNFailed;     break;
  };
  setMDNSentState( state );

  return receipt;
}

TQString KMMessage::replaceHeadersInString( const TQString & s ) const {
  TQString result = s;
  TQRegExp rx( "\\$\\{([a-z0-9-]+)\\}", false );
  Q_ASSERT( rx.isValid() );

  TQRegExp rxDate( "\\$\\{date\\}" );
  Q_ASSERT( rxDate.isValid() );

  TQString sDate = KMime::DateFormatter::formatDate(
                      KMime::DateFormatter::Localized, date() );

  int idx = 0;
  if( ( idx = rxDate.search( result, idx ) ) != -1  ) {
    result.replace( idx, rxDate.matchedLength(), sDate );
  }

  idx = 0;
  while ( ( idx = rx.search( result, idx ) ) != -1 ) {
    TQString replacement = headerField( TQString(rx.cap(1)).latin1() );
    result.replace( idx, rx.matchedLength(), replacement );
    idx += replacement.length();
  }
  return result;
}

KMMessage* KMMessage::createDeliveryReceipt() const
{
  TQString str, receiptTo;
  KMMessage *receipt;

  receiptTo = headerField("Disposition-Notification-To");
  if ( receiptTo.stripWhiteSpace().isEmpty() ) return 0;
  receiptTo.remove( '\n' );

  receipt = new KMMessage;
  receipt->initFromMessage(this);
  receipt->setTo(receiptTo);
  receipt->setSubject(i18n("Receipt: ") + subject());

  str  = "Your message was successfully delivered.";
  str += "\n\n---------- Message header follows ----------\n";
  str += headerAsString();
  str += "--------------------------------------------\n";
  // Conversion to latin1 is correct here as Mail headers should contain
  // ascii only
  receipt->setBody(str.latin1());
  receipt->setAutomaticFields();

  return receipt;
}


void KMMessage::applyIdentity( uint id )
{
  const KPIM::Identity & ident =
    kmkernel->identityManager()->identityForUoidOrDefault( id );

  if(ident.fullEmailAddr().isEmpty())
    setFrom("");
  else
    setFrom(ident.fullEmailAddr());

  if(ident.replyToAddr().isEmpty())
    setReplyTo("");
  else
    setReplyTo(ident.replyToAddr());

  if(ident.bcc().isEmpty())
    setBcc("");
  else
    setBcc(ident.bcc());

  if (ident.organization().isEmpty())
    removeHeaderField("Organization");
  else
    setHeaderField("Organization", ident.organization());

  if (ident.isDefault())
    removeHeaderField("X-KMail-Identity");
  else
    setHeaderField("X-KMail-Identity", TQString::number( ident.uoid() ));

  if ( ident.transport().isEmpty() )
    removeHeaderField( "X-KMail-Transport" );
  else
    setHeaderField( "X-KMail-Transport", ident.transport() );

  if ( ident.fcc().isEmpty() )
    setFcc( TQString() );
  else
    setFcc( ident.fcc() );

  if ( ident.drafts().isEmpty() )
    setDrafts( TQString() );
  else
    setDrafts( ident.drafts() );

  if ( ident.templates().isEmpty() )
    setTemplates( TQString() );
  else
    setTemplates( ident.templates() );

}

//-----------------------------------------------------------------------------
void KMMessage::initHeader( uint id )
{
  applyIdentity( id );
  setTo("");
  setSubject("");
  setDateToday();

  setHeaderField("User-Agent", "KMail/" KMAIL_VERSION );
  // This will allow to change Content-Type:
  setHeaderField("Content-Type","text/plain");
}

uint KMMessage::identityUoid() const {
  TQString idString = headerField("X-KMail-Identity").stripWhiteSpace();
  bool ok = false;
  int id = idString.toUInt( &ok );

  if ( !ok || id == 0 )
    id = kmkernel->identityManager()->identityForAddress( to() + ", " + cc() ).uoid();
  if ( id == 0 && parent() )
    id = parent()->identity();

  return id;
}


//-----------------------------------------------------------------------------
void KMMessage::initFromMessage(const KMMessage *msg, bool idHeaders)
{
  uint id = msg->identityUoid();

  if ( idHeaders ) initHeader(id);
  else setHeaderField("X-KMail-Identity", TQString::number(id));
  if (!msg->headerField("X-KMail-Transport").isEmpty())
    setHeaderField("X-KMail-Transport", msg->headerField("X-KMail-Transport"));
}


//-----------------------------------------------------------------------------
void KMMessage::cleanupHeader()
{
  DwHeaders& header = mMsg->Headers();
  DwField* field = header.FirstField();
  DwField* nextField;

  if (mNeedsAssembly) mMsg->Assemble();
  mNeedsAssembly = false;

  while (field)
  {
    nextField = field->Next();
    if (field->FieldBody()->AsString().empty())
    {
      header.RemoveField(field);
      mNeedsAssembly = true;
    }
    field = nextField;
  }
}


//-----------------------------------------------------------------------------
void KMMessage::setAutomaticFields(bool aIsMulti)
{
  DwHeaders& header = mMsg->Headers();
  header.MimeVersion().FromString("1.0");

  if (aIsMulti || numBodyParts() > 1)
  {
    // Set the type to 'Multipart' and the subtype to 'Mixed'
    DwMediaType& contentType = dwContentType();
    contentType.SetType(   DwMime::kTypeMultipart);
    contentType.SetSubtype(DwMime::kSubtypeMixed );

    // Create a random printable string and set it as the boundary parameter
    contentType.CreateBoundary(0);
  }
  mNeedsAssembly = true;
}


//-----------------------------------------------------------------------------
TQString KMMessage::dateStr() const
{
  TDEConfigGroup general( KMKernel::config(), "General" );
  DwHeaders& header = mMsg->Headers();
  time_t unixTime;

  if (!header.HasDate()) return "";
  unixTime = header.Date().AsUnixTime();

  //kdDebug(5006)<<"####  Date = "<<header.Date().AsString().c_str()<<endl;

  return KMime::DateFormatter::formatDate(
      static_cast<KMime::DateFormatter::FormatType>(general.readNumEntry( "dateFormat", KMime::DateFormatter::Fancy )),
      unixTime, general.readEntry( "customDateFormat" ));
}


//-----------------------------------------------------------------------------
TQCString KMMessage::dateShortStr() const
{
  DwHeaders& header = mMsg->Headers();
  time_t unixTime;

  if (!header.HasDate()) return "";
  unixTime = header.Date().AsUnixTime();

  TQCString result = ctime(&unixTime);
  int len = result.length();
  if (result[len-1]=='\n')
    result.truncate(len-1);

  return result;
}


//-----------------------------------------------------------------------------
TQString KMMessage::dateIsoStr() const
{
  DwHeaders& header = mMsg->Headers();
  time_t unixTime;

  if (!header.HasDate()) return "";
  unixTime = header.Date().AsUnixTime();

  char cstr[64];
  strftime(cstr, 63, "%Y-%m-%d %H:%M:%S", localtime(&unixTime));
  return TQString(cstr);
}


//-----------------------------------------------------------------------------
time_t KMMessage::date() const
{
  time_t res = ( time_t )-1;
  DwHeaders& header = mMsg->Headers();
  if (header.HasDate())
    res = header.Date().AsUnixTime();
  return res;
}


//-----------------------------------------------------------------------------
void KMMessage::setDateToday()
{
  struct timeval tval;
  gettimeofday(&tval, 0);
  setDate((time_t)tval.tv_sec);
}


//-----------------------------------------------------------------------------
void KMMessage::setDate(time_t aDate)
{
  mDate = aDate;
  mMsg->Headers().Date().FromCalendarTime(aDate);
  mMsg->Headers().Date().Assemble();
  mNeedsAssembly = true;
  mDirty = true;
}


//-----------------------------------------------------------------------------
void KMMessage::setDate(const TQCString& aStr)
{
  DwHeaders& header = mMsg->Headers();

  header.Date().FromString(aStr);
  header.Date().Parse();
  mNeedsAssembly = true;
  mDirty = true;

  if (header.HasDate())
    mDate = header.Date().AsUnixTime();
}


//-----------------------------------------------------------------------------
TQString KMMessage::to() const
{
  // handle To same as Cc below, bug 80747
  TQValueList<TQCString> rawHeaders = rawHeaderFields( "To" );
  TQStringList headers;
  for ( TQValueList<TQCString>::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) {
    headers << *it;
  }
  return KPIM::normalizeAddressesAndDecodeIDNs( headers.join( ", " ) );
}


//-----------------------------------------------------------------------------
void KMMessage::setTo(const TQString& aStr)
{
  setHeaderField( "To", aStr, Address );
}

//-----------------------------------------------------------------------------
TQString KMMessage::toStrip() const
{
  return stripEmailAddr( to() );
}

//-----------------------------------------------------------------------------
TQString KMMessage::replyTo() const
{
  return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("Reply-To") );
}


//-----------------------------------------------------------------------------
void KMMessage::setReplyTo(const TQString& aStr)
{
  setHeaderField( "Reply-To", aStr, Address );
}


//-----------------------------------------------------------------------------
void KMMessage::setReplyTo(KMMessage* aMsg)
{
  setHeaderField( "Reply-To", aMsg->from(), Address );
}


//-----------------------------------------------------------------------------
TQString KMMessage::cc() const
{
  // get the combined contents of all Cc headers (as workaround for invalid
  // messages with multiple Cc headers)
  TQValueList<TQCString> rawHeaders = rawHeaderFields( "Cc" );
  TQStringList headers;
  for ( TQValueList<TQCString>::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) {
    headers << *it;
  }
  return KPIM::normalizeAddressesAndDecodeIDNs( headers.join( ", " ) );
}


//-----------------------------------------------------------------------------
void KMMessage::setCc(const TQString& aStr)
{
  setHeaderField( "Cc", aStr, Address );
}


//-----------------------------------------------------------------------------
TQString KMMessage::ccStrip() const
{
  return stripEmailAddr( cc() );
}


//-----------------------------------------------------------------------------
TQString KMMessage::bcc() const
{
  return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("Bcc") );
}


//-----------------------------------------------------------------------------
void KMMessage::setBcc(const TQString& aStr)
{
  setHeaderField( "Bcc", aStr, Address );
}

//-----------------------------------------------------------------------------
TQString KMMessage::fcc() const
{
  return headerField( "X-KMail-Fcc" );
}


//-----------------------------------------------------------------------------
void KMMessage::setFcc( const TQString &aStr )
{
  setHeaderField( "X-KMail-Fcc", aStr );
}

//-----------------------------------------------------------------------------
void KMMessage::setDrafts( const TQString &aStr )
{
  mDrafts = aStr;
}

//-----------------------------------------------------------------------------
void KMMessage::setTemplates( const TQString &aStr )
{
  mTemplates = aStr;
}

//-----------------------------------------------------------------------------
TQString KMMessage::who() const
{
  if (mParent)
    return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField(mParent->whoField().utf8()) );
  return from();
}


//-----------------------------------------------------------------------------
TQString KMMessage::from() const
{
  return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("From") );
}


//-----------------------------------------------------------------------------
void KMMessage::setFrom(const TQString& bStr)
{
  TQString aStr = bStr;
  if (aStr.isNull())
    aStr = "";
  setHeaderField( "From", aStr, Address );
  mDirty = true;
}


//-----------------------------------------------------------------------------
TQString KMMessage::fromStrip() const
{
  return stripEmailAddr( from() );
}

//-----------------------------------------------------------------------------
TQString KMMessage::sender() const {
  AddrSpecList asl = extractAddrSpecs( "Sender" );
  if ( asl.empty() )
    asl = extractAddrSpecs( "From" );
  if ( asl.empty() )
    return TQString();
  return asl.front().asString();
}

//-----------------------------------------------------------------------------
TQString KMMessage::subject() const
{
  return headerField("Subject");
}


//-----------------------------------------------------------------------------
void KMMessage::setSubject(const TQString& aStr)
{
  setHeaderField("Subject",aStr);
  mDirty = true;
}


//-----------------------------------------------------------------------------
TQString KMMessage::xmark() const
{
  return headerField("X-KMail-Mark");
}


//-----------------------------------------------------------------------------
void KMMessage::setXMark(const TQString& aStr)
{
  setHeaderField("X-KMail-Mark", aStr);
  mDirty = true;
}


//-----------------------------------------------------------------------------
TQString KMMessage::replyToId() const
{
  int leftAngle, rightAngle;
  TQString replyTo, references;

  replyTo = headerField("In-Reply-To");
  // search the end of the (first) message id in the In-Reply-To header
  rightAngle = replyTo.find( '>' );
  if (rightAngle != -1)
    replyTo.truncate( rightAngle + 1 );
  // now search the start of the message id
  leftAngle = replyTo.findRev( '<' );
  if (leftAngle != -1)
    replyTo = replyTo.mid( leftAngle );

  // if we have found a good message id we can return immediately
  // We ignore mangled In-Reply-To headers which are created by a
  // misconfigured Mutt. They look like this <"from foo"@bar.baz>, i.e.
  // they contain double quotes and spaces. We only check for '"'.
  if (!replyTo.isEmpty() && (replyTo[0] == '<') &&
      ( -1 == replyTo.find( '"' ) ) )
    return replyTo;

  references = headerField("References");
  leftAngle = references.findRev( '<' );
  if (leftAngle != -1)
    references = references.mid( leftAngle );
  rightAngle = references.find( '>' );
  if (rightAngle != -1)
    references.truncate( rightAngle + 1 );

  // if we found a good message id in the References header return it
  if (!references.isEmpty() && references[0] == '<')
    return references;
  // else return the broken message id we found in the In-Reply-To header
  else
    return replyTo;
}


//-----------------------------------------------------------------------------
TQString KMMessage::replyToIdMD5() const {
  return base64EncodedMD5( replyToId() );
}

//-----------------------------------------------------------------------------
TQString KMMessage::references() const
{
  int leftAngle, rightAngle;
  TQString references = headerField( "References" );

  // keep the last two entries for threading
  leftAngle = references.findRev( '<' );
  leftAngle = references.findRev( '<', leftAngle - 1 );
  if( leftAngle != -1 )
    references = references.mid( leftAngle );
  rightAngle = references.findRev( '>' );
  if( rightAngle != -1 )
    references.truncate( rightAngle + 1 );

  if( !references.isEmpty() && references[0] == '<' )
    return references;
  else
    return TQString();
}

//-----------------------------------------------------------------------------
TQString KMMessage::replyToAuxIdMD5() const
{
  TQString result = references();
  // references contains two items, use the first one
  // (the second to last reference)
  const int rightAngle = result.find( '>' );
  if( rightAngle != -1 )
    result.truncate( rightAngle + 1 );

  return base64EncodedMD5( result );
}

//-----------------------------------------------------------------------------
TQString KMMessage::strippedSubjectMD5() const {
  return base64EncodedMD5( stripOffPrefixes( subject() ), true /*utf8*/ );
}

//-----------------------------------------------------------------------------
TQString KMMessage::subjectMD5() const {
  return base64EncodedMD5( subject(), true /*utf8*/ );
}

//-----------------------------------------------------------------------------
bool KMMessage::subjectIsPrefixed() const {
  return subjectMD5() != strippedSubjectMD5();
}

//-----------------------------------------------------------------------------
void KMMessage::setReplyToId(const TQString& aStr)
{
  setHeaderField("In-Reply-To", aStr);
  mDirty = true;
}


//-----------------------------------------------------------------------------
TQString KMMessage::msgId() const
{
  TQString msgId = headerField("Message-Id");

  // search the end of the message id
  const int rightAngle = msgId.find( '>' );
  if (rightAngle != -1)
    msgId.truncate( rightAngle + 1 );
  // now search the start of the message id
  const int leftAngle = msgId.findRev( '<' );
  if (leftAngle != -1)
    msgId = msgId.mid( leftAngle );
  return msgId;
}


//-----------------------------------------------------------------------------
TQString KMMessage::msgIdMD5() const {
  return base64EncodedMD5( msgId() );
}


//-----------------------------------------------------------------------------
void KMMessage::setMsgId(const TQString& aStr)
{
  setHeaderField("Message-Id", aStr);
  mDirty = true;
}

//-----------------------------------------------------------------------------
size_t KMMessage::msgSizeServer() const {
  return headerField( "X-Length" ).toULong();
}


//-----------------------------------------------------------------------------
void KMMessage::setMsgSizeServer(size_t size)
{
  setHeaderField("X-Length", TQCString().setNum(size));
  mDirty = true;
}

//-----------------------------------------------------------------------------
ulong KMMessage::UID() const {
  return headerField( "X-UID" ).toULong();
}


//-----------------------------------------------------------------------------
void KMMessage::setUID(ulong uid)
{
  setHeaderField("X-UID", TQCString().setNum(uid));
  mDirty = true;
}

//-----------------------------------------------------------------------------
AddressList KMMessage::splitAddrField( const TQCString & str )
{
  AddressList result;
  const char * scursor = str.begin();
  if ( !scursor )
    return AddressList();
  const char * const send = str.begin() + str.length();
  if ( !parseAddressList( scursor, send, result ) )
    kdDebug(5006) << "Error in address splitting: parseAddressList returned false!"
                  << endl;
  return result;
}

AddressList KMMessage::headerAddrField( const TQCString & aName ) const {
  return KMMessage::splitAddrField( rawHeaderField( aName ) );
}

AddrSpecList KMMessage::extractAddrSpecs( const TQCString & header ) const {
  AddressList al = headerAddrField( header );
  AddrSpecList result;
  for ( AddressList::const_iterator ait = al.begin() ; ait != al.end() ; ++ait )
    for ( MailboxList::const_iterator mit = (*ait).mailboxList.begin() ; mit != (*ait).mailboxList.end() ; ++mit )
      result.push_back( (*mit).addrSpec );
  return result;
}

TQCString KMMessage::rawHeaderField( const TQCString & name ) const {
  if ( name.isEmpty() ) return TQCString();

  DwHeaders & header = mMsg->Headers();
  DwField * field = header.FindField( name );

  if ( !field ) return TQCString();

  return header.FieldBody( name.data() ).AsString().c_str();
}

TQValueList<TQCString> KMMessage::rawHeaderFields( const TQCString& field ) const
{
  if ( field.isEmpty() || !mMsg->Headers().FindField( field ) )
    return TQValueList<TQCString>();

  std::vector<DwFieldBody*> v = mMsg->Headers().AllFieldBodies( field.data() );
  TQValueList<TQCString> headerFields;
  for ( uint i = 0; i < v.size(); ++i ) {
    headerFields.append( v[i]->AsString().c_str() );
  }

  return headerFields;
}

TQString KMMessage::headerField(const TQCString& aName) const
{
  if ( aName.isEmpty() )
    return TQString();

  if ( !mMsg->Headers().FindField( aName ) )
    return TQString();

  return decodeRFC2047String( mMsg->Headers().FieldBody( aName.data() ).AsString().c_str(),
                              charset() );

}

TQStringList KMMessage::headerFields( const TQCString& field ) const
{
  if ( field.isEmpty() || !mMsg->Headers().FindField( field ) )
    return TQStringList();

  std::vector<DwFieldBody*> v = mMsg->Headers().AllFieldBodies( field.data() );
  TQStringList headerFields;
  for ( uint i = 0; i < v.size(); ++i ) {
    headerFields.append( decodeRFC2047String( v[i]->AsString().c_str(), charset() ) );
  }

  return headerFields;
}

//-----------------------------------------------------------------------------
void KMMessage::removeHeaderField(const TQCString& aName)
{
  DwHeaders & header = mMsg->Headers();
  DwField * field = header.FindField(aName);
  if (!field) return;

  header.RemoveField(field);
  mNeedsAssembly = true;
}

//-----------------------------------------------------------------------------
void KMMessage::removeHeaderFields(const TQCString& aName)
{
  DwHeaders & header = mMsg->Headers();
  while ( DwField * field = header.FindField(aName) ) {
    header.RemoveField(field);
    mNeedsAssembly = true;
  }
}


//-----------------------------------------------------------------------------
void KMMessage::setHeaderField( const TQCString& aName, const TQString& bValue,
                                HeaderFieldType type, bool prepend )
{
#if 0
  if ( type != Unstructured )
    kdDebug(5006) << "KMMessage::setHeaderField( \"" << aName << "\", \""
                << bValue << "\", " << type << " )" << endl;
#endif
  if (aName.isEmpty()) return;

  DwHeaders& header = mMsg->Headers();

  DwString str;
  DwField* field;
  TQCString aValue;
  if (!bValue.isEmpty())
  {
    TQString value = bValue;
    if ( type == Address )
      value = KPIM::normalizeAddressesAndEncodeIDNs( value );
#if 0
    if ( type != Unstructured )
      kdDebug(5006) << "value: \"" << value << "\"" << endl;
#endif
    TQCString encoding = autoDetectCharset( charset(), sPrefCharsets, value );
    if (encoding.isEmpty())
       encoding = "utf-8";
    aValue = encodeRFC2047String( value, encoding );
#if 0
    if ( type != Unstructured )
      kdDebug(5006) << "aValue: \"" << aValue << "\"" << endl;
#endif
  }
  str = aName.data();
  if (str[str.length()-1] != ':') str += ": ";
  else str += ' ';
  if ( !aValue.isEmpty() )
    str += aValue.data();
  if (str[str.length()-1] != '\n') str += '\n';

  field = new DwField(str, mMsg);
  field->Parse();

  if ( prepend )
    header.AddFieldAt( 1, field );
  else
    header.AddOrReplaceField( field );
  mNeedsAssembly = true;
}


//-----------------------------------------------------------------------------
TQCString KMMessage::typeStr() const
{
  DwHeaders& header = mMsg->Headers();
  if (header.HasContentType()) return header.ContentType().TypeStr().c_str();
  else return "";
}


//-----------------------------------------------------------------------------
int KMMessage::type() const
{
  DwHeaders& header = mMsg->Headers();
  if (header.HasContentType()) return header.ContentType().Type();
  else return DwMime::kTypeNull;
}


//-----------------------------------------------------------------------------
void KMMessage::setTypeStr(const TQCString& aStr)
{
  dwContentType().SetTypeStr(DwString(aStr));
  dwContentType().Parse();
  mNeedsAssembly = true;
}


//-----------------------------------------------------------------------------
void KMMessage::setType(int aType)
{
  dwContentType().SetType(aType);
  dwContentType().Assemble();
  mNeedsAssembly = true;
}



//-----------------------------------------------------------------------------
TQCString KMMessage::subtypeStr() const
{
  DwHeaders& header = mMsg->Headers();
  if (header.HasContentType()) return header.ContentType().SubtypeStr().c_str();
  else return "";
}


//-----------------------------------------------------------------------------
int KMMessage::subtype() const
{
  DwHeaders& header = mMsg->Headers();
  if (header.HasContentType()) return header.ContentType().Subtype();
  else return DwMime::kSubtypeNull;
}


//-----------------------------------------------------------------------------
void KMMessage::setSubtypeStr(const TQCString& aStr)
{
  dwContentType().SetSubtypeStr(DwString(aStr));
  dwContentType().Parse();
  mNeedsAssembly = true;
}


//-----------------------------------------------------------------------------
void KMMessage::setSubtype(int aSubtype)
{
  dwContentType().SetSubtype(aSubtype);
  dwContentType().Assemble();
  mNeedsAssembly = true;
}


//-----------------------------------------------------------------------------
void KMMessage::setDwMediaTypeParam( DwMediaType &mType,
                                     const TQCString& attr,
                                     const TQCString& val )
{
  mType.Parse();
  DwParameter *param = mType.FirstParameter();
  while(param) {
    if (!kasciistricmp(param->Attribute().c_str(), attr))
      break;
    else
      param = param->Next();
  }
  if (!param){
    param = new DwParameter;
    param->SetAttribute(DwString( attr ));
    mType.AddParameter( param );
  }
  else
    mType.SetModified();
  param->SetValue(DwString( val ));
  mType.Assemble();
}


//-----------------------------------------------------------------------------
void KMMessage::setContentTypeParam(const TQCString& attr, const TQCString& val)
{
  if (mNeedsAssembly) mMsg->Assemble();
  mNeedsAssembly = false;
  setDwMediaTypeParam( dwContentType(), attr, val );
  mNeedsAssembly = true;
}


//-----------------------------------------------------------------------------
TQCString KMMessage::contentTransferEncodingStr() const
{
  DwHeaders& header = mMsg->Headers();
  if (header.HasContentTransferEncoding())
    return header.ContentTransferEncoding().AsString().c_str();
  else return "";
}


//-----------------------------------------------------------------------------
int KMMessage::contentTransferEncoding( DwEntity *entity ) const
{
  if ( !entity )
    entity = mMsg;

  DwHeaders& header = entity->Headers();
  if ( header.HasContentTransferEncoding() )
    return header.ContentTransferEncoding().AsEnum();
  else return DwMime::kCteNull;
}


//-----------------------------------------------------------------------------
void KMMessage::setContentTransferEncodingStr( const TQCString& cteString,
                                               DwEntity *entity )
{
  if ( !entity )
    entity = mMsg;

  entity->Headers().ContentTransferEncoding().FromString( cteString );
  entity->Headers().ContentTransferEncoding().Parse();
  mNeedsAssembly = true;
}


//-----------------------------------------------------------------------------
void KMMessage::setContentTransferEncoding( int cte, DwEntity *entity )
{
  if ( !entity )
    entity = mMsg;

  entity->Headers().ContentTransferEncoding().FromEnum( cte );
  mNeedsAssembly = true;
}


//-----------------------------------------------------------------------------
DwHeaders& KMMessage::headers() const
{
  return mMsg->Headers();
}


//-----------------------------------------------------------------------------
void KMMessage::setNeedsAssembly()
{
  mNeedsAssembly = true;
}

//-----------------------------------------------------------------------------
void KMMessage::assembleIfNeeded()
{
  Q_ASSERT( mMsg );

  if ( mNeedsAssembly ) {
    mMsg->Assemble();
    mNeedsAssembly = false;
  }
}

//-----------------------------------------------------------------------------
TQCString KMMessage::body() const
{
  const DwString& body = mMsg->Body().AsString();
  TQCString str = KMail::Util::CString( body );
  // Calls length() -> slow
  //kdWarning( str.length() != body.length(), 5006 )
  //  << "KMMessage::body(): body is binary but used as text!" << endl;
  return str;
}


//-----------------------------------------------------------------------------
TQByteArray KMMessage::bodyDecodedBinary() const
{
  DwString dwstr;
  const DwString& dwsrc = mMsg->Body().AsString();

  switch (cte())
  {
  case DwMime::kCteBase64:
    DwDecodeBase64(dwsrc, dwstr);
    break;
  case DwMime::kCteQuotedPrintable:
    DwDecodeQuotedPrintable(dwsrc, dwstr);
    break;
  default:
    dwstr = dwsrc;
    break;
  }

  int len = dwstr.size();
  TQByteArray ba(len);
  memcpy(ba.data(),dwstr.data(),len);
  return ba;
}


//-----------------------------------------------------------------------------
TQCString KMMessage::bodyDecoded() const
{
  DwString dwstr;
  DwString dwsrc = mMsg->Body().AsString();

  switch (cte())
  {
  case DwMime::kCteBase64:
    DwDecodeBase64(dwsrc, dwstr);
    break;
  case DwMime::kCteQuotedPrintable:
    DwDecodeQuotedPrintable(dwsrc, dwstr);
    break;
  default:
    dwstr = dwsrc;
    break;
  }

  return KMail::Util::CString( dwstr );

  // Calling TQCString::length() is slow
  //TQCString result = KMail::Util::CString( dwstr );
  //kdWarning(result.length() != len, 5006)
  //  << "KMMessage::bodyDecoded(): body is binary but used as text!" << endl;
  //return result;
}


//-----------------------------------------------------------------------------
TQValueList<int> KMMessage::determineAllowedCtes( const CharFreq& cf,
                                                 bool allow8Bit,
                                                 bool willBeSigned )
{
  TQValueList<int> allowedCtes;

  switch ( cf.type() ) {
  case CharFreq::SevenBitText:
    allowedCtes << DwMime::kCte7bit;
  case CharFreq::EightBitText:
    if ( allow8Bit )
      allowedCtes << DwMime::kCte8bit;
  case CharFreq::SevenBitData:
    if ( cf.printableRatio() > 5.0/6.0 ) {
      // let n the length of data and p the number of printable chars.
      // Then base64 \approx 4n/3; qp \approx p + 3(n-p)
      // => qp < base64 iff p > 5n/6.
      allowedCtes << DwMime::kCteQp;
      allowedCtes << DwMime::kCteBase64;
    } else {
      allowedCtes << DwMime::kCteBase64;
      allowedCtes << DwMime::kCteQp;
    }
    break;
  case CharFreq::EightBitData:
    allowedCtes << DwMime::kCteBase64;
    break;
  case CharFreq::None:
  default:
    // just nothing (avoid compiler warning)
    ;
  }

  // In the following cases only QP and Base64 are allowed:
  // - the buffer will be OpenPGP/MIME signed and it contains trailing
  //   whitespace (cf. RFC 3156)
  // - a line starts with "From "
  if ( ( willBeSigned && cf.hasTrailingWhitespace() ) ||
       cf.hasLeadingFrom() ) {
    allowedCtes.remove( DwMime::kCte8bit );
    allowedCtes.remove( DwMime::kCte7bit );
  }

  return allowedCtes;
}


//-----------------------------------------------------------------------------
void KMMessage::setBodyAndGuessCte( const TQByteArray& aBuf,
                                    TQValueList<int> & allowedCte,
                                    bool allow8Bit,
                                    bool willBeSigned,
                                    DwEntity *entity )
{
  if ( !entity )
    entity = mMsg;

  CharFreq cf( aBuf ); // it's safe to pass null arrays
  allowedCte = determineAllowedCtes( cf, allow8Bit, willBeSigned );
  setCte( allowedCte[0], entity ); // choose best fitting
  setBodyEncodedBinary( aBuf, entity );
}


//-----------------------------------------------------------------------------
void KMMessage::setBodyAndGuessCte( const TQCString& aBuf,
                                    TQValueList<int> & allowedCte,
                                    bool allow8Bit,
                                    bool willBeSigned,
                                    DwEntity *entity )
{
  if ( !entity )
    entity = mMsg;

  CharFreq cf( aBuf.data(), aBuf.size()-1 ); // it's safe to pass null strings
  allowedCte = determineAllowedCtes( cf, allow8Bit, willBeSigned );
  setCte( allowedCte[0], entity ); // choose best fitting
  setBodyEncoded( aBuf, entity );
}


//-----------------------------------------------------------------------------
void KMMessage::setBodyEncoded(const TQCString& aStr, DwEntity *entity )
{
  if ( !entity )
    entity = mMsg;

  DwString dwSrc(aStr.data(), aStr.size()-1 /* not the trailing NUL */);
  DwString dwResult;

  switch (cte( entity ))
  {
  case DwMime::kCteBase64:
    DwEncodeBase64(dwSrc, dwResult);
    break;
  case DwMime::kCteQuotedPrintable:
    DwEncodeQuotedPrintable(dwSrc, dwResult);
    break;
  default:
    dwResult = dwSrc;
    break;
  }

  entity->Body().FromString(dwResult);
  mNeedsAssembly = true;
}

//-----------------------------------------------------------------------------
void KMMessage::setBodyEncodedBinary( const TQByteArray& aStr, DwEntity *entity )
{
  if ( !entity )
    entity = mMsg;

  DwString dwSrc(aStr.data(), aStr.size());
  DwString dwResult;

  switch ( cte( entity ) )
  {
  case DwMime::kCteBase64:
    DwEncodeBase64( dwSrc, dwResult );
    break;
  case DwMime::kCteQuotedPrintable:
    DwEncodeQuotedPrintable( dwSrc, dwResult );
    break;
  default:
    dwResult = dwSrc;
    break;
  }

  entity->Body().FromString( dwResult );
  entity->Body().Parse();

  mNeedsAssembly = true;
}


//-----------------------------------------------------------------------------
void KMMessage::setBody(const TQCString& aStr)
{
  mMsg->Body().FromString(KMail::Util::dwString(aStr));
  mNeedsAssembly = true;
}
void KMMessage::setBody(const DwString& aStr)
{
  mMsg->Body().FromString(aStr);
  mNeedsAssembly = true;
}
void KMMessage::setBody(const char* aStr)
{
  mMsg->Body().FromString(aStr);
  mNeedsAssembly = true;
}

//-----------------------------------------------------------------------------
void KMMessage::setMultiPartBody( const TQCString & aStr ) {
  setBody( aStr );
  mMsg->Body().Parse();
  mNeedsAssembly = true;
}


// Patched by Daniel Moisset <dmoisset@grulic.org.ar>
// modified numbodyparts, bodypart to take nested body parts as
// a linear sequence.
// third revision, Sep 26 2000

// this is support structure for traversing tree without recursion

//-----------------------------------------------------------------------------
int KMMessage::numBodyParts() const
{
  int count = 0;
  DwBodyPart* part = getFirstDwBodyPart();
  TQPtrList< DwBodyPart > parts;

  while (part)
  {
    //dive into multipart messages
    while (    part
            && part->hasHeaders()
            && part->Headers().HasContentType()
            && part->Body().FirstBodyPart()
            && (DwMime::kTypeMultipart == part->Headers().ContentType().Type()) )
    {
      parts.append( part );
      part = part->Body().FirstBodyPart();
    }
    // this is where currPart->msgPart contains a leaf message part
    count++;
    // go up in the tree until reaching a node with next
    // (or the last top-level node)
    while (part && !(part->Next()) && !(parts.isEmpty()))
    {
      part = parts.getLast();
      parts.removeLast();
    }

    if (part && part->Body().Message() &&
        part->Body().Message()->Body().FirstBodyPart())
    {
      part = part->Body().Message()->Body().FirstBodyPart();
    } else if (part) {
      part = part->Next();
    }
  }

  return count;
}


//-----------------------------------------------------------------------------
DwBodyPart * KMMessage::getFirstDwBodyPart() const
{
  return mMsg->Body().FirstBodyPart();
}


//-----------------------------------------------------------------------------
int KMMessage::partNumber( DwBodyPart * aDwBodyPart ) const
{
  DwBodyPart *curpart;
  TQPtrList< DwBodyPart > parts;
  int curIdx = 0;
  int idx = 0;
  // Get the DwBodyPart for this index

  curpart = getFirstDwBodyPart();

  while (curpart && !idx) {
    //dive into multipart messages
    while(    curpart
           && curpart->hasHeaders()
           && curpart->Headers().HasContentType()
           && curpart->Body().FirstBodyPart()
           && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) )
    {
      parts.append( curpart );
      curpart = curpart->Body().FirstBodyPart();
    }
    // this is where currPart->msgPart contains a leaf message part
    if (curpart == aDwBodyPart)
      idx = curIdx;
    curIdx++;
    // go up in the tree until reaching a node with next
    // (or the last top-level node)
    while (curpart && !(curpart->Next()) && !(parts.isEmpty()))
    {
      curpart = parts.getLast();
      parts.removeLast();
    } ;
    if (curpart)
      curpart = curpart->Next();
  }
  return idx;
}


//-----------------------------------------------------------------------------
DwBodyPart * KMMessage::dwBodyPart( int aIdx ) const
{
  DwBodyPart *part, *curpart;
  TQPtrList< DwBodyPart > parts;
  int curIdx = 0;
  // Get the DwBodyPart for this index

  curpart = getFirstDwBodyPart();
  part = 0;

  while (curpart && !part) {
    //dive into multipart messages
    while(    curpart
           && curpart->hasHeaders()
           && curpart->Headers().HasContentType()
           && curpart->Body().FirstBodyPart()
           && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) )
    {
      parts.append( curpart );
      curpart = curpart->Body().FirstBodyPart();
    }
    // this is where currPart->msgPart contains a leaf message part
    if (curIdx==aIdx)
        part = curpart;
    curIdx++;
    // go up in the tree until reaching a node with next
    // (or the last top-level node)
    while (curpart && !(curpart->Next()) && !(parts.isEmpty()))
    {
      curpart = parts.getLast();
      parts.removeLast();
    }
    if (curpart)
      curpart = curpart->Next();
  }
  return part;
}


//-----------------------------------------------------------------------------
DwBodyPart * KMMessage::findDwBodyPart( int type, int subtype ) const
{
  DwBodyPart *part, *curpart;
  TQPtrList< DwBodyPart > parts;
  // Get the DwBodyPart for this index

  curpart = getFirstDwBodyPart();
  part = 0;

  while (curpart && !part) {
    //dive into multipart messages
    while(curpart
	  && curpart->hasHeaders()
	  && curpart->Headers().HasContentType()
      && curpart->Body().FirstBodyPart()
	  && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) {
	parts.append( curpart );
	curpart = curpart->Body().FirstBodyPart();
    }
    // this is where curPart->msgPart contains a leaf message part

    // pending(khz): Find out WHY this look does not travel down *into* an
    //               embedded "Message/RfF822" message containing a "Multipart/Mixed"
    if ( curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() ) {
      kdDebug(5006) << curpart->Headers().ContentType().TypeStr().c_str()
		<< "  " << curpart->Headers().ContentType().SubtypeStr().c_str() << endl;
    }

    if (curpart &&
	curpart->hasHeaders() &&
        curpart->Headers().HasContentType() &&
	curpart->Headers().ContentType().Type() == type &&
	curpart->Headers().ContentType().Subtype() == subtype) {
	part = curpart;
    } else {
      // go up in the tree until reaching a node with next
      // (or the last top-level node)
      while (curpart && !(curpart->Next()) && !(parts.isEmpty())) {
	curpart = parts.getLast();
	parts.removeLast();
      } ;
      if (curpart)
	curpart = curpart->Next();
    }
  }
  return part;
}

//-----------------------------------------------------------------------------
DwBodyPart * KMMessage::findDwBodyPart( const TQCString& type, const TQCString&  subtype ) const
{
  DwBodyPart *part, *curpart;
  TQPtrList< DwBodyPart > parts;
  // Get the DwBodyPart for this index

  curpart = getFirstDwBodyPart();
  part = 0;

  while (curpart && !part) {
    //dive into multipart messages
    while(curpart
	  && curpart->hasHeaders()
	  && curpart->Headers().HasContentType()
      && curpart->Body().FirstBodyPart()
	  && (DwMime::kTypeMultipart == curpart->Headers().ContentType().Type()) ) {
	parts.append( curpart );
	curpart = curpart->Body().FirstBodyPart();
    }
    // this is where curPart->msgPart contains a leaf message part

    // pending(khz): Find out WHY this look does not travel down *into* an
    //               embedded "Message/RfF822" message containing a "Multipart/Mixed"
    if (curpart && curpart->hasHeaders() && curpart->Headers().HasContentType() ) {
      kdDebug(5006) << curpart->Headers().ContentType().TypeStr().c_str()
            << "  " << curpart->Headers().ContentType().SubtypeStr().c_str() << endl;
    }

    if (curpart &&
	curpart->hasHeaders() &&
        curpart->Headers().HasContentType() &&
	curpart->Headers().ContentType().TypeStr().c_str() == type &&
	curpart->Headers().ContentType().SubtypeStr().c_str() == subtype) {
	part = curpart;
    } else {
      // go up in the tree until reaching a node with next
      // (or the last top-level node)
      while (curpart && !(curpart->Next()) && !(parts.isEmpty())) {
	curpart = parts.getLast();
	parts.removeLast();
      } ;
      if (curpart)
	curpart = curpart->Next();
    }
  }
  return part;
}


void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart )
{
  // TODO: Instead of manually implementing RFC2231 header encoding (i.e.
  //       possibly multiple values given as paramname*0=..; parmaname*1=..;...
  //       or par as paramname*0*=..; parmaname*1*=..;..., which should be
  //       concatenated), use a generic method to decode the header, using RFC
  //       2047 or 2231, or whatever future RFC might be appropriate!
  //       Right now, some fields are decoded, while others are not. E.g.
  //       Content-Disposition is not decoded here, rather only on demand in
  //       KMMsgPart::fileName; Name however is decoded here and stored as a
  //       decoded String in KMMsgPart...
  // Content-type
  TQCString additionalCTypeParams;
  if (headers.HasContentType())
  {
    DwMediaType& ct = headers.ContentType();
    aPart->setOriginalContentTypeStr( ct.AsString().c_str() );
    aPart->setTypeStr(ct.TypeStr().c_str());
    aPart->setSubtypeStr(ct.SubtypeStr().c_str());
    DwParameter *param = ct.FirstParameter();
    while(param)
    {
      if (!tqstricmp(param->Attribute().c_str(), "charset")) {
        if (aPart->type() == DwMime::kTypeText) {
          aPart->setCharset(TQCString(param->Value().c_str()).lower());
        }
      }
      else if (!tqstrnicmp(param->Attribute().c_str(), "name*", 5))
        aPart->setName(KMMsgBase::decodeRFC2231String(KMMsgBase::extractRFC2231HeaderField( param->Value().c_str(), "name" )));
      else {
        additionalCTypeParams += ';';
        additionalCTypeParams += param->AsString().c_str();
      }
      param=param->Next();
    }
  }
  else
  {
    aPart->setTypeStr("text");      // Set to defaults
    aPart->setSubtypeStr("plain");
  }
  aPart->setAdditionalCTypeParamStr( additionalCTypeParams );
  // Modification by Markus
  if (aPart->name().isEmpty())
  {
    if (headers.HasContentType() && !headers.ContentType().Name().empty()) {
      aPart->setName(KMMsgBase::decodeRFC2047String(headers.
            ContentType().Name().c_str()) );
    } else if (headers.HasSubject() && !headers.Subject().AsString().empty()) {
      aPart->setName( KMMsgBase::decodeRFC2047String(headers.
            Subject().AsString().c_str()) );
    }
  }

  // Content-transfer-encoding
  if (headers.HasContentTransferEncoding())
    aPart->setCteStr(headers.ContentTransferEncoding().AsString().c_str());
  else
    aPart->setCteStr("7bit");

  // Content-description
  if (headers.HasContentDescription())
    aPart->setContentDescription( KMMsgBase::decodeRFC2047String(
                                    headers.ContentDescription().AsString().c_str() ) );
  else
    aPart->setContentDescription("");

  // Content-disposition
  if (headers.HasContentDisposition())
    aPart->setContentDisposition(headers.ContentDisposition().AsString().c_str());
  else
    aPart->setContentDisposition("");
}

//-----------------------------------------------------------------------------
void KMMessage::bodyPart(DwBodyPart* aDwBodyPart, KMMessagePart* aPart,
			 bool withBody)
{
  if ( !aPart )
    return;

  aPart->clear();

  if( aDwBodyPart && aDwBodyPart->hasHeaders()  ) {
    // This must not be an empty string, because we'll get a
    // spurious empty Subject: line in some of the parts.
    //aPart->setName(" ");
    // partSpecifier
    TQString partId( aDwBodyPart->partId() );
    aPart->setPartSpecifier( partId );

    DwHeaders& headers = aDwBodyPart->Headers();
    applyHeadersToMessagePart( headers, aPart );

    // Body
    if (withBody)
      aPart->setBody( aDwBodyPart->Body().AsString() );
    else
      aPart->setBody( TQCString("") );

    // Content-id
    if ( headers.HasContentId() ) {
      const TQCString contentId = headers.ContentId().AsString().c_str();
      // ignore leading '<' and trailing '>'
      aPart->setContentId( contentId.mid( 1, contentId.length() - 2 ) );
    }
  }
  // If no valid body part was given,
  // set all MultipartBodyPart attributes to empty values.
  else
  {
    aPart->setTypeStr("");
    aPart->setSubtypeStr("");
    aPart->setCteStr("");
    // This must not be an empty string, because we'll get a
    // spurious empty Subject: line in some of the parts.
    //aPart->setName(" ");
    aPart->setContentDescription("");
    aPart->setContentDisposition("");
    aPart->setBody(TQCString(""));
    aPart->setContentId("");
  }
}


//-----------------------------------------------------------------------------
void KMMessage::bodyPart(int aIdx, KMMessagePart* aPart) const
{
  if ( !aPart )
    return;

  // If the DwBodyPart was found get the header fields and body
  if ( DwBodyPart *part = dwBodyPart( aIdx ) ) {
    KMMessage::bodyPart(part, aPart);
    if( aPart->name().isEmpty() )
      aPart->setName( i18n("Attachment: %1").arg( aIdx ) );
  }
}


//-----------------------------------------------------------------------------
void KMMessage::deleteBodyParts()
{
  mMsg->Body().DeleteBodyParts();
}

//-----------------------------------------------------------------------------

bool KMMessage::deleteBodyPart( int partIndex )
{
  KMMessagePart part;
  DwBodyPart *dwpart = findPart( partIndex );
  if ( !dwpart )
    return false;
  KMMessage::bodyPart( dwpart, &part, true );
  if ( !part.isComplete() )
     return false;

  DwBody *parentNode = dynamic_cast<DwBody*>( dwpart->Parent() );
  if ( !parentNode )
    return false;
  parentNode->RemoveBodyPart( dwpart );

  // add dummy part to show that a attachment has been deleted
  KMMessagePart dummyPart;
  dummyPart.duplicate( part );
  TQString comment = i18n("This attachment has been deleted.");
  if ( !part.fileName().isEmpty() )
    comment = i18n( "The attachment '%1' has been deleted." ).arg( part.fileName() );
  dummyPart.setContentDescription( comment );
  dummyPart.setBodyEncodedBinary( TQByteArray() );
  TQCString cd = dummyPart.contentDisposition();
  if ( cd.find( "inline", 0, false ) == 0 ) {
    cd.replace( 0, 10, "attachment" );
    dummyPart.setContentDisposition( cd );
  } else if ( cd.isEmpty() ) {
    dummyPart.setContentDisposition( "attachment" );
  }
  DwBodyPart* newDwPart = createDWBodyPart( &dummyPart );
  parentNode->AddBodyPart( newDwPart );
  getTopLevelPart()->Assemble();
  return true;
}

//-----------------------------------------------------------------------------
DwBodyPart* KMMessage::createDWBodyPart(const KMMessagePart* aPart)
{
  DwBodyPart* part = DwBodyPart::NewBodyPart(emptyString, 0);

  if ( !aPart )
    return part;

  TQCString charset  = aPart->charset();
  TQCString type     = aPart->typeStr();
  TQCString subtype  = aPart->subtypeStr();
  TQCString cte      = aPart->cteStr();
  TQCString contDesc = aPart->contentDescriptionEncoded();
  TQCString contDisp = aPart->contentDisposition();
  TQCString name     = KMMsgBase::encodeRFC2231StringAutoDetectCharset( aPart->name(), charset );
  bool RFC2231encoded = aPart->name() != TQString(name);
  TQCString paramAttr  = aPart->parameterAttribute();

  DwHeaders& headers = part->Headers();

  DwMediaType& ct = headers.ContentType();
  if (!type.isEmpty() && !subtype.isEmpty())
  {
    ct.SetTypeStr(type.data());
    ct.SetSubtypeStr(subtype.data());
    if (!charset.isEmpty()){
      DwParameter *param;
      param=new DwParameter;
      param->SetAttribute("charset");
      param->SetValue(charset.data());
      ct.AddParameter(param);
    }
  }

  TQCString additionalParam = aPart->additionalCTypeParamStr();
  if( !additionalParam.isEmpty() )
  {
    TQCString parAV;
    DwString parA, parV;
    int iL, i1, i2, iM;
    iL = additionalParam.length();
    i1 = 0;
    i2 = additionalParam.find(';', i1, false);
    while ( i1 < iL )
    {
      if( -1 == i2 )
	i2 = iL;
      if( i1+1 < i2 ) {
	parAV = additionalParam.mid( i1, (i2-i1) );
	iM = parAV.find('=');
	if( -1 < iM )
        {
	  parA = parAV.left( iM ).data();
	  parV = parAV.right( parAV.length() - iM - 1 ).data();
	  if( ('"' == parV.at(0)) && ('"' == parV.at(parV.length()-1)) )
          {
	    parV.erase( 0,  1);
	    parV.erase( parV.length()-1 );
	  }
	}
	else
        {
	  parA = parAV.data();
	  parV = "";
	}
	DwParameter *param;
	param = new DwParameter;
	param->SetAttribute( parA );
	param->SetValue(     parV );
	ct.AddParameter( param );
      }
      i1 = i2+1;
      i2 = additionalParam.find(';', i1, false);
    }
  }

  if ( !name.isEmpty() ) {
    if (RFC2231encoded)
    {
      DwParameter *nameParam;
      nameParam = new DwParameter;
      nameParam->SetAttribute("name*");
      nameParam->SetValue(name.data(),true);
      ct.AddParameter(nameParam);
    } else {
      ct.SetName(name.data());
    }
  }

  if (!paramAttr.isEmpty())
  {
    TQCString paramValue;
    paramValue = KMMsgBase::encodeRFC2231StringAutoDetectCharset( aPart->parameterValue(), charset );
    DwParameter *param = new DwParameter;
    if (aPart->parameterValue() != TQString(paramValue))
    {
      param->SetAttribute((paramAttr + '*').data());
      param->SetValue(paramValue.data(),true);
    } else {
      param->SetAttribute(paramAttr.data());
      param->SetValue(paramValue.data());
    }
    ct.AddParameter(param);
  }

  if (!cte.isEmpty())
    headers.Cte().FromString(cte);

  if (!contDesc.isEmpty())
    headers.ContentDescription().FromString(contDesc);

  if (!contDisp.isEmpty())
    headers.ContentDisposition().FromString(contDisp);

  const DwString bodyStr = aPart->dwBody();
  if (!bodyStr.empty())
    part->Body().FromString(bodyStr);
  else
    part->Body().FromString("");

  if (!aPart->partSpecifier().isNull())
    part->SetPartId( aPart->partSpecifier().latin1() );

  if (aPart->decodedSize() > 0)
    part->SetBodySize( aPart->decodedSize() );

  return part;
}


//-----------------------------------------------------------------------------
void KMMessage::addDwBodyPart(DwBodyPart * aDwPart)
{
  mMsg->Body().AddBodyPart( aDwPart );
  mNeedsAssembly = true;
}


//-----------------------------------------------------------------------------
void KMMessage::addBodyPart(const KMMessagePart* aPart)
{
  DwBodyPart* part = createDWBodyPart( aPart );
  addDwBodyPart( part );
}


//-----------------------------------------------------------------------------
TQString KMMessage::generateMessageId( const TQString& addr )
{
  TQDateTime datetime = TQDateTime::currentDateTime();
  TQString msgIdStr;

  msgIdStr = '<' + datetime.toString( "yyyyMMddhhmm.sszzz" );

  TQString msgIdSuffix;
  TDEConfigGroup general( KMKernel::config(), "General" );

  if( general.readBoolEntry( "useCustomMessageIdSuffix", false ) )
    msgIdSuffix = general.readEntry( "myMessageIdSuffix" );

  if( !msgIdSuffix.isEmpty() )
    msgIdStr += '@' + msgIdSuffix;
  else
    msgIdStr += '.' + KPIM::encodeIDN( addr );

  msgIdStr += '>';

  return msgIdStr;
}


//-----------------------------------------------------------------------------
TQCString KMMessage::html2source( const TQCString & src )
{
  TQCString result( 1 + 6*(src.size()-1) );  // maximal possible length

  TQCString::ConstIterator s = src.begin();
  TQCString::Iterator d = result.begin();
  while ( *s ) {
    switch ( *s ) {
    case '<': {
        *d++ = '&';
        *d++ = 'l';
        *d++ = 't';
        *d++ = ';';
        ++s;
      }
      break;
    case '\r': {
        ++s;
      }
      break;
    case '\n': {
        *d++ = '<';
        *d++ = 'b';
        *d++ = 'r';
        *d++ = '>';
        ++s;
      }
      break;
    case '>': {
        *d++ = '&';
        *d++ = 'g';
        *d++ = 't';
        *d++ = ';';
        ++s;
      }
      break;
    case '&': {
        *d++ = '&';
        *d++ = 'a';
        *d++ = 'm';
        *d++ = 'p';
        *d++ = ';';
        ++s;
      }
      break;
    case '"': {
        *d++ = '&';
        *d++ = 'q';
        *d++ = 'u';
        *d++ = 'o';
        *d++ = 't';
        *d++ = ';';
        ++s;
      }
      break;
    case '\'': {
        *d++ = '&';
	*d++ = 'a';
	*d++ = 'p';
	*d++ = 's';
	*d++ = ';';
	++s;
      }
      break;
    default:
        *d++ = *s++;
    }
  }
  result.truncate( d - result.begin() ); // adds trailing NUL
  return result;
}

//-----------------------------------------------------------------------------
TQString KMMessage::encodeMailtoUrl( const TQString& str )
{
  TQString result;
  result = TQString::fromLatin1( KMMsgBase::encodeRFC2047String( str,
                                                                "utf-8" ) );
  result = KURL::encode_string( result );
  return result;
}


//-----------------------------------------------------------------------------
TQString KMMessage::decodeMailtoUrl( const TQString& url )
{
  TQString result;
  result = KURL::decode_string( url );
  result = KMMsgBase::decodeRFC2047String( result.latin1() );
  return result;
}


//-----------------------------------------------------------------------------
TQCString KMMessage::stripEmailAddr( const TQCString& aStr )
{
  //kdDebug(5006) << "KMMessage::stripEmailAddr( " << aStr << " )" << endl;

  if ( aStr.isEmpty() )
    return TQCString();

  TQCString result;

  // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
  // The purpose is to extract a displayable string from the mailboxes.
  // Comments in the addr-spec are not handled. No error checking is done.

  TQCString name;
  TQCString comment;
  TQCString angleAddress;
  enum { TopLevel, InComment, InAngleAddress } context = TopLevel;
  bool inQuotedString = false;
  int commentLevel = 0;

  for ( const char* p = aStr.data(); *p; ++p ) {
    switch ( context ) {
    case TopLevel : {
      switch ( *p ) {
      case '"' : inQuotedString = !inQuotedString;
                 break;
      case '(' : if ( !inQuotedString ) {
                   context = InComment;
                   commentLevel = 1;
                 }
                 else
                   name += *p;
                 break;
      case '<' : if ( !inQuotedString ) {
                   context = InAngleAddress;
                 }
                 else
                   name += *p;
                 break;
      case '\\' : // quoted character
                 ++p; // skip the '\'
                 if ( *p )
                   name += *p;
                 break;
      case ',' : if ( !inQuotedString ) {
                   // next email address
                   if ( !result.isEmpty() )
                     result += ", ";
                   name = name.stripWhiteSpace();
                   comment = comment.stripWhiteSpace();
                   angleAddress = angleAddress.stripWhiteSpace();
                   /*
                   kdDebug(5006) << "Name    : \"" << name
                                 << "\"" << endl;
                   kdDebug(5006) << "Comment : \"" << comment
                                 << "\"" << endl;
                   kdDebug(5006) << "Address : \"" << angleAddress
                                 << "\"" << endl;
                   */
                   if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
                     // handle Outlook-style addresses like
                     // john.doe@invalid (John Doe)
                     result += comment;
                   }
                   else if ( !name.isEmpty() ) {
                     result += name;
                   }
                   else if ( !comment.isEmpty() ) {
                     result += comment;
                   }
                   else if ( !angleAddress.isEmpty() ) {
                     result += angleAddress;
                   }
                   name = TQCString();
                   comment = TQCString();
                   angleAddress = TQCString();
                 }
                 else
                   name += *p;
                 break;
      default :  name += *p;
      }
      break;
    }
    case InComment : {
      switch ( *p ) {
      case '(' : ++commentLevel;
                 comment += *p;
                 break;
      case ')' : --commentLevel;
                 if ( commentLevel == 0 ) {
                   context = TopLevel;
                   comment += ' '; // separate the text of several comments
                 }
                 else
                   comment += *p;
                 break;
      case '\\' : // quoted character
                 ++p; // skip the '\'
                 if ( *p )
                   comment += *p;
                 break;
      default :  comment += *p;
      }
      break;
    }
    case InAngleAddress : {
      switch ( *p ) {
      case '"' : inQuotedString = !inQuotedString;
                 angleAddress += *p;
                 break;
      case '>' : if ( !inQuotedString ) {
                   context = TopLevel;
                 }
                 else
                   angleAddress += *p;
                 break;
      case '\\' : // quoted character
                 ++p; // skip the '\'
                 if ( *p )
                   angleAddress += *p;
                 break;
      default :  angleAddress += *p;
      }
      break;
    }
    } // switch ( context )
  }
  if ( !result.isEmpty() )
    result += ", ";
  name = name.stripWhiteSpace();
  comment = comment.stripWhiteSpace();
  angleAddress = angleAddress.stripWhiteSpace();
  /*
  kdDebug(5006) << "Name    : \"" << name << "\"" << endl;
  kdDebug(5006) << "Comment : \"" << comment << "\"" << endl;
  kdDebug(5006) << "Address : \"" << angleAddress << "\"" << endl;
  */
  if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
    // handle Outlook-style addresses like
    // john.doe@invalid (John Doe)
    result += comment;
  }
  else if ( !name.isEmpty() ) {
    result += name;
  }
  else if ( !comment.isEmpty() ) {
    result += comment;
  }
  else if ( !angleAddress.isEmpty() ) {
    result += angleAddress;
  }

  //kdDebug(5006) << "KMMessage::stripEmailAddr(...) returns \"" << result
  //              << "\"" << endl;
  return result;
}

//-----------------------------------------------------------------------------
TQString KMMessage::stripEmailAddr( const TQString& aStr )
{
  //kdDebug(5006) << "KMMessage::stripEmailAddr( " << aStr << " )" << endl;

  if ( aStr.isEmpty() )
    return TQString();

  TQString result;

  // The following is a primitive parser for a mailbox-list (cf. RFC 2822).
  // The purpose is to extract a displayable string from the mailboxes.
  // Comments in the addr-spec are not handled. No error checking is done.

  TQString name;
  TQString comment;
  TQString angleAddress;
  enum { TopLevel, InComment, InAngleAddress } context = TopLevel;
  bool inQuotedString = false;
  int commentLevel = 0;

  TQChar ch;
  unsigned int strLength(aStr.length());
  for ( uint index = 0; index < strLength; ++index ) {
    ch = aStr[index];
    switch ( context ) {
    case TopLevel : {
      switch ( ch.latin1() ) {
      case '"' : inQuotedString = !inQuotedString;
                 break;
      case '(' : if ( !inQuotedString ) {
                   context = InComment;
                   commentLevel = 1;
                 }
                 else
                   name += ch;
                 break;
      case '<' : if ( !inQuotedString ) {
                   context = InAngleAddress;
                 }
                 else
                   name += ch;
                 break;
      case '\\' : // quoted character
                 ++index; // skip the '\'
                 if ( index < aStr.length() )
                   name += aStr[index];
                 break;
      case ',' : if ( !inQuotedString ) {
                   // next email address
                   if ( !result.isEmpty() )
                     result += ", ";
                   name = name.stripWhiteSpace();
                   comment = comment.stripWhiteSpace();
                   angleAddress = angleAddress.stripWhiteSpace();
                   /*
                   kdDebug(5006) << "Name    : \"" << name
                                 << "\"" << endl;
                   kdDebug(5006) << "Comment : \"" << comment
                                 << "\"" << endl;
                   kdDebug(5006) << "Address : \"" << angleAddress
                                 << "\"" << endl;
                   */
                   if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
                     // handle Outlook-style addresses like
                     // john.doe@invalid (John Doe)
                     result += comment;
                   }
                   else if ( !name.isEmpty() ) {
                     result += name;
                   }
                   else if ( !comment.isEmpty() ) {
                     result += comment;
                   }
                   else if ( !angleAddress.isEmpty() ) {
                     result += angleAddress;
                   }
                   name = TQString();
                   comment = TQString();
                   angleAddress = TQString();
                 }
                 else
                   name += ch;
                 break;
      default :  name += ch;
      }
      break;
    }
    case InComment : {
      switch ( ch.latin1() ) {
      case '(' : ++commentLevel;
                 comment += ch;
                 break;
      case ')' : --commentLevel;
                 if ( commentLevel == 0 ) {
                   context = TopLevel;
                   comment += ' '; // separate the text of several comments
                 }
                 else
                   comment += ch;
                 break;
      case '\\' : // quoted character
                 ++index; // skip the '\'
                 if ( index < aStr.length() )
                   comment += aStr[index];
                 break;
      default :  comment += ch;
      }
      break;
    }
    case InAngleAddress : {
      switch ( ch.latin1() ) {
      case '"' : inQuotedString = !inQuotedString;
                 angleAddress += ch;
                 break;
      case '>' : if ( !inQuotedString ) {
                   context = TopLevel;
                 }
                 else
                   angleAddress += ch;
                 break;
      case '\\' : // quoted character
                 ++index; // skip the '\'
                 if ( index < aStr.length() )
                   angleAddress += aStr[index];
                 break;
      default :  angleAddress += ch;
      }
      break;
    }
    } // switch ( context )
  }
  if ( !result.isEmpty() )
    result += ", ";
  name = name.stripWhiteSpace();
  comment = comment.stripWhiteSpace();
  angleAddress = angleAddress.stripWhiteSpace();
  /*
  kdDebug(5006) << "Name    : \"" << name << "\"" << endl;
  kdDebug(5006) << "Comment : \"" << comment << "\"" << endl;
  kdDebug(5006) << "Address : \"" << angleAddress << "\"" << endl;
  */
  if ( angleAddress.isEmpty() && !comment.isEmpty() ) {
    // handle Outlook-style addresses like
    // john.doe@invalid (John Doe)
    result += comment;
  }
  else if ( !name.isEmpty() ) {
    result += name;
  }
  else if ( !comment.isEmpty() ) {
    result += comment;
  }
  else if ( !angleAddress.isEmpty() ) {
    result += angleAddress;
  }

  //kdDebug(5006) << "KMMessage::stripEmailAddr(...) returns \"" << result
  //              << "\"" << endl;
  return result;
}

//-----------------------------------------------------------------------------
TQString KMMessage::quoteHtmlChars( const TQString& str, bool removeLineBreaks )
{
  TQString result;

  unsigned int strLength(str.length());
  result.reserve( 6*strLength ); // maximal possible length
  for( unsigned int i = 0; i < strLength; ++i )
    switch ( str[i].latin1() ) {
    case '<':
      result += "&lt;";
      break;
    case '>':
      result += "&gt;";
      break;
    case '&':
      result += "&amp;";
      break;
    case '"':
      result += "&quot;";
      break;
    case '\n':
      if ( !removeLineBreaks )
	result += "<br>";
      break;
    case '\r':
      // ignore CR
      break;
    default:
      result += str[i];
    }

  result.squeeze();
  return result;
}

//-----------------------------------------------------------------------------
TQString KMMessage::emailAddrAsAnchor(const TQString& aEmail, bool stripped, const TQString& cssStyle, bool aLink)
{
  if( aEmail.isEmpty() )
    return aEmail;

  TQStringList addressList = KPIM::splitEmailAddrList( aEmail );
  TQString result;

  for( TQStringList::ConstIterator it = addressList.begin();
       ( it != addressList.end() );
       ++it ) {
    if( !(*it).isEmpty() ) {

      // Extract the name, mail and some pretty versions out of the mail address
      TQString name, mail;
      KPIM::getNameAndMail( *it, name, mail );
      TQString pretty;
      TQString prettyStripped;
      if ( name.stripWhiteSpace().isEmpty() ) {
        pretty = mail;
        prettyStripped = mail;
      } else {
        pretty = KPIM::quoteNameIfNecessary( name ) + " <" + mail + ">";
        prettyStripped = name;
      }

      if(aLink) {
        result += "<a href=\"mailto:"
               + KMMessage::encodeMailtoUrl( pretty )
               + "\" "+cssStyle+">";
      }

      if ( stripped ) {
        result += KMMessage::quoteHtmlChars( prettyStripped, true );
      }
      else {
        result += KMMessage::quoteHtmlChars( pretty, true );
      }

      if(aLink)
        result += "</a>, ";
    }
  }
  // cut of the trailing ", "
  if(aLink)
    result.truncate( result.length() - 2 );

  //kdDebug(5006) << "KMMessage::emailAddrAsAnchor('" << aEmail
  //              << "') returns:\n-->" << result << "<--" << endl;
  return result;
}

//-----------------------------------------------------------------------------
//static
TQStringList KMMessage::stripAddressFromAddressList( const TQString& address,
                                                    const TQStringList& list )
{
  TQStringList addresses( list );
  TQString addrSpec( KPIM::getEmailAddress( address ) );
  for ( TQStringList::Iterator it = addresses.begin();
       it != addresses.end(); ) {
    if ( kasciistricmp( addrSpec.utf8().data(),
                        KPIM::getEmailAddress( *it ).utf8().data() ) == 0 ) {
      kdDebug(5006) << "Removing " << *it << " from the address list"
                    << endl;
      it = addresses.remove( it );
    }
    else
      ++it;
  }
  return addresses;
}


//-----------------------------------------------------------------------------
//static
TQStringList KMMessage::stripMyAddressesFromAddressList( const TQStringList& list )
{
  TQStringList addresses = list;
  for( TQStringList::Iterator it = addresses.begin();
       it != addresses.end(); ) {
    kdDebug(5006) << "Check whether " << *it << " is one of my addresses"
                  << endl;
    if( kmkernel->identityManager()->thatIsMe( KPIM::getEmailAddress( *it ) ) ) {
      kdDebug(5006) << "Removing " << *it << " from the address list"
                    << endl;
      it = addresses.remove( it );
    }
    else
      ++it;
  }
  return addresses;
}


//-----------------------------------------------------------------------------
//static
bool KMMessage::addressIsInAddressList( const TQString& address,
                                        const TQStringList& addresses )
{
  TQString addrSpec = KPIM::getEmailAddress( address );
  for( TQStringList::ConstIterator it = addresses.begin();
       it != addresses.end(); ++it ) {
    if ( kasciistricmp( addrSpec.utf8().data(),
                        KPIM::getEmailAddress( *it ).utf8().data() ) == 0 )
      return true;
  }
  return false;
}


//-----------------------------------------------------------------------------
//static
TQString KMMessage::expandAliases( const TQString& recipients )
{
  if ( recipients.isEmpty() )
    return TQString();

  TQStringList recipientList = KPIM::splitEmailAddrList( recipients );

  TQString expandedRecipients;
  for ( TQStringList::Iterator it = recipientList.begin();
        it != recipientList.end(); ++it ) {
    if ( !expandedRecipients.isEmpty() )
      expandedRecipients += ", ";
    TQString receiver = (*it).stripWhiteSpace();

    // try to expand distribution list
    TQString expandedList = KAddrBookExternal::expandDistributionList( receiver );
    if ( !expandedList.isEmpty() ) {
      expandedRecipients += expandedList;
      continue;
    }

    // try to expand nick name
    TQString expandedNickName = KabcBridge::expandNickName( receiver );
    if ( !expandedNickName.isEmpty() ) {
      expandedRecipients += expandedNickName;
      continue;
    }

    // check whether the address is missing the domain part
    // FIXME: looking for '@' might be wrong
    if ( receiver.find('@') == -1 ) {
      TDEConfigGroup general( KMKernel::config(), "General" );
      TQString defaultdomain = general.readEntry( "Default domain" );
      if( !defaultdomain.isEmpty() ) {
        expandedRecipients += receiver + "@" + defaultdomain;
      }
      else {
        expandedRecipients += guessEmailAddressFromLoginName( receiver );
      }
    }
    else
      expandedRecipients += receiver;
  }

  return expandedRecipients;
}


//-----------------------------------------------------------------------------
//static
TQString KMMessage::guessEmailAddressFromLoginName( const TQString& loginName )
{
  if ( loginName.isEmpty() )
    return TQString();

  char hostnameC[256];
  // null terminate this C string
  hostnameC[255] = '\0';
  // set the string to 0 length if gethostname fails
  if ( gethostname( hostnameC, 255 ) )
    hostnameC[0] = '\0';
  TQString address = loginName;
  address += '@';
  address += TQString::fromLocal8Bit( hostnameC );

  // try to determine the real name
  const KUser user( loginName );
  if ( user.isValid() ) {
    TQString fullName = user.fullName();
    if ( fullName.find( TQRegExp( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ) ) != -1 )
      address = '"' + fullName.replace( '\\', "\\" ).replace( '"', "\\" )
	      + "\" <" + address + '>';
    else
      address = fullName + " <" + address + '>';
  }

  return address;
}

//-----------------------------------------------------------------------------
void KMMessage::readConfig()
{
  KMMsgBase::readConfig();

  TDEConfig *config=KMKernel::config();
  TDEConfigGroupSaver saver(config, "General");

  config->setGroup("General");

  int languageNr = config->readNumEntry("reply-current-language",0);

  { // area for config group "KMMessage #n"
    TDEConfigGroupSaver saver(config, TQString("KMMessage #%1").arg(languageNr));
    sReplyLanguage = config->readEntry("language",TDEGlobal::locale()->language());
    sReplyStr = config->readEntry("phrase-reply",
      i18n("On %D, you wrote:"));
    sReplyAllStr = config->readEntry("phrase-reply-all",
      i18n("On %D, %F wrote:"));
    sForwardStr = config->readEntry("phrase-forward",
      i18n("Forwarded Message"));
    sIndentPrefixStr = config->readEntry("indent-prefix",">%_");
  }

  { // area for config group "Composer"
    TDEConfigGroupSaver saver(config, "Composer");
    sSmartQuote = GlobalSettings::self()->smartQuote();
    sWordWrap = GlobalSettings::self()->wordWrap();
    sWrapCol = GlobalSettings::self()->lineWrapWidth();
    if ((sWrapCol == 0) || (sWrapCol > 78))
      sWrapCol = 78;
    if (sWrapCol < 30)
      sWrapCol = 30;

    sPrefCharsets = config->readListEntry("pref-charsets");
  }

  { // area for config group "Reader"
    TDEConfigGroupSaver saver(config, "Reader");
    sHeaderStrategy = HeaderStrategy::create( config->readEntry( "header-set-displayed", "rich" ) );
  }
}

TQCString KMMessage::defaultCharset()
{
  TQCString retval;

  if (!sPrefCharsets.isEmpty())
    retval = sPrefCharsets[0].latin1();

  if (retval.isEmpty()  || (retval == "locale")) {
    retval = TQCString(kmkernel->networkCodec()->mimeName());
    kasciitolower( retval.data() );
  }

  if (retval == "jisx0208.1983-0") retval = "iso-2022-jp";
  else if (retval == "ksc5601.1987-0") retval = "euc-kr";
  return retval;
}

const TQStringList &KMMessage::preferredCharsets()
{
  return sPrefCharsets;
}

//-----------------------------------------------------------------------------
TQCString KMMessage::charset() const
{
  if ( mMsg->Headers().HasContentType() ) {
    DwMediaType &mType=mMsg->Headers().ContentType();
    mType.Parse();
    DwParameter *param=mType.FirstParameter();
    while(param){
      if (!kasciistricmp(param->Attribute().c_str(), "charset"))
        return param->Value().c_str();
      else param=param->Next();
    }
  }
  return ""; // us-ascii, but we don't have to specify it
}

//-----------------------------------------------------------------------------
void KMMessage::setCharset( const TQCString &charset, DwEntity *entity )
{
  kdWarning( type() != DwMime::kTypeText )
    << "KMMessage::setCharset(): trying to set a charset for a non-textual mimetype." << endl
    << "Fix this caller:" << endl
    << "====================================================================" << endl
    << kdBacktrace( 5 ) << endl
    << "====================================================================" << endl;

  if ( !entity )
    entity = mMsg;

  DwMediaType &mType = entity->Headers().ContentType();
  mType.Parse();
  DwParameter *param = mType.FirstParameter();
  while( param ) {

    // FIXME use the mimelib functions here for comparison.
    if ( !kasciistricmp( param->Attribute().c_str(), "charset" ) )
      break;

    param = param->Next();
  }
  if ( !param ) {
    param = new DwParameter;
    param->SetAttribute( "charset" );
    mType.AddParameter( param );
  }
  else
    mType.SetModified();

  TQCString lowerCharset = charset;
  kasciitolower( lowerCharset.data() );
  param->SetValue( DwString( lowerCharset ) );
  mType.Assemble();
}


//-----------------------------------------------------------------------------
void KMMessage::setStatus(const KMMsgStatus aStatus, int idx)
{
  if (mStatus == aStatus)
    return;
  KMMsgBase::setStatus(aStatus, idx);
}

void KMMessage::setEncryptionState(const KMMsgEncryptionState s, int idx)
{
    if( mEncryptionState == s )
        return;
    mEncryptionState = s;
    mDirty = true;
    KMMsgBase::setEncryptionState(s, idx);
}

void KMMessage::setSignatureState(KMMsgSignatureState s, int idx)
{
    if( mSignatureState == s )
        return;
    mSignatureState = s;
    mDirty = true;
    KMMsgBase::setSignatureState(s, idx);
}

void KMMessage::setMDNSentState( KMMsgMDNSentState status, int idx )
{
  if ( mMDNSentState == status )
    return;
  if ( status == 0 )
    status = KMMsgMDNStateUnknown;
  mMDNSentState = status;
  mDirty = true;
  KMMsgBase::setMDNSentState( status, idx );
}

//-----------------------------------------------------------------------------
void KMMessage::link( const KMMessage *aMsg, KMMsgStatus aStatus )
{
  Q_ASSERT( aStatus == KMMsgStatusReplied
      || aStatus == KMMsgStatusForwarded
      || aStatus == KMMsgStatusDeleted );

  TQString message = headerField( "X-KMail-Link-Message" );
  if ( !message.isEmpty() )
    message += ',';
  TQString type = headerField( "X-KMail-Link-Type" );
  if ( !type.isEmpty() )
    type += ',';

  message += TQString::number( aMsg->getMsgSerNum() );
  if ( aStatus == KMMsgStatusReplied )
    type += "reply";
  else if ( aStatus == KMMsgStatusForwarded )
    type += "forward";
  else if ( aStatus == KMMsgStatusDeleted )
    type += "deleted";

  setHeaderField( "X-KMail-Link-Message", message );
  setHeaderField( "X-KMail-Link-Type", type );
}

//-----------------------------------------------------------------------------
void KMMessage::getLink(int n, ulong *retMsgSerNum, KMMsgStatus *reStatus) const
{
  *retMsgSerNum = 0;
  *reStatus = KMMsgStatusUnknown;

  TQString message = headerField("X-KMail-Link-Message");
  TQString type = headerField("X-KMail-Link-Type");
  message = message.section(',', n, n);
  type = type.section(',', n, n);

  if ( !message.isEmpty() && !type.isEmpty() ) {
    *retMsgSerNum = message.toULong();
    if ( type == "reply" )
      *reStatus = KMMsgStatusReplied;
    else if ( type == "forward" )
      *reStatus = KMMsgStatusForwarded;
    else if ( type == "deleted" )
      *reStatus = KMMsgStatusDeleted;
  }
}

//-----------------------------------------------------------------------------
DwBodyPart* KMMessage::findDwBodyPart( DwBodyPart* part, const TQString & partSpecifier )
{
  if ( !part ) return 0;
  DwBodyPart* current;

  if ( part->partId() == partSpecifier )
    return part;

  // multipart
  if ( part->hasHeaders() &&
       part->Headers().HasContentType() &&
       part->Body().FirstBodyPart() &&
       (DwMime::kTypeMultipart == part->Headers().ContentType().Type() ) &&
       (current = findDwBodyPart( part->Body().FirstBodyPart(), partSpecifier )) )
  {
    return current;
  }

  // encapsulated message
  if ( part->Body().Message() &&
       part->Body().Message()->Body().FirstBodyPart() &&
       (current = findDwBodyPart( part->Body().Message()->Body().FirstBodyPart(),
                                  partSpecifier )) )
  {
    return current;
  }

  // next part
  return findDwBodyPart( part->Next(), partSpecifier );
}

//-----------------------------------------------------------------------------
void KMMessage::updateBodyPart(const TQString partSpecifier, const TQByteArray & data)
{
  if ( !data.data() || !data.size() )
    return;

  DwString content( data.data(), data.size() );
  if ( numBodyParts() > 0 &&
       partSpecifier != "0" &&
       partSpecifier != "TEXT" )
  {
    TQString specifier = partSpecifier;
    if ( partSpecifier.endsWith(".HEADER") ||
         partSpecifier.endsWith(".MIME") ) {
      // get the parent bodypart
      specifier = partSpecifier.section( '.', 0, -2 );
    }

    // search for the bodypart
    mLastUpdated = findDwBodyPart( getFirstDwBodyPart(), specifier );
    kdDebug(5006) << "KMMessage::updateBodyPart " << specifier << endl;
    if (!mLastUpdated)
    {
      kdWarning(5006) << "KMMessage::updateBodyPart - can not find part "
        << specifier << endl;
      return;
    }
    if ( partSpecifier.endsWith(".MIME") )
    {
      // update headers
      // get rid of EOL
      content.resize( TQMAX( content.length(), 2 ) - 2 );
      // we have to delete the fields first as they might have been created by
      // an earlier call to DwHeaders::FieldBody
      mLastUpdated->Headers().DeleteAllFields();
      mLastUpdated->Headers().FromString( content );
      mLastUpdated->Headers().Parse();
    } else if ( partSpecifier.endsWith(".HEADER") )
    {
      // update header of embedded message
      mLastUpdated->Body().Message()->Headers().FromString( content );
      mLastUpdated->Body().Message()->Headers().Parse();
    } else {
      // update body
      mLastUpdated->Body().FromString( content );
      TQString parentSpec = partSpecifier.section( '.', 0, -2 );
      if ( !parentSpec.isEmpty() )
      {
        DwBodyPart* parent = findDwBodyPart( getFirstDwBodyPart(), parentSpec );
        if ( parent && parent->hasHeaders() && parent->Headers().HasContentType() )
        {
          const DwMediaType& contentType = parent->Headers().ContentType();
          if ( contentType.Type() == DwMime::kTypeMessage &&
               contentType.Subtype() == DwMime::kSubtypeRfc822 )
          {
            // an embedded message that is not multipart
            // update this directly
            parent->Body().Message()->Body().FromString( content );
          }
        }
      }
    }

  } else
  {
    // update text-only messages
    if ( partSpecifier == "TEXT" )
      deleteBodyParts(); // delete empty parts first
    mMsg->Body().FromString( content );
    mMsg->Body().Parse();
  }
  mNeedsAssembly = true;
  if (! partSpecifier.endsWith(".HEADER") )
  {
    // notify observers
    notify();
  }
}

void KMMessage::updateInvitationState()
{
  if ( mMsg && mMsg->hasHeaders() && mMsg->Headers().HasContentType() ) {
    TQString cntType = mMsg->Headers().ContentType().TypeStr().c_str();
    cntType += '/';
    cntType += mMsg->Headers().ContentType().SubtypeStr().c_str();
    if ( cntType.lower() == "text/calendar" ) {
      setStatus( KMMsgStatusHasInvitation );
      return;
    }
  }
  setStatus( KMMsgStatusHasNoInvitation );
  return;
}

//-----------------------------------------------------------------------------
void KMMessage::updateAttachmentState( DwBodyPart* part )
{
  if ( !part )
    part = getFirstDwBodyPart();

  if ( !part )
  {
    // kdDebug(5006) << "updateAttachmentState - no part!" << endl;
    setStatus( KMMsgStatusHasNoAttach );
    return;
  }

  bool filenameEmpty = true;
  if ( part->hasHeaders() ) {
    if ( part->Headers().HasContentDisposition() ) {
      DwDispositionType cd = part->Headers().ContentDisposition();
      filenameEmpty = cd.Filename().empty();
      if ( filenameEmpty ) {
        // let's try if it is rfc 2231 encoded which mimelib can't handle
        filenameEmpty = KMMsgBase::decodeRFC2231String( KMMsgBase::extractRFC2231HeaderField( cd.AsString().c_str(), "filename" ) ).isEmpty();
      }
    }

    // Filename still empty? Check if the content-type has a "name" parameter and try to use that as
    // the attachment name
    if ( filenameEmpty && part->Headers().HasContentType() ) {
      DwMediaType contentType = part->Headers().ContentType();
      filenameEmpty = contentType.Name().empty();
      if ( filenameEmpty ) {
        // let's try if it is rfc 2231 encoded which mimelib can't handle
        filenameEmpty = KMMsgBase::decodeRFC2231String( KMMsgBase::extractRFC2231HeaderField(
            contentType.AsString().c_str(), "name" ) ).isEmpty();
      }
    }
  }

  if ( part->hasHeaders() &&
       ( ( part->Headers().HasContentDisposition() &&
           !part->Headers().ContentDisposition().Filename().empty() ) ||
         ( part->Headers().HasContentType() &&
           !filenameEmpty ) ) )
  {
    // now blacklist certain ContentTypes
    if ( !part->Headers().HasContentType() ||
         ( part->Headers().HasContentType() &&
           part->Headers().ContentType().Subtype() != DwMime::kSubtypePgpSignature &&
           part->Headers().ContentType().Subtype() != DwMime::kSubtypePkcs7Signature ) )
    {
      setStatus( KMMsgStatusHasAttach );
    }
    return;
  }

  // multipart
  if ( part->hasHeaders() &&
       part->Headers().HasContentType() &&
       part->Body().FirstBodyPart() &&
       (DwMime::kTypeMultipart == part->Headers().ContentType().Type() ) )
  {
    updateAttachmentState( part->Body().FirstBodyPart() );
  }

  // encapsulated message
  if ( part->Body().Message() &&
       part->Body().Message()->Body().FirstBodyPart() )
  {
    updateAttachmentState( part->Body().Message()->Body().FirstBodyPart() );
  }

  // next part
  if ( part->Next() )
    updateAttachmentState( part->Next() );
  else if ( attachmentState() == KMMsgAttachmentUnknown )
    setStatus( KMMsgStatusHasNoAttach );
}

void KMMessage::setBodyFromUnicode( const TQString &str, DwEntity *entity )
{
  TQCString encoding = KMMsgBase::autoDetectCharset( charset(), KMMessage::preferredCharsets(), str );
  if ( encoding.isEmpty() )
    encoding = "utf-8";
  const TQTextCodec * codec = KMMsgBase::codecForName( encoding );
  assert( codec );
  TQValueList<int> dummy;
  setCharset( encoding, entity );
  setBodyAndGuessCte( codec->fromUnicode( str ), dummy, false /* no 8bit */,
                      false, entity );
}

const TQTextCodec * KMMessage::codec() const {
  const TQTextCodec * c = mOverrideCodec;
  if ( !c )
    // no override-codec set for this message, try the CT charset parameter:
    c = KMMsgBase::codecForName( charset() );
  if ( !c ) {
    // Ok, no override and nothing in the message, let's use the fallback
    // the user configured
    c = KMMsgBase::codecForName( GlobalSettings::self()->fallbackCharacterEncoding().latin1() );
  }
  if ( !c )
    // no charset means us-ascii (RFC 2045), so using local encoding should
    // be okay
    c = kmkernel->networkCodec();
  assert( c );
  return c;
}

TQString KMMessage::bodyToUnicode(const TQTextCodec* codec) const {
  if ( !codec )
    // No codec was given, so try the charset in the mail
    codec = this->codec();
  assert( codec );

  return codec->toUnicode( bodyDecoded() );
}

//-----------------------------------------------------------------------------
TQCString KMMessage::mboxMessageSeparator()
{
  TQCString str( KPIM::getFirstEmailAddress( rawHeaderField("From") ) );
  if ( str.isEmpty() )
    str = "unknown@unknown.invalid";
  TQCString dateStr( dateShortStr() );
  if ( dateStr.isEmpty() ) {
    time_t t = ::time( 0 );
    dateStr = ctime( &t );
    const int len = dateStr.length();
    if ( dateStr[len-1] == '\n' )
      dateStr.truncate( len - 1 );
  }
  return "From " + str + " " + dateStr + "\n";
}

void KMMessage::deleteWhenUnused()
{
  sPendingDeletes << this;
}

DwBodyPart* KMMessage::findPart( int index )
{
  int accu = 0;
  return findPartInternal( getTopLevelPart(), index, accu );
}

DwBodyPart* KMMessage::findPartInternal(DwEntity * root, int index, int & accu)
{
  accu++;
  if ( index < accu ) // should not happen
    return 0;
  DwBodyPart *current = dynamic_cast<DwBodyPart*>( root );
  if ( index == accu )
    return current;
  DwBodyPart *rv = 0;
  if ( root->Body().FirstBodyPart() )
    rv = findPartInternal( root->Body().FirstBodyPart(), index, accu );
  if ( !rv && current && current->Next() )
    rv = findPartInternal( current->Next(), index, accu );
  if ( !rv && root->Body().Message() )
    rv = findPartInternal( root->Body().Message(), index, accu );
  return rv;
}

