diff options
Diffstat (limited to 'kmail/kmmessage.cpp')
-rw-r--r-- | kmail/kmmessage.cpp | 4373 |
1 files changed, 4373 insertions, 0 deletions
diff --git a/kmail/kmmessage.cpp b/kmail/kmmessage.cpp new file mode 100644 index 000000000..9c006b685 --- /dev/null +++ b/kmail/kmmessage.cpp @@ -0,0 +1,4373 @@ +// -*- mode: C++; c-file-style: "gnu" -*- +// 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 <kasciistringtools.h> + +#include <kpgpblock.h> +#include <kaddrbook.h> + +#include <kapplication.h> +#include <kglobalsettings.h> +#include <kdebug.h> +#include <kconfig.h> +#include <khtml_part.h> +#include <kuser.h> +#include <kidna.h> +#include <kasciistricmp.h> + +#include <qcursor.h> +#include <qtextcodec.h> +#include <qmessagebox.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 <klocale.h> +#include <stdlib.h> +#include <unistd.h> +#include "util.h" + +#if ALLOW_GUI +#include <kmessagebox.h> +#endif + +using namespace KMime; + +static DwString emptyString(""); + +// Values that are set from the config file with KMMessage::readConfig() +static QString sReplyLanguage, sReplyStr, sReplyAllStr, sIndentPrefixStr; +static bool sSmartQuote, + sWordWrap; +static int sWrapCol; +static QStringList sPrefCharsets; + +QString KMMessage::sForwardStr; +const HeaderStrategy * KMMessage::sHeaderStrategy = HeaderStrategy::rich(); +//helper +static void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart ); + +QValueList<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 QCString& aStr) +{ + if (!aStr) return; + mMsg->Headers().References().FromString(aStr); + mNeedsAssembly = true; +} + + +//----------------------------------------------------------------------------- +QCString 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 QString& aStr, + QString& brokenAddress ) +{ + if ( aStr.isEmpty() ) { + return KPIM::AddressEmpty; + } + + QStringList list = KPIM::splitEmailAddrList( aStr ); + for( QStringList::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; +} + +//----------------------------------------------------------------------------- +QCString KMMessage::asString() const { + return KMail::Util::CString( asDwString() ); +} + + +QByteArray KMMessage::asSendableString() const +{ + KMMessage msg( new DwMessage( *this->mMsg ) ); + msg.removePrivateHeaderFields(); + msg.removeHeaderField("Bcc"); + return KMail::Util::ByteArray( msg.asDwString() ); // and another copy again! +} + +QCString 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() ); +} + + +//---------------------------------------------------------------------------- +QString KMMessage::headerAsString() const +{ + DwHeaders& header = mMsg->Headers(); + header.Assemble(); + if ( header.AsString().empty() ) + return QString::null; + return QString::fromLatin1( header.AsString().c_str() ); +} + + +//----------------------------------------------------------------------------- +DwMediaType& KMMessage::dwContentType() +{ + return mMsg->Headers().ContentType(); +} + +void KMMessage::fromByteArray( const QByteArray & ba, bool setStatus ) { + return fromDwString( DwString( ba.data(), ba.size() ), setStatus ); +} + +void KMMessage::fromString( const QCString & str, bool aSetStatus ) { + return fromDwString( KMail::Util::dwString( str ), aSetStatus ); +} + +void KMMessage::fromDwString(const DwString& str, bool aSetStatus) +{ + delete mMsg; + mMsg = new DwMessage; + mMsg->FromString( str ); + mMsg->Parse(); + + if (aSetStatus) { + 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 (attachmentState() == KMMsgAttachmentUnknown && readyToShow()) + updateAttachmentState(); + + mNeedsAssembly = false; + mDate = date(); +} + + +//----------------------------------------------------------------------------- +QString KMMessage::formatString(const QString& aStr) const +{ + QString result, str; + QChar 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( QString &line ) +{ + int i = line.length()-1; + while( (i >= 0) && ((line[i] == ' ') || (line[i] == '\t'))) + i--; + line.truncate( i+1); +} + +static QString splitLine( QString &line) +{ + removeTrailingSpace( line ); + int i = 0; + int j = -1; + int l = line.length(); + + // TODO: Replace tabs with spaces first. + + while(i < l) + { + QChar c = line[i]; + if ((c == '>') || (c == ':') || (c == '|')) + j = i+1; + else if ((c != ' ') && (c != '\t')) + break; + i++; + } + + if ( j <= 0 ) + { + return ""; + } + if ( i == l ) + { + QString result = line.left(j); + line = QString::null; + return result; + } + + QString result = line.left(j); + line = line.mid(j); + return result; +} + +static QString flowText(QString &text, const QString& indent, int maxLength) +{ + maxLength--; + if (text.isEmpty()) + { + return indent+"<NULL>\n"; + } + QString 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(); + } + + QString line = text.left(i); + if (i < (int) text.length()) + text = text.mid(i); + else + text = QString::null; + + result += indent + line + '\n'; + + if (text.isEmpty()) + return result; + } +} + +static bool flushPart(QString &msg, QStringList &part, + const QString &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()); + } + + QString text; + for(QStringList::Iterator it2 = part.begin(); + it2 != part.end(); + it2++) + { + QString 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 QString stripSignature( const QString & msg, bool clearSigned ) { + if ( clearSigned ) + return msg.left( msg.findRev( QRegExp( "\n--\\s?\n" ) ) ); + else + return msg.left( msg.findRev( "\n-- \n" ) ); +} + +QString KMMessage::smartQuote( const QString & msg, int maxLineLength ) +{ + QStringList part; + QString oldIndent; + bool firstPart = true; + + + const QStringList lines = QStringList::split('\n', msg, true); + + QString result; + for(QStringList::const_iterator it = lines.begin(); + it != lines.end(); + ++it) + { + QString line = *it; + + const QString indent = splitLine( line ); + + if ( line.isEmpty()) + { + if (!firstPart) + part.append(QString::null); + continue; + }; + + if (firstPart) + { + oldIndent = indent; + firstPart = false; + } + + if (oldIndent != indent) + { + QString fromLine; + // Search if the last non-blank line could be "From" line + if (part.count() && (oldIndent.length() < indent.length())) + { + QStringList::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, + QCString& parsedString, + const QTextCodec*& codec, + bool& isHTML ) const +{ + if ( !root ) return; + + isHTML = false; + // initialy parse the complete message to decrypt any encrypted parts + { + ObjectTreeParser otp( 0, 0, true, false, true ); + otp.parseObjectTree( root ); + } + 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(); + } +} + +//----------------------------------------------------------------------------- + +QString KMMessage::asPlainText( bool aStripSignature, bool allowDecryption ) const { + QCString parsedString; + bool isHTML = false; + const QTextCodec * codec = 0; + + partNode * root = partNode::fromMessage( this ); + if ( !root ) return QString::null; + parseTextStringFromDwPart( root, parsedString, codec, isHTML ); + delete root; + + if ( mOverrideCodec || !codec ) + codec = this->codec(); + + if ( parsedString.isEmpty() ) + return QString::null; + + bool clearSigned = false; + QString result; + + // decrypt + if ( allowDecryption ) { + QPtrList<Kpgp::Block> pgpBlocks; + QStrList 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 ) { + KHTMLPart 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; +} + +QString KMMessage::asQuotedString( const QString& aHeaderStr, + const QString& aIndentStr, + const QString& selection /* = QString::null */, + bool aStripSignature /* = true */, + bool allowDecryption /* = true */) const +{ + QString content = selection.isEmpty() ? + asPlainText( aStripSignature, allowDecryption ) : selection ; + + // Remove blank lines at the beginning: + const int firstNonWS = content.find( QRegExp( "\\S" ) ); + const int lineStart = content.findRev( '\n', firstNonWS ); + if ( lineStart >= 0 ) + content.remove( 0, static_cast<unsigned int>( lineStart ) ); + + const QString indentStr = formatString( aIndentStr ); + + content.replace( '\n', '\n' + indentStr ); + content.prepend( indentStr ); + content += '\n'; + + const QString headerStr = formatString( aHeaderStr ); + if ( sSmartQuote && sWordWrap ) + return headerStr + smartQuote( content, sWrapCol ); + return headerStr + content; +} + +//----------------------------------------------------------------------------- +KMMessage* KMMessage::createReply( KMail::ReplyStrategy replyStrategy, + QString selection /* = QString::null */, + bool noQuote /* = false */, + bool allowDecryption /* = true */, + bool selectionIsBody /* = false */, + const QString &tmpl /* = QString::null */ ) +{ + KMMessage* msg = new KMMessage; + QString str, replyStr, mailingListStr, replyToStr, toStr; + QStringList mailingListAddresses; + QCString 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 ) { + QString listPost = headerField("List-Post"); + QRegExp 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 + replyStr = sReplyAllStr; + + 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 + QStringList 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 + QStringList recipients = KPIM::splitEmailAddrList( toStr ); + toStr = stripMyAddressesFromAddressList( recipients ).join(", "); + + break; + } + case KMail::ReplyAll : { + QStringList recipients; + QStringList 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 ( QStringList::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() ) { + QStringList list; + if (!to().isEmpty()) + list += KPIM::splitEmailAddrList(to()); + if (!cc().isEmpty()) + list += KPIM::splitEmailAddrList(cc()); + for( QStringList::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() ) { + QStringList 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 ( QStringList::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(); + } + replyStr = sReplyStr; // reply to author, so use "On ... you wrote:" + replyAll = false; + break; + } + case KMail::ReplyNone : { + // the addressees will be set by the caller + } + } + + msg->setTo(toStr); + + refStr = getRefStr(); + if (!refStr.isEmpty()) + msg->setReferences(refStr); + //In-Reply-To = original msg-id + msg->setReplyToId(msgId()); + +// if (!noQuote) { +// if( selectionIsBody ){ +// QCString cStr = selection.latin1(); +// msg->setBody( cStr ); +// }else{ +// msg->setBody(asQuotedString(replyStr + "\n", sIndentPrefixStr, selection, +// sSmartQuote, allowDecryption).utf8()); +// } +// } + + msg->setSubject( replySubject() ); + + TemplateParser parser( msg, (replyAll ? TemplateParser::ReplyAll : TemplateParser::Reply), + selection, sSmartQuote, noQuote, allowDecryption, selectionIsBody ); + 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; +} + + +//----------------------------------------------------------------------------- +QCString KMMessage::getRefStr() const +{ + QCString 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 QString &toStr ) +{ + // copy the message 1:1 + KMMessage* msg = new KMMessage( new DwMessage( *this->mMsg ) ); + KMMessagePart msgPart; + + uint id = 0; + QString 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 + QString strByWayOf = QString("%1 (by way of %2 <%3>)") + .arg( from() ) + .arg( ident.fullName() ) + .arg( ident.emailAddr() ); + + // Resent-From: content + QString strFrom = QString("%1 <%2>") + .arg( ident.fullName() ) + .arg( ident.emailAddr() ); + + // format the current date to be used in Resent-Date: + QString origDate = msg->headerField( "Date" ); + msg->setDateToday(); + QString 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; +} + + +//----------------------------------------------------------------------------- +QCString KMMessage::createForwardBody() +{ + QString s; + QCString str; + + if (sHeaderStrategy == HeaderStrategy::all()) { + s = "\n\n---------- " + sForwardStr + " ----------\n\n"; + s += headerAsString(); + str = asQuotedString(s, "", QString::null, 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, "", QString::null, false, false).utf8(); + str += "\n-------------------------------------------------------\n"; + } + + return str; +} + +void KMMessage::sanitizeHeaders( const QStringList& 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( QString::fromLatin1( field->FieldNameStr().c_str() ) ) ) + header.RemoveField(field); + field = nextField; + } + mMsg->Assemble(); +} + +//----------------------------------------------------------------------------- +KMMessage* KMMessage::createForward( const QString &tmpl /* = QString::null */ ) +{ + KMMessage* msg = new KMMessage(); + QString id; + + // 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 + const int type = msg->type(); + const int subtype = msg->subtype(); + + msg->sanitizeHeaders(); + + // strip blacklisted parts + QStringList blacklist = GlobalSettings::self()->mimetypesToStripWhenInlineForwarding(); + for ( QStringList::Iterator it = blacklist.begin(); it != blacklist.end(); ++it ) { + QString entry = (*it); + int sep = entry.find( '/' ); + QCString type = entry.left( sep ).latin1(); + QCString 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->setType( type ); + msg->setSubtype( subtype ); + } 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(); + } + // QString st = QString::fromUtf8(createForwardBody()); + + msg->setSubject( forwardSubject() ); + + TemplateParser parser( msg, TemplateParser::Forward, + asPlainText( false, false ), + false, false, false, false); + if ( !tmpl.isEmpty() ) { + parser.process( tmpl, this ); + } else { + parser.process( this ); + } + + // QCString 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( QCursor::ArrowCursor ); + int answer = QMessageBox::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( QCursor::ArrowCursor ); + int answer = QMessageBox::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, + QValueList<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: + QString 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 + QString special; // fill in case of error, warning or failure + KConfigGroup 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. + QString 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"); + QString returnPath = returnPathList.isEmpty() ? QString::null + : 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: + QString 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" ); + + QString 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; +} + +QString KMMessage::replaceHeadersInString( const QString & s ) const { + QString result = s; + QRegExp rx( "\\$\\{([a-z0-9-]+)\\}", false ); + Q_ASSERT( rx.isValid() ); + + QRegExp rxDate( "\\$\\{date\\}" ); + Q_ASSERT( rxDate.isValid() ); + + QString 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 ) { + QString replacement = headerField( rx.cap(1).latin1() ); + result.replace( idx, rx.matchedLength(), replacement ); + idx += replacement.length(); + } + return result; +} + +KMMessage* KMMessage::createDeliveryReceipt() const +{ + QString 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", QString::number( ident.uoid() )); + + if ( ident.transport().isEmpty() ) + removeHeaderField( "X-KMail-Transport" ); + else + setHeaderField( "X-KMail-Transport", ident.transport() ); + + if ( ident.fcc().isEmpty() ) + setFcc( QString::null ); + else + setFcc( ident.fcc() ); + + if ( ident.drafts().isEmpty() ) + setDrafts( QString::null ); + else + setDrafts( ident.drafts() ); + + if ( ident.templates().isEmpty() ) + setTemplates( QString::null ); + 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 { + QString 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", QString::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; +} + + +//----------------------------------------------------------------------------- +QString KMMessage::dateStr() const +{ + KConfigGroup 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" )); +} + + +//----------------------------------------------------------------------------- +QCString KMMessage::dateShortStr() const +{ + DwHeaders& header = mMsg->Headers(); + time_t unixTime; + + if (!header.HasDate()) return ""; + unixTime = header.Date().AsUnixTime(); + + QCString result = ctime(&unixTime); + int len = result.length(); + if (result[len-1]=='\n') + result.truncate(len-1); + + return result; +} + + +//----------------------------------------------------------------------------- +QString 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 QString(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 QCString& aStr) +{ + DwHeaders& header = mMsg->Headers(); + + header.Date().FromString(aStr); + header.Date().Parse(); + mNeedsAssembly = true; + mDirty = true; + + if (header.HasDate()) + mDate = header.Date().AsUnixTime(); +} + + +//----------------------------------------------------------------------------- +QString KMMessage::to() const +{ + // handle To same as Cc below, bug 80747 + QValueList<QCString> rawHeaders = rawHeaderFields( "To" ); + QStringList headers; + for ( QValueList<QCString>::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) { + headers << *it; + } + return KPIM::normalizeAddressesAndDecodeIDNs( headers.join( ", " ) ); +} + + +//----------------------------------------------------------------------------- +void KMMessage::setTo(const QString& aStr) +{ + setHeaderField( "To", aStr, Address ); +} + +//----------------------------------------------------------------------------- +QString KMMessage::toStrip() const +{ + return stripEmailAddr( to() ); +} + +//----------------------------------------------------------------------------- +QString KMMessage::replyTo() const +{ + return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("Reply-To") ); +} + + +//----------------------------------------------------------------------------- +void KMMessage::setReplyTo(const QString& aStr) +{ + setHeaderField( "Reply-To", aStr, Address ); +} + + +//----------------------------------------------------------------------------- +void KMMessage::setReplyTo(KMMessage* aMsg) +{ + setHeaderField( "Reply-To", aMsg->from(), Address ); +} + + +//----------------------------------------------------------------------------- +QString KMMessage::cc() const +{ + // get the combined contents of all Cc headers (as workaround for invalid + // messages with multiple Cc headers) + QValueList<QCString> rawHeaders = rawHeaderFields( "Cc" ); + QStringList headers; + for ( QValueList<QCString>::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) { + headers << *it; + } + return KPIM::normalizeAddressesAndDecodeIDNs( headers.join( ", " ) ); +} + + +//----------------------------------------------------------------------------- +void KMMessage::setCc(const QString& aStr) +{ + setHeaderField( "Cc", aStr, Address ); +} + + +//----------------------------------------------------------------------------- +QString KMMessage::ccStrip() const +{ + return stripEmailAddr( cc() ); +} + + +//----------------------------------------------------------------------------- +QString KMMessage::bcc() const +{ + return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("Bcc") ); +} + + +//----------------------------------------------------------------------------- +void KMMessage::setBcc(const QString& aStr) +{ + setHeaderField( "Bcc", aStr, Address ); +} + +//----------------------------------------------------------------------------- +QString KMMessage::fcc() const +{ + return headerField( "X-KMail-Fcc" ); +} + + +//----------------------------------------------------------------------------- +void KMMessage::setFcc( const QString &aStr ) +{ + setHeaderField( "X-KMail-Fcc", aStr ); +} + +//----------------------------------------------------------------------------- +void KMMessage::setDrafts( const QString &aStr ) +{ + mDrafts = aStr; +} + +//----------------------------------------------------------------------------- +void KMMessage::setTemplates( const QString &aStr ) +{ + mTemplates = aStr; +} + +//----------------------------------------------------------------------------- +QString KMMessage::who() const +{ + if (mParent) + return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField(mParent->whoField().utf8()) ); + return from(); +} + + +//----------------------------------------------------------------------------- +QString KMMessage::from() const +{ + return KPIM::normalizeAddressesAndDecodeIDNs( rawHeaderField("From") ); +} + + +//----------------------------------------------------------------------------- +void KMMessage::setFrom(const QString& bStr) +{ + QString aStr = bStr; + if (aStr.isNull()) + aStr = ""; + setHeaderField( "From", aStr, Address ); + mDirty = true; +} + + +//----------------------------------------------------------------------------- +QString KMMessage::fromStrip() const +{ + return stripEmailAddr( from() ); +} + +//----------------------------------------------------------------------------- +QString KMMessage::sender() const { + AddrSpecList asl = extractAddrSpecs( "Sender" ); + if ( asl.empty() ) + asl = extractAddrSpecs( "From" ); + if ( asl.empty() ) + return QString::null; + return asl.front().asString(); +} + +//----------------------------------------------------------------------------- +QString KMMessage::subject() const +{ + return headerField("Subject"); +} + + +//----------------------------------------------------------------------------- +void KMMessage::setSubject(const QString& aStr) +{ + setHeaderField("Subject",aStr); + mDirty = true; +} + + +//----------------------------------------------------------------------------- +QString KMMessage::xmark() const +{ + return headerField("X-KMail-Mark"); +} + + +//----------------------------------------------------------------------------- +void KMMessage::setXMark(const QString& aStr) +{ + setHeaderField("X-KMail-Mark", aStr); + mDirty = true; +} + + +//----------------------------------------------------------------------------- +QString KMMessage::replyToId() const +{ + int leftAngle, rightAngle; + QString 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; +} + + +//----------------------------------------------------------------------------- +QString KMMessage::replyToIdMD5() const { + return base64EncodedMD5( replyToId() ); +} + +//----------------------------------------------------------------------------- +QString KMMessage::references() const +{ + int leftAngle, rightAngle; + QString 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 QString::null; +} + +//----------------------------------------------------------------------------- +QString KMMessage::replyToAuxIdMD5() const +{ + QString 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 ); +} + +//----------------------------------------------------------------------------- +QString KMMessage::strippedSubjectMD5() const { + return base64EncodedMD5( stripOffPrefixes( subject() ), true /*utf8*/ ); +} + +//----------------------------------------------------------------------------- +QString KMMessage::subjectMD5() const { + return base64EncodedMD5( subject(), true /*utf8*/ ); +} + +//----------------------------------------------------------------------------- +bool KMMessage::subjectIsPrefixed() const { + return subjectMD5() != strippedSubjectMD5(); +} + +//----------------------------------------------------------------------------- +void KMMessage::setReplyToId(const QString& aStr) +{ + setHeaderField("In-Reply-To", aStr); + mDirty = true; +} + + +//----------------------------------------------------------------------------- +QString KMMessage::msgId() const +{ + QString 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; +} + + +//----------------------------------------------------------------------------- +QString KMMessage::msgIdMD5() const { + return base64EncodedMD5( msgId() ); +} + + +//----------------------------------------------------------------------------- +void KMMessage::setMsgId(const QString& 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", QCString().setNum(size)); + mDirty = true; +} + +//----------------------------------------------------------------------------- +ulong KMMessage::UID() const { + return headerField( "X-UID" ).toULong(); +} + + +//----------------------------------------------------------------------------- +void KMMessage::setUID(ulong uid) +{ + setHeaderField("X-UID", QCString().setNum(uid)); + mDirty = true; +} + +//----------------------------------------------------------------------------- +AddressList KMMessage::splitAddrField( const QCString & 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 QCString & aName ) const { + return KMMessage::splitAddrField( rawHeaderField( aName ) ); +} + +AddrSpecList KMMessage::extractAddrSpecs( const QCString & 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; +} + +QCString KMMessage::rawHeaderField( const QCString & name ) const { + if ( name.isEmpty() ) return QCString(); + + DwHeaders & header = mMsg->Headers(); + DwField * field = header.FindField( name ); + + if ( !field ) return QCString(); + + return header.FieldBody( name.data() ).AsString().c_str(); +} + +QValueList<QCString> KMMessage::rawHeaderFields( const QCString& field ) const +{ + if ( field.isEmpty() || !mMsg->Headers().FindField( field ) ) + return QValueList<QCString>(); + + std::vector<DwFieldBody*> v = mMsg->Headers().AllFieldBodies( field.data() ); + QValueList<QCString> headerFields; + for ( uint i = 0; i < v.size(); ++i ) { + headerFields.append( v[i]->AsString().c_str() ); + } + + return headerFields; +} + +QString KMMessage::headerField(const QCString& aName) const +{ + if ( aName.isEmpty() ) + return QString::null; + + if ( !mMsg->Headers().FindField( aName ) ) + return QString::null; + + return decodeRFC2047String( mMsg->Headers().FieldBody( aName.data() ).AsString().c_str(), + charset() ); + +} + +QStringList KMMessage::headerFields( const QCString& field ) const +{ + if ( field.isEmpty() || !mMsg->Headers().FindField( field ) ) + return QStringList(); + + std::vector<DwFieldBody*> v = mMsg->Headers().AllFieldBodies( field.data() ); + QStringList headerFields; + for ( uint i = 0; i < v.size(); ++i ) { + headerFields.append( decodeRFC2047String( v[i]->AsString().c_str(), charset() ) ); + } + + return headerFields; +} + +//----------------------------------------------------------------------------- +void KMMessage::removeHeaderField(const QCString& aName) +{ + DwHeaders & header = mMsg->Headers(); + DwField * field = header.FindField(aName); + if (!field) return; + + header.RemoveField(field); + mNeedsAssembly = true; +} + +//----------------------------------------------------------------------------- +void KMMessage::removeHeaderFields(const QCString& aName) +{ + DwHeaders & header = mMsg->Headers(); + while ( DwField * field = header.FindField(aName) ) { + header.RemoveField(field); + mNeedsAssembly = true; + } +} + + +//----------------------------------------------------------------------------- +void KMMessage::setHeaderField( const QCString& aName, const QString& 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; + QCString aValue; + if (!bValue.isEmpty()) + { + QString value = bValue; + if ( type == Address ) + value = KPIM::normalizeAddressesAndEncodeIDNs( value ); +#if 0 + if ( type != Unstructured ) + kdDebug(5006) << "value: \"" << value << "\"" << endl; +#endif + QCString 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; + if (str[str.length()-1] != ':') str += ": "; + else str += ' '; + if ( !aValue.isEmpty() ) + str += aValue; + 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; +} + + +//----------------------------------------------------------------------------- +QCString 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 QCString& aStr) +{ + dwContentType().SetTypeStr(DwString(aStr)); + dwContentType().Parse(); + mNeedsAssembly = true; +} + + +//----------------------------------------------------------------------------- +void KMMessage::setType(int aType) +{ + dwContentType().SetType(aType); + dwContentType().Assemble(); + mNeedsAssembly = true; +} + + + +//----------------------------------------------------------------------------- +QCString 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 QCString& 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 QCString& attr, + const QCString& 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 QCString& attr, const QCString& val) +{ + if (mNeedsAssembly) mMsg->Assemble(); + mNeedsAssembly = false; + setDwMediaTypeParam( dwContentType(), attr, val ); + mNeedsAssembly = true; +} + + +//----------------------------------------------------------------------------- +QCString KMMessage::contentTransferEncodingStr() const +{ + DwHeaders& header = mMsg->Headers(); + if (header.HasContentTransferEncoding()) + return header.ContentTransferEncoding().AsString().c_str(); + else return ""; +} + + +//----------------------------------------------------------------------------- +int KMMessage::contentTransferEncoding() const +{ + DwHeaders& header = mMsg->Headers(); + if (header.HasContentTransferEncoding()) + return header.ContentTransferEncoding().AsEnum(); + else return DwMime::kCteNull; +} + + +//----------------------------------------------------------------------------- +void KMMessage::setContentTransferEncodingStr(const QCString& aStr) +{ + mMsg->Headers().ContentTransferEncoding().FromString(aStr); + mMsg->Headers().ContentTransferEncoding().Parse(); + mNeedsAssembly = true; +} + + +//----------------------------------------------------------------------------- +void KMMessage::setContentTransferEncoding(int aCte) +{ + mMsg->Headers().ContentTransferEncoding().FromEnum(aCte); + mNeedsAssembly = true; +} + + +//----------------------------------------------------------------------------- +DwHeaders& KMMessage::headers() const +{ + return mMsg->Headers(); +} + + +//----------------------------------------------------------------------------- +void KMMessage::setNeedsAssembly() +{ + mNeedsAssembly = true; +} + + +//----------------------------------------------------------------------------- +QCString KMMessage::body() const +{ + const DwString& body = mMsg->Body().AsString(); + QCString 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; +} + + +//----------------------------------------------------------------------------- +QByteArray 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(); + QByteArray ba(len); + memcpy(ba.data(),dwstr.data(),len); + return ba; +} + + +//----------------------------------------------------------------------------- +QCString 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 QCString::length() is slow + //QCString result = KMail::Util::CString( dwstr ); + //kdWarning(result.length() != len, 5006) + // << "KMMessage::bodyDecoded(): body is binary but used as text!" << endl; + //return result; +} + + +//----------------------------------------------------------------------------- +QValueList<int> KMMessage::determineAllowedCtes( const CharFreq& cf, + bool allow8Bit, + bool willBeSigned ) +{ + QValueList<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 QByteArray& aBuf, + QValueList<int> & allowedCte, + bool allow8Bit, + bool willBeSigned ) +{ + CharFreq cf( aBuf ); // it's safe to pass null arrays + + allowedCte = determineAllowedCtes( cf, allow8Bit, willBeSigned ); + +#ifndef NDEBUG + DwString dwCte; + DwCteEnumToStr(allowedCte[0], dwCte); + kdDebug(5006) << "CharFreq returned " << cf.type() << "/" + << cf.printableRatio() << " and I chose " + << dwCte.c_str() << endl; +#endif + + setCte( allowedCte[0] ); // choose best fitting + setBodyEncodedBinary( aBuf ); +} + + +//----------------------------------------------------------------------------- +void KMMessage::setBodyAndGuessCte( const QCString& aBuf, + QValueList<int> & allowedCte, + bool allow8Bit, + bool willBeSigned ) +{ + CharFreq cf( aBuf.data(), aBuf.size()-1 ); // it's safe to pass null strings + + allowedCte = determineAllowedCtes( cf, allow8Bit, willBeSigned ); + +#ifndef NDEBUG + DwString dwCte; + DwCteEnumToStr(allowedCte[0], dwCte); + kdDebug(5006) << "CharFreq returned " << cf.type() << "/" + << cf.printableRatio() << " and I chose " + << dwCte.c_str() << endl; +#endif + + setCte( allowedCte[0] ); // choose best fitting + setBodyEncoded( aBuf ); +} + + +//----------------------------------------------------------------------------- +void KMMessage::setBodyEncoded(const QCString& aStr) +{ + DwString dwSrc(aStr.data(), aStr.size()-1 /* not the trailing NUL */); + DwString dwResult; + + switch (cte()) + { + case DwMime::kCteBase64: + DwEncodeBase64(dwSrc, dwResult); + break; + case DwMime::kCteQuotedPrintable: + DwEncodeQuotedPrintable(dwSrc, dwResult); + break; + default: + dwResult = dwSrc; + break; + } + + mMsg->Body().FromString(dwResult); + mNeedsAssembly = true; +} + +//----------------------------------------------------------------------------- +void KMMessage::setBodyEncodedBinary(const QByteArray& aStr) +{ + DwString dwSrc(aStr.data(), aStr.size()); + DwString dwResult; + + switch (cte()) + { + case DwMime::kCteBase64: + DwEncodeBase64(dwSrc, dwResult); + break; + case DwMime::kCteQuotedPrintable: + DwEncodeQuotedPrintable(dwSrc, dwResult); + break; + default: + dwResult = dwSrc; + break; + } + + mMsg->Body().FromString(dwResult); + mNeedsAssembly = true; +} + + +//----------------------------------------------------------------------------- +void KMMessage::setBody(const QCString& 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 QCString & 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(); + QPtrList< 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; + QPtrList< 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; + QPtrList< 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; + QPtrList< 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 QCString& type, const QCString& subtype ) const +{ + DwBodyPart *part, *curpart; + QPtrList< 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 + QCString 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 (!qstricmp(param->Attribute().c_str(), "charset")) + aPart->setCharset(QCString(param->Value().c_str()).lower()); + else if (!qstrnicmp(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(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 + QString partId( aDwBodyPart->partId() ); + aPart->setPartSpecifier( partId ); + + DwHeaders& headers = aDwBodyPart->Headers(); + applyHeadersToMessagePart( headers, aPart ); + + // Body + if (withBody) + aPart->setBody( aDwBodyPart->Body().AsString() ); + else + aPart->setBody( QCString("") ); + + // Content-id + if ( headers.HasContentId() ) { + const QCString 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(QCString("")); + 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(); +} + +//----------------------------------------------------------------------------- +DwBodyPart* KMMessage::createDWBodyPart(const KMMessagePart* aPart) +{ + DwBodyPart* part = DwBodyPart::NewBodyPart(emptyString, 0); + + if ( !aPart ) + return part; + + QCString charset = aPart->charset(); + QCString type = aPart->typeStr(); + QCString subtype = aPart->subtypeStr(); + QCString cte = aPart->cteStr(); + QCString contDesc = aPart->contentDescriptionEncoded(); + QCString contDisp = aPart->contentDisposition(); + QCString encoding = autoDetectCharset(charset, sPrefCharsets, aPart->name()); + if (encoding.isEmpty()) encoding = "utf-8"; + QCString name = KMMsgBase::encodeRFC2231String(aPart->name(), encoding); + bool RFC2231encoded = aPart->name() != QString(name); + QCString 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); + } + } + + QCString additionalParam = aPart->additionalCTypeParamStr(); + if( !additionalParam.isEmpty() ) + { + QCString 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 ); + parV = parAV.right( parAV.length() - iM - 1 ); + if( ('"' == parV.at(0)) && ('"' == parV.at(parV.length()-1)) ) + { + parV.erase( 0, 1); + parV.erase( parV.length()-1 ); + } + } + else + { + parA = parAV; + 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()) + { + QCString encoding = autoDetectCharset(charset, sPrefCharsets, + aPart->parameterValue()); + if (encoding.isEmpty()) encoding = "utf-8"; + QCString paramValue; + paramValue = KMMsgBase::encodeRFC2231String(aPart->parameterValue(), + encoding); + DwParameter *param = new DwParameter; + if (aPart->parameterValue() != QString(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 ); +} + + +//----------------------------------------------------------------------------- +QString KMMessage::generateMessageId( const QString& addr ) +{ + QDateTime datetime = QDateTime::currentDateTime(); + QString msgIdStr; + + msgIdStr = '<' + datetime.toString( "yyyyMMddhhmm.sszzz" ); + + QString msgIdSuffix; + KConfigGroup 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; +} + + +//----------------------------------------------------------------------------- +QCString KMMessage::html2source( const QCString & src ) +{ + QCString result( 1 + 6*(src.size()-1) ); // maximal possible length + + QCString::ConstIterator s = src.begin(); + QCString::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; +} + +//----------------------------------------------------------------------------- +QString KMMessage::encodeMailtoUrl( const QString& str ) +{ + QString result; + result = QString::fromLatin1( KMMsgBase::encodeRFC2047String( str, + "utf-8" ) ); + result = KURL::encode_string( result ); + return result; +} + + +//----------------------------------------------------------------------------- +QString KMMessage::decodeMailtoUrl( const QString& url ) +{ + QString result; + result = KURL::decode_string( url ); + result = KMMsgBase::decodeRFC2047String( result.latin1() ); + return result; +} + + +//----------------------------------------------------------------------------- +QCString KMMessage::stripEmailAddr( const QCString& aStr ) +{ + //kdDebug(5006) << "KMMessage::stripEmailAddr( " << aStr << " )" << endl; + + if ( aStr.isEmpty() ) + return QCString(); + + QCString 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. + + QCString name; + QCString comment; + QCString angleAddress; + enum { TopLevel, InComment, InAngleAddress } context = TopLevel; + bool inQuotedString = false; + int commentLevel = 0; + + for ( 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 = QCString(); + comment = QCString(); + angleAddress = QCString(); + } + 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; +} + +//----------------------------------------------------------------------------- +QString KMMessage::stripEmailAddr( const QString& aStr ) +{ + //kdDebug(5006) << "KMMessage::stripEmailAddr( " << aStr << " )" << endl; + + if ( aStr.isEmpty() ) + return QString::null; + + QString 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. + + QString name; + QString comment; + QString angleAddress; + enum { TopLevel, InComment, InAngleAddress } context = TopLevel; + bool inQuotedString = false; + int commentLevel = 0; + + QChar 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 = QString::null; + comment = QString::null; + angleAddress = QString::null; + } + 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; +} + +//----------------------------------------------------------------------------- +QString KMMessage::quoteHtmlChars( const QString& str, bool removeLineBreaks ) +{ + QString 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 += "<"; + break; + case '>': + result += ">"; + break; + case '&': + result += "&"; + break; + case '"': + result += """; + break; + case '\n': + if ( !removeLineBreaks ) + result += "<br>"; + break; + case '\r': + // ignore CR + break; + default: + result += str[i]; + } + + result.squeeze(); + return result; +} + +//----------------------------------------------------------------------------- +QString KMMessage::emailAddrAsAnchor(const QString& aEmail, bool stripped, const QString& cssStyle, bool aLink) +{ + if( aEmail.isEmpty() ) + return aEmail; + + QStringList addressList = KPIM::splitEmailAddrList( aEmail ); + + QString result; + + for( QStringList::ConstIterator it = addressList.begin(); + ( it != addressList.end() ); + ++it ) { + if( !(*it).isEmpty() ) { + QString address = *it; + if(aLink) { + result += "<a href=\"mailto:" + + KMMessage::encodeMailtoUrl( address ) + + "\" "+cssStyle+">"; + } + if( stripped ) + address = KMMessage::stripEmailAddr( address ); + result += KMMessage::quoteHtmlChars( address, 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 +QStringList KMMessage::stripAddressFromAddressList( const QString& address, + const QStringList& list ) +{ + QStringList addresses( list ); + QString addrSpec( KPIM::getEmailAddress( address ) ); + for ( QStringList::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 +QStringList KMMessage::stripMyAddressesFromAddressList( const QStringList& list ) +{ + QStringList addresses = list; + for( QStringList::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 QString& address, + const QStringList& addresses ) +{ + QString addrSpec = KPIM::getEmailAddress( address ); + for( QStringList::ConstIterator it = addresses.begin(); + it != addresses.end(); ++it ) { + if ( kasciistricmp( addrSpec.utf8().data(), + KPIM::getEmailAddress( *it ).utf8().data() ) == 0 ) + return true; + } + return false; +} + + +//----------------------------------------------------------------------------- +//static +QString KMMessage::expandAliases( const QString& recipients ) +{ + if ( recipients.isEmpty() ) + return QString(); + + QStringList recipientList = KPIM::splitEmailAddrList( recipients ); + + QString expandedRecipients; + for ( QStringList::Iterator it = recipientList.begin(); + it != recipientList.end(); ++it ) { + if ( !expandedRecipients.isEmpty() ) + expandedRecipients += ", "; + QString receiver = (*it).stripWhiteSpace(); + + // try to expand distribution list + QString expandedList = KAddrBookExternal::expandDistributionList( receiver ); + if ( !expandedList.isEmpty() ) { + expandedRecipients += expandedList; + continue; + } + + // try to expand nick name + QString 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 ) { + KConfigGroup general( KMKernel::config(), "General" ); + QString defaultdomain = general.readEntry( "Default domain" ); + if( !defaultdomain.isEmpty() ) { + expandedRecipients += receiver + "@" + defaultdomain; + } + else { + expandedRecipients += guessEmailAddressFromLoginName( receiver ); + } + } + else + expandedRecipients += receiver; + } + + return expandedRecipients; +} + + +//----------------------------------------------------------------------------- +//static +QString KMMessage::guessEmailAddressFromLoginName( const QString& loginName ) +{ + if ( loginName.isEmpty() ) + return QString(); + + 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'; + QString address = loginName; + address += '@'; + address += QString::fromLocal8Bit( hostnameC ); + + // try to determine the real name + const KUser user( loginName ); + if ( user.isValid() ) { + QString fullName = user.fullName(); + if ( fullName.find( QRegExp( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" ) ) != -1 ) + address = '"' + fullName.replace( '\\', "\\" ).replace( '"', "\\" ) + + "\" <" + address + '>'; + else + address = fullName + " <" + address + '>'; + } + + return address; +} + +//----------------------------------------------------------------------------- +void KMMessage::readConfig() +{ + KMMsgBase::readConfig(); + + KConfig *config=KMKernel::config(); + KConfigGroupSaver saver(config, "General"); + + config->setGroup("General"); + + int languageNr = config->readNumEntry("reply-current-language",0); + + { // area for config group "KMMessage #n" + KConfigGroupSaver saver(config, QString("KMMessage #%1").arg(languageNr)); + sReplyLanguage = config->readEntry("language",KGlobal::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" + KConfigGroupSaver 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" + KConfigGroupSaver saver(config, "Reader"); + sHeaderStrategy = HeaderStrategy::create( config->readEntry( "header-set-displayed", "rich" ) ); + } +} + +QCString KMMessage::defaultCharset() +{ + QCString retval; + + if (!sPrefCharsets.isEmpty()) + retval = sPrefCharsets[0].latin1(); + + if (retval.isEmpty() || (retval == "locale")) { + retval = QCString(kmkernel->networkCodec()->mimeName()); + KPIM::kAsciiToLower( retval.data() ); + } + + if (retval == "jisx0208.1983-0") retval = "iso-2022-jp"; + else if (retval == "ksc5601.1987-0") retval = "euc-kr"; + return retval; +} + +const QStringList &KMMessage::preferredCharsets() +{ + return sPrefCharsets; +} + +//----------------------------------------------------------------------------- +QCString 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 QCString& bStr) +{ + 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; + QCString aStr = bStr; + KPIM::kAsciiToLower( aStr.data() ); + DwMediaType &mType = dwContentType(); + mType.Parse(); + DwParameter *param=mType.FirstParameter(); + while(param) + // FIXME use the mimelib functions here for comparison. + if (!kasciistricmp(param->Attribute().c_str(), "charset")) break; + else param=param->Next(); + if (!param){ + param=new DwParameter; + param->SetAttribute("charset"); + mType.AddParameter(param); + } + else + mType.SetModified(); + param->SetValue(DwString(aStr)); + 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 ); + + QString message = headerField( "X-KMail-Link-Message" ); + if ( !message.isEmpty() ) + message += ','; + QString type = headerField( "X-KMail-Link-Type" ); + if ( !type.isEmpty() ) + type += ','; + + message += QString::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 *retStatus) const +{ + *retMsgSerNum = 0; + *retStatus = KMMsgStatusUnknown; + + QString message = headerField("X-KMail-Link-Message"); + QString 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" ) + *retStatus = KMMsgStatusReplied; + else if ( type == "forward" ) + *retStatus = KMMsgStatusForwarded; + else if ( type == "deleted" ) + *retStatus = KMMsgStatusDeleted; + } +} + +//----------------------------------------------------------------------------- +DwBodyPart* KMMessage::findDwBodyPart( DwBodyPart* part, const QString & 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 QString partSpecifier, const QByteArray & data) +{ + if ( !data.data() || !data.size() ) + return; + + DwString content( data.data(), data.size() ); + if ( numBodyParts() > 0 && + partSpecifier != "0" && + partSpecifier != "TEXT" ) + { + QString 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( QMAX( 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 ); + QString 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::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(); + } + } + } + + 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 QString & str ) { + QCString encoding = KMMsgBase::autoDetectCharset( charset(), KMMessage::preferredCharsets(), str ); + if ( encoding.isEmpty() ) + encoding = "utf-8"; + const QTextCodec * codec = KMMsgBase::codecForName( encoding ); + assert( codec ); + QValueList<int> dummy; + setCharset( encoding ); + setBodyAndGuessCte( codec->fromUnicode( str ), dummy, false /* no 8bit */ ); +} + +const QTextCodec * KMMessage::codec() const { + const QTextCodec * 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; +} + +QString KMMessage::bodyToUnicode(const QTextCodec* codec) const { + if ( !codec ) + // No codec was given, so try the charset in the mail + codec = this->codec(); + assert( codec ); + + return codec->toUnicode( bodyDecoded() ); +} + +//----------------------------------------------------------------------------- +QCString KMMessage::mboxMessageSeparator() +{ + QCString str( KPIM::getFirstEmailAddress( rawHeaderField("From") ) ); + if ( str.isEmpty() ) + str = "unknown@unknown.invalid"; + QCString 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; +} |