You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3402 lines
101 KiB
3402 lines
101 KiB
// -*- mode: C++; c-file-style: "gnu" -*- |
|
// kmmessage.cpp |
|
|
|
#include "kmmessage.h" |
|
|
|
#include <config-kmail.h> |
|
|
|
// if you do not want GUI elements in here then set ALLOW_GUI to 0. |
|
// needed temporarily until KMime is replacing the partNode helper class: |
|
#include "partNode.h" |
|
#define ALLOW_GUI 1 |
|
#include "kmkernel.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 <version-kmail.h> |
|
#include "kmmessagetag.h" |
|
#include "libmessageviewer/headerstrategy.h" |
|
#include "globalsettings.h" |
|
using Message::HeaderStrategy; |
|
#include "libmessageviewer/kmaddrbook.h" |
|
#include "libmessageviewer/kcursorsaver.h" |
|
#include "templateparser.h" |
|
using KMail::TemplateParser; |
|
#include "stringutil.h" |
|
#include "mdnadvicedialog.h" |
|
|
|
#include <kpimidentities/identity.h> |
|
#include <kpimidentities/identitymanager.h> |
|
#include <kpimutils/email.h> |
|
|
|
#include <libkpgp/kpgpblock.h> |
|
#include <kaddrbookexternal.h> |
|
|
|
#include <kglobal.h> |
|
#include <kascii.h> |
|
#include <kglobalsettings.h> |
|
#include <kdebug.h> |
|
#include <kconfig.h> |
|
#include <khtml_part.h> |
|
#include <kuser.h> |
|
#include <kconfiggroup.h> |
|
#include <KProtocolManager> |
|
|
|
#include <QList> |
|
#include <QCursor> |
|
#include <QMessageBox> |
|
#include <QTextCodec> |
|
#include <QByteArray> |
|
#include <QHostInfo> |
|
|
|
#include <kmime/kmime_dateformatter.h> |
|
#include <kmime/kmime_charfreq.h> |
|
#include <kmime/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> |
|
|
|
#if ALLOW_GUI |
|
#include <kmessagebox.h> |
|
#include "util.h" |
|
#endif |
|
|
|
using namespace KMail; |
|
using namespace KMime; |
|
|
|
struct KMMessageStaticData |
|
{ |
|
KMMessageStaticData() |
|
: emptyString(""), headerStrategy( HeaderStrategy::rich() ) |
|
{ |
|
} |
|
|
|
DwString emptyString; |
|
|
|
// Values that are set from the config file with KMMessage::readConfig() |
|
bool smartQuote : 1, wordWrap : 1; |
|
int wrapCol; |
|
QStringList prefCharsets; |
|
const HeaderStrategy* headerStrategy; |
|
QList<KMMessage*> pendingDeletes; |
|
}; |
|
|
|
K_GLOBAL_STATIC( KMMessageStaticData, s ) |
|
|
|
//helper |
|
static void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart ); |
|
|
|
//----------------------------------------------------------------------------- |
|
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(); |
|
if ( msgInfo.tagList() ) { |
|
if ( !mTagList ) |
|
mTagList = new KMMessageTagList(); |
|
*mTagList = *msgInfo.tagList(); |
|
} else { |
|
delete mTagList; |
|
mTagList = 0; |
|
} |
|
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.clear(); |
|
mStatus.setNew(); |
|
mEncryptionState = KMMsgEncryptionStateUnknown; |
|
mSignatureState = KMMsgSignatureStateUnknown; |
|
mMDNSentState = KMMsgMDNStateUnknown; |
|
mDate = 0; |
|
mUnencryptedMsg = 0; |
|
mLastUpdated = 0; |
|
mTagList = 0; |
|
mCursorPos = 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; |
|
if ( other.tagList() ) { |
|
if ( !mTagList ) |
|
mTagList = new KMMessageTagList(); |
|
*mTagList = *other.tagList(); |
|
} else { |
|
delete mTagList; |
|
mTagList = 0; |
|
} |
|
setDrafts( other.drafts() ); |
|
setTemplates( other.templates() ); |
|
//mFileName = ""; // we might not want to copy the other messages filename (?) |
|
//KMMsgBase::assign( &other ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
KMMessage::~KMMessage() |
|
{ |
|
delete mMsg; |
|
kmkernel->undoStack()->msgDestroyed( this ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setReferences(const QByteArray& aStr) |
|
{ |
|
if (aStr.isNull()) return; |
|
mMsg->Headers().References().FromString(aStr); |
|
mNeedsAssembly = true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray KMMessage::id() const |
|
{ |
|
DwHeaders& header = mMsg->Headers(); |
|
if (header.HasMessageId()) |
|
return header.MessageId().AsString().c_str(); |
|
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() && s->pendingDeletes.contains( this ) ) { |
|
s->pendingDeletes.removeAll( this ); |
|
if ( parent() ) { |
|
int idx = parent()->find( this ); |
|
if ( idx > 0 ) { |
|
parent()->removeMsg( idx ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
bool KMMessage::isUrgent() const { |
|
return headerField( "Priority" ).contains( "urgent", Qt::CaseSensitive ) |
|
|| headerField( "X-Priority" ).startsWith( '2' ) |
|
|| headerField( "X-Priority" ).startsWith( '1' ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setUnencryptedMsg( KMMessage* unencrypted ) |
|
{ |
|
delete mUnencryptedMsg; |
|
mUnencryptedMsg = unencrypted; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
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; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray KMMessage::asString() const { |
|
return QByteArray( asDwString().c_str() ); |
|
} |
|
|
|
|
|
QByteArray KMMessage::asSendableString() const |
|
{ |
|
KMMessage msg( new DwMessage( *this->mMsg ) ); |
|
msg.removePrivateHeaderFields(); |
|
msg.removeHeaderField("Bcc"); |
|
return msg.asString(); |
|
} |
|
|
|
QByteArray KMMessage::headerAsSendableString() const |
|
{ |
|
KMMessage msg( new DwMessage( *this->mMsg ) ); |
|
msg.removePrivateHeaderFields(); |
|
msg.removeHeaderField("Bcc"); |
|
return msg.headerAsString().toLatin1(); |
|
} |
|
|
|
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-QuotePrefix" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setStatusFields() |
|
{ |
|
char str[2] = { 0, 0 }; |
|
|
|
setHeaderField( "Status", mStatus.isNew() ? "R" : "RO" ); |
|
setHeaderField( "X-Status", mStatus.getStatusStr() ); |
|
|
|
str[0] = (char)encryptionState(); |
|
setHeaderField("X-KMail-EncryptionState", str); |
|
|
|
str[0] = (char)signatureState(); |
|
//kDebug() << "Setting SignatureState header field to" << str[0]; |
|
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(); |
|
return QString::fromLatin1( header.AsString().c_str() ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
DwMediaType& KMMessage::dwContentType() |
|
{ |
|
return mMsg->Headers().ContentType(); |
|
} |
|
|
|
void KMMessage::fromString( const QByteArray & ba, bool setStatus ) { |
|
return fromDwString( DwString( ba.data(), ba.size() ), setStatus ); |
|
} |
|
|
|
void KMMessage::fromDwString(const DwString& str, bool aSetStatus) |
|
{ |
|
delete mMsg; |
|
mMsg = new DwMessage; |
|
mMsg->FromString( str ); |
|
mMsg->Parse(); |
|
|
|
if (aSetStatus) { |
|
setStatus(headerField("Status").toLatin1(), headerField("X-Status").toLatin1()); |
|
if ( !headerField( "X-KMail-EncryptionState" ).isEmpty() ) |
|
setEncryptionStateChar( headerField( "X-KMail-EncryptionState" ).at(0) ); |
|
if ( !headerField( "X-KMail-SignatureState" ).isEmpty() ) |
|
setSignatureStateChar( headerField( "X-KMail-SignatureState" ).at(0)); |
|
if ( !headerField("X-KMail-MDN-Sent").isEmpty() ) |
|
setMDNSentState( static_cast<KMMsgMDNSentState>( headerField("X-KMail-MDN-Sent").at(0).toLatin1() ) ); |
|
} |
|
if (attachmentState() == KMMsgAttachmentUnknown && readyToShow()) |
|
updateAttachmentState(); |
|
|
|
mNeedsAssembly = false; |
|
mDate = date(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::parseTextStringFromDwPart( partNode * root, |
|
QByteArray& parsedString, |
|
const QTextCodec*& codec, |
|
bool& isHTML ) const |
|
{ |
|
if ( !root ) |
|
return; |
|
|
|
isHTML = false; |
|
partNode * curNode = root->findType( DwMime::kTypeText, |
|
DwMime::kSubtypeUnknown, |
|
true, false ); |
|
kDebug() << ( curNode ? "text part found!\n" : "sorry, no text node!\n" ); |
|
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::asPlainTextFromObjectTree( partNode *root, bool aStripSignature, |
|
bool allowDecryption ) const |
|
{ |
|
Q_ASSERT( root ); |
|
Q_ASSERT( root->processed() ); |
|
|
|
QByteArray parsedString; |
|
bool isHTML = false; |
|
const QTextCodec * codec = 0; |
|
|
|
if ( !root ) return QString(); |
|
parseTextStringFromDwPart( root, parsedString, codec, isHTML ); |
|
|
|
if ( mOverrideCodec || !codec ) |
|
codec = this->codec(); |
|
|
|
if ( parsedString.isEmpty() ) |
|
return QString(); |
|
|
|
bool clearSigned = false; |
|
QString result; |
|
|
|
// decrypt |
|
if ( allowDecryption ) { |
|
QList<Kpgp::Block> pgpBlocks; |
|
QList<QByteArray> 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 StringUtil::stripSignature( result, clearSigned ); |
|
else |
|
return result; |
|
} |
|
|
|
QString KMMessage::asPlainText( bool aStripSignature, bool allowDecryption ) const |
|
{ |
|
partNode *root = partNode::fromMessage( this ); |
|
if ( !root ) |
|
return QString(); |
|
|
|
ObjectTreeParser otp; |
|
otp.parseObjectTree( root ); |
|
QString result = asPlainTextFromObjectTree( root, aStripSignature, allowDecryption ); |
|
delete root; |
|
return result; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
QString KMMessage::asQuotedString( const QString& aIndentStr, |
|
const QString& selection /*.clear() */, |
|
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.indexOf( QRegExp( "\\S" ) ); |
|
const int lineStart = content.lastIndexOf( '\n', firstNonWS ); |
|
if ( lineStart >= 0 ) |
|
content.remove( 0, static_cast<unsigned int>( lineStart ) ); |
|
|
|
const QString indentStr = StringUtil::formatString( aIndentStr, from() ); |
|
|
|
if ( s->smartQuote && s->wordWrap ) |
|
content = StringUtil::smartQuote( content, s->wrapCol - indentStr.length() ); |
|
|
|
content.replace( '\n', '\n' + indentStr ); |
|
content.prepend( indentStr ); |
|
content += '\n'; |
|
|
|
return content; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
KMMessage* KMMessage::createReply( ReplyStrategy replyStrategy, |
|
const QString &selection /*.clear() */, |
|
bool noQuote /* = false */, |
|
bool allowDecryption /* = true */, |
|
bool selectionIsBody /* = false */, |
|
const QString &tmpl /* = QString() */ ) |
|
{ |
|
KMMessage* msg = new KMMessage; |
|
QString str, mailingListStr, replyToStr, toStr; |
|
QStringList mailingListAddresses; |
|
QByteArray 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").contains( "mailto:", Qt::CaseInsensitive ) ) { |
|
QString listPost = headerField("List-Post"); |
|
QRegExp rx( "<mailto:([^@>]+)@([^>]+)>", Qt::CaseInsensitive ); |
|
if ( rx.indexIn( listPost, 0 ) != -1 ) // matched |
|
mailingListAddresses << rx.cap(1) + '@' + rx.cap(2); |
|
} |
|
|
|
switch( replyStrategy ) { |
|
case 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(); |
|
replyAll = false; |
|
} |
|
// strip all my addresses from the list of recipients |
|
QStringList recipients = KPIMUtils::splitAddressList( toStr ); |
|
toStr = StringUtil::stripMyAddressesFromAddressList( recipients ).join(", "); |
|
// ... unless the list contains only my addresses (reply to self) |
|
if ( toStr.isEmpty() && !recipients.isEmpty() ) |
|
toStr = recipients[0]; |
|
|
|
break; |
|
} |
|
case 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 = KPIMUtils::splitAddressList( toStr ); |
|
toStr = StringUtil::stripMyAddressesFromAddressList( recipients ).join(", "); |
|
|
|
break; |
|
} |
|
case ReplyAll : { |
|
QStringList recipients; |
|
QStringList ccRecipients; |
|
|
|
// add addresses from the Reply-To header to the list of recipients |
|
if( !replyToStr.isEmpty() ) { |
|
recipients += KPIMUtils::splitAddressList( replyToStr ); |
|
// strip all possible mailing list addresses from the list of Reply-To |
|
// addresses |
|
for ( QStringList::const_iterator it = mailingListAddresses.constBegin(); |
|
it != mailingListAddresses.constEnd(); |
|
++it ) { |
|
recipients = StringUtil::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(); |
|
kDebug() << "Added" << from() <<"to the list of CC recipients"; |
|
} |
|
// 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(); |
|
kDebug() << "Added" << from() <<"to the list of recipients"; |
|
} |
|
} |
|
|
|
// strip all my addresses from the list of recipients |
|
toStr = StringUtil::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 += KPIMUtils::splitAddressList(to()); |
|
if (!cc().isEmpty()) |
|
list += KPIMUtils::splitAddressList(cc()); |
|
for( QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it ) { |
|
if( !StringUtil::addressIsInAddressList( *it, recipients ) |
|
&& !StringUtil::addressIsInAddressList( *it, ccRecipients ) ) { |
|
ccRecipients += *it; |
|
kDebug() << "Added" << *it <<"to the list of CC recipients"; |
|
} |
|
} |
|
} |
|
|
|
if ( !ccRecipients.isEmpty() ) { |
|
// strip all my addresses from the list of CC recipients |
|
ccRecipients = StringUtil::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 ReplyAuthor : { |
|
if ( !replyToStr.isEmpty() ) { |
|
QStringList recipients = KPIMUtils::splitAddressList( 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.constBegin(); |
|
it != mailingListAddresses.constEnd(); |
|
++it ) { |
|
recipients = StringUtil::stripAddressFromAddressList( *it, recipients ); |
|
} |
|
if ( !recipients.isEmpty() ) { |
|
toStr = recipients.join(", "); |
|
} |
|
else { |
|
// there was only the mailing list post address in the Reply-To header, |
|
// so use the From address instead |
|
toStr = from(); |
|
} |
|
} |
|
else if ( !from().isEmpty() ) { |
|
toStr = from(); |
|
} |
|
replyAll = false; |
|
break; |
|
} |
|
case 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()); |
|
|
|
msg->setSubject( replySubject() ); |
|
|
|
// If the reply shouldn't be blank, apply the template to the message |
|
if ( !noQuote ) { |
|
TemplateParser parser( msg, (replyAll ? TemplateParser::ReplyAll : TemplateParser::Reply), |
|
selection, s->smartQuote, allowDecryption, selectionIsBody ); |
|
if ( !tmpl.isEmpty() ) |
|
parser.process( tmpl, this ); |
|
else |
|
parser.process( this ); |
|
} |
|
|
|
msg->link( this, MessageStatus::statusReplied() ); |
|
|
|
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; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray KMMessage::getRefStr() const |
|
{ |
|
QByteArray firstRef, lastRef, refStr, retRefStr; |
|
int i, j; |
|
|
|
refStr = headerField("References").trimmed().toLatin1(); |
|
|
|
if (refStr.isEmpty()) |
|
return headerField("Message-Id").toLatin1(); |
|
|
|
i = refStr.indexOf('<'); |
|
j = refStr.indexOf('>'); |
|
firstRef = refStr.mid(i, j-i+1); |
|
if (!firstRef.isEmpty()) |
|
retRefStr = firstRef + ' '; |
|
|
|
i = refStr.lastIndexOf('<'); |
|
j = refStr.lastIndexOf('>'); |
|
|
|
lastRef = refStr.mid(i, j-i+1); |
|
if (!lastRef.isEmpty() && lastRef != firstRef) |
|
retRefStr += lastRef + ' '; |
|
|
|
retRefStr += headerField("Message-Id").toLatin1(); |
|
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" ).trimmed(); |
|
if ( !strId.isEmpty()) |
|
id = strId.toUInt(); |
|
const KPIMIdentities::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", StringUtil::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, MessageStatus::statusForwarded() ); |
|
|
|
return msg; |
|
} |
|
|
|
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() */ ) |
|
{ |
|
KMMessage* msg = new KMMessage(); |
|
|
|
// 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. |
|
if ( type() != DwMime::kTypeMultipart && |
|
( type() != DwMime::kTypeText || |
|
( type() == DwMime::kTypeText && |
|
subtype() != DwMime::kSubtypeHtml && subtype() != DwMime::kSubtypePlain ) ) ) { |
|
|
|
|
|
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().c_str() ); |
|
// use the headers of the original mail |
|
applyHeadersToMessagePart( mMsg->Headers(), &secondPart ); |
|
msg->addBodyPart(&secondPart); |
|
msg->mNeedsAssembly = true; |
|
msg->cleanupHeader(); |
|
} |
|
|
|
// Normal message (multipart or text/plain|html) |
|
// Just copy the message, the template parser will do the hard work of |
|
// replacing the body text in TemplateParser::addProcessedBodyToMessage() |
|
else { |
|
msg->fromDwString( this->asDwString() ); |
|
DwMediaType oldContentType = msg->mMsg->Headers().ContentType(); |
|
msg->sanitizeHeaders(); |
|
msg->initFromMessage( this ); |
|
|
|
// restore the content type, initFromMessage() sets the contents type to |
|
// text/plain, via initHeader(), for unclear reasons |
|
msg->mMsg->Headers().ContentType().FromString( oldContentType.AsString() ); |
|
msg->mMsg->Headers().ContentType().Parse(); |
|
msg->mMsg->Assemble(); |
|
} |
|
|
|
msg->setSubject( forwardSubject() ); |
|
|
|
TemplateParser parser( msg, TemplateParser::Forward, |
|
QString(), |
|
false, false, false); |
|
if ( !tmpl.isEmpty() ) |
|
parser.process( tmpl, this ); |
|
else |
|
parser.process( this ); |
|
|
|
msg->link( this, MessageStatus::statusForwarded() ); |
|
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 ) ) { |
|
const KCursorSaver saver( Qt::ArrowCursor ); |
|
MDNAdviceDialog::MDNAdvice answer; |
|
answer = MDNAdviceDialog::questionIgnoreSend( mdnMessageBoxes[i].text, |
|
mdnMessageBoxes[i].canDeny ); |
|
switch ( answer ) { |
|
case MDNAdviceDialog::MDNSend: |
|
return 3; |
|
|
|
case MDNAdviceDialog::MDNSendDenied: |
|
return 2; |
|
|
|
default: |
|
case MDNAdviceDialog::MDNIgnore: |
|
return 0; |
|
} |
|
} |
|
kWarning() <<"didn't find data for message box \"" |
|
<< what << "\""; |
|
return 0; |
|
} |
|
|
|
KMMessage* KMMessage::createMDN( MDN::ActionMode a, |
|
MDN::DispositionType d, |
|
bool allowGUI, |
|
QList<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; |
|
kDebug() << "mdnSentState() == '" << st <<"'"; |
|
#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.trimmed().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.readEntry( "default-policy", 0 ); |
|
if ( !mode || mode < 0 || mode > 3 ) { |
|
// early out for ignore: |
|
setMDNSentState( KMMsgMDNIgnore ); |
|
return 0; |
|
} |
|
if ( mode == 1 /* ask */ && !allowGUI ) |
|
return 0; // don't setMDNSentState here! |
|
|
|
// 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", Qt::CaseSensitive ) ) { |
|
// ### 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. |
|
kDebug() << "KPIMUtils::splitAddressList(receiptTo):" // krazy:exclude=kdebug |
|
<< KPIMUtils::splitAddressList(receiptTo).join("\n"); |
|
if ( KPIMUtils::splitAddressList(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() |
|
: returnPathList.front().localPart + '@' + returnPathList.front().domain ; |
|
kDebug() << "clean return path:" << returnPath; |
|
if ( returnPath.isEmpty() || !receiptTo.contains( returnPath, Qt::CaseSensitive ) ) { |
|
if ( !allowGUI ) return 0; // don't setMDNSentState here! |
|
mode = requestAdviceOnMDN( returnPath.isEmpty() ? |
|
"mdnReturnPathEmpty" : |
|
"mdnReturnPathNotInReceiptTo" ); |
|
s = MDN::SentManually; |
|
} |
|
|
|
if ( true ) { |
|
if ( mode == 1 ) { // ask |
|
assert( allowGUI ); |
|
mode = requestAdviceOnMDN( "mdnNormalAsk" ); |
|
s = MDN::SentManually; // asked user |
|
} |
|
|
|
switch ( mode ) { |
|
case 0: // ignore: |
|
setMDNSentState( KMMsgMDNIgnore ); |
|
return 0; |
|
default: |
|
case 1: |
|
kFatal() << "The \"ask\" mode should never appear here!"; |
|
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.readEntry( "quote-message", 0 ); |
|
if ( num < 0 || num > 2 ) num = 0; |
|
/* 0=> Nothing, 1=>Full Message, 2=>HeadersOnly*/ |
|
|
|
KMMessagePart thirdMsgPart; |
|
switch ( num ) { |
|
case 1: |
|
thirdMsgPart.setTypeStr( "message" ); |
|
thirdMsgPart.setSubtypeStr( "rfc822" ); |
|
thirdMsgPart.setBody( asSendableString() ); |
|
receipt->addBodyPart( &thirdMsgPart ); |
|
break; |
|
case 2: |
|
thirdMsgPart.setTypeStr( "text" ); |
|
thirdMsgPart.setSubtypeStr( "rfc822-headers" ); |
|
thirdMsgPart.setBody( headerAsSendableString() ); |
|
receipt->addBodyPart( &thirdMsgPart ); |
|
break; |
|
case 0: |
|
default: |
|
break; |
|
}; |
|
|
|
receipt->setTo( receiptTo ); |
|
receipt->setSubject( "Message Disposition Notification" ); |
|
receipt->setReplyToId( msgId() ); |
|
receipt->setReferences( getRefStr() ); |
|
|
|
receipt->cleanupHeader(); |
|
|
|
kDebug() << "final message:" + receipt->asString(); |
|
|
|
// |
|
// 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-]+)\\}", Qt::CaseInsensitive ); |
|
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.indexIn( result, idx ) ) != -1 ) { |
|
result.replace( idx, rxDate.matchedLength(), sDate ); |
|
} |
|
|
|
idx = 0; |
|
while ( ( idx = rx.indexIn( result, idx ) ) != -1 ) { |
|
QString replacement = headerField( rx.cap(1).toLatin1() ); |
|
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.trimmed().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 toLatin1 is correct here as Mail headers should contain |
|
// ascii only |
|
receipt->setBody(str.toLatin1()); |
|
receipt->setAutomaticFields(); |
|
|
|
return receipt; |
|
} |
|
|
|
|
|
void KMMessage::applyIdentity( uint id ) |
|
{ |
|
const KPIMIdentities::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() ); |
|
else |
|
setFcc( ident.fcc() ); |
|
|
|
if (ident.drafts().isEmpty()) |
|
setDrafts( QString() ); |
|
else |
|
setDrafts( ident.drafts() ); |
|
|
|
if (ident.templates().isEmpty()) |
|
setTemplates( QString() ); |
|
else |
|
setTemplates( ident.templates() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::initHeader( uint id ) |
|
{ |
|
applyIdentity( id ); |
|
setTo(""); |
|
setSubject(""); |
|
setDateToday(); |
|
|
|
// user agent, e.g. KMail/1.9.50 (Windows/5.0; KDE/3.97.1; i686; svn-762186; 2008-01-15) |
|
QStringList extraInfo; |
|
# if defined KMAIL_SVN_REVISION_STRING && defined KMAIL_SVN_LAST_CHANGE |
|
extraInfo << KMAIL_SVN_REVISION_STRING << KMAIL_SVN_LAST_CHANGE; |
|
#else |
|
#error forgot to include version-kmail.h |
|
# endif |
|
setHeaderField("User-Agent", |
|
KProtocolManager::userAgentForApplication( "KMail", KMAIL_VERSION, extraInfo ) |
|
); |
|
|
|
// This will allow to change Content-Type: |
|
setHeaderField("Content-Type","text/plain"); |
|
} |
|
|
|
uint KMMessage::identityUoid() const { |
|
QString idString = headerField("X-KMail-Identity").trimmed(); |
|
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(); |
|
|
|
//kDebug()<<"#### Date ="<<header.Date().AsString().c_str(); |
|
|
|
return KMime::DateFormatter::formatDate( |
|
static_cast<KMime::DateFormatter::FormatType>(general.readEntry( "dateFormat", |
|
int( KMime::DateFormatter::Fancy ) )), |
|
unixTime, general.readEntry( "customDateFormat" )); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray KMMessage::dateShortStr() const |
|
{ |
|
DwHeaders& header = mMsg->Headers(); |
|
time_t unixTime; |
|
|
|
if (!header.HasDate()) return ""; |
|
unixTime = header.Date().AsUnixTime(); |
|
|
|
QByteArray result = ctime(&unixTime); |
|
|
|
if (result[result.length()-1]=='\n') |
|
result.truncate(result.length()-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 QByteArray& 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 |
|
QList<QByteArray> rawHeaders = rawHeaderFields( "To" ); |
|
QStringList headers; |
|
for ( QList<QByteArray>::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) { |
|
headers << QString::fromUtf8(*it); |
|
} |
|
return KPIMUtils::normalizeAddressesAndDecodeIdn( headers.join( ", " ) ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setTo(const QString& aStr) |
|
{ |
|
setHeaderField( "To", aStr, Address ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMessage::toStrip() const |
|
{ |
|
return StringUtil::stripEmailAddr( to() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMessage::replyTo() const |
|
{ |
|
return KPIMUtils::normalizeAddressesAndDecodeIdn( QString::fromUtf8(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) |
|
QList<QByteArray> rawHeaders = rawHeaderFields( "Cc" ); |
|
QStringList headers; |
|
for ( QList<QByteArray>::Iterator it = rawHeaders.begin(); it != rawHeaders.end(); ++it ) { |
|
headers << QString::fromUtf8(*it); |
|
} |
|
return KPIMUtils::normalizeAddressesAndDecodeIdn( headers.join( ", " ) ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setCc(const QString& aStr) |
|
{ |
|
setHeaderField( "Cc", aStr, Address ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMessage::ccStrip() const |
|
{ |
|
return StringUtil::stripEmailAddr( cc() ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMessage::bcc() const |
|
{ |
|
return KPIMUtils::normalizeAddressesAndDecodeIdn( QString::fromUtf8(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 KPIMUtils::normalizeAddressesAndDecodeIdn( QString::fromUtf8(rawHeaderField(mParent->whoField().toUtf8())) ); |
|
return from(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMessage::from() const |
|
{ |
|
return KPIMUtils::normalizeAddressesAndDecodeIdn( QString::fromUtf8(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 StringUtil::stripEmailAddr( from() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMessage::sender() const { |
|
AddrSpecList asl = extractAddrSpecs( "Sender" ); |
|
if ( asl.empty() ) |
|
asl = extractAddrSpecs( "From" ); |
|
if ( asl.empty() ) |
|
return QString(); |
|
return asl.front().asString(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMessage::subject() const |
|
{ |
|
return headerField("Subject"); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setSubject(const QString& aStr) |
|
{ |
|
setHeaderField("Subject",aStr); |
|
mDirty = true; |
|
} |
|
|
|
//Reimplement virtuals from KMMsgBase |
|
//----------------------------------------------------------------------------- |
|
//Different from KMMsgInfo as that reads from the index |
|
QString KMMessage::tagString() const |
|
{ |
|
if ( mTagList ) |
|
return mTagList->join( "," ); |
|
return QString(); |
|
} |
|
|
|
KMMessageTagList *KMMessage::tagList() const { return mTagList; } |
|
|
|
//----------------------------------------------------------------------------- |
|
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.indexOf( '>' ); |
|
if (rightAngle != -1) |
|
replyTo.truncate( rightAngle + 1 ); |
|
// now search the start of the message id |
|
leftAngle = replyTo.lastIndexOf( '<' ); |
|
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] == '<') && |
|
( !replyTo.contains( '"' ) ) ) |
|
return replyTo; |
|
|
|
references = headerField("References"); |
|
leftAngle = references.lastIndexOf( '<' ); |
|
if (leftAngle != -1) |
|
references = references.mid( leftAngle ); |
|
rightAngle = references.indexOf( '>' ); |
|
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.lastIndexOf( '<' ); |
|
leftAngle = references.lastIndexOf( '<', leftAngle - 1 ); |
|
if( leftAngle != -1 ) |
|
references = references.mid( leftAngle ); |
|
rightAngle = references.lastIndexOf( '>' ); |
|
if( rightAngle != -1 ) |
|
references.truncate( rightAngle + 1 ); |
|
|
|
if( !references.isEmpty() && references[0] == '<' ) |
|
return references; |
|
else |
|
return QString(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QString KMMessage::replyToAuxIdMD5() const |
|
{ |
|
QString result = references(); |
|
// references contains two items, use the first one |
|
// (the second to last reference) |
|
const int rightAngle = result.indexOf( '>' ); |
|
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.indexOf( '>' ); |
|
if (rightAngle != -1) |
|
msgId.truncate( rightAngle + 1 ); |
|
// now search the start of the message id |
|
const int leftAngle = msgId.lastIndexOf( '<' ); |
|
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", QByteArray::number((qlonglong)size)); |
|
mDirty = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
ulong KMMessage::UID() const { |
|
return headerField( "X-UID", NoEncoding ).toULong(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setUID(ulong uid) |
|
{ |
|
setHeaderField( "X-UID", QByteArray::number((qlonglong)uid), Unstructured, false, NoEncoding ); |
|
mDirty = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
AddressList KMMessage::headerAddrField( const QByteArray & aName ) const { |
|
return StringUtil::splitAddrField( rawHeaderField( aName ) ); |
|
} |
|
|
|
AddrSpecList KMMessage::extractAddrSpecs( const QByteArray & header ) const { |
|
AddressList al = headerAddrField( header ); |
|
AddrSpecList result; |
|
for ( AddressList::const_iterator ait = al.constBegin() ; ait != al.constEnd() ; ++ait ) |
|
for ( MailboxList::const_iterator mit = (*ait).mailboxList.constBegin() ; mit != (*ait).mailboxList.constEnd() ; ++mit ) |
|
result.push_back( (*mit).addrSpec() ); |
|
return result; |
|
} |
|
|
|
QByteArray KMMessage::rawHeaderField( const QByteArray &name ) const |
|
{ |
|
if ( !name.isEmpty() && mMsg ) { |
|
DwHeaders &header = mMsg->Headers(); |
|
DwField *field = header.FindField( name.constData() ); |
|
|
|
if ( !field ) { |
|
return QByteArray(); |
|
} |
|
|
|
return header.FieldBody( name.data() ).AsString().c_str(); |
|
} |
|
return QByteArray(); |
|
} |
|
|
|
QList<QByteArray> KMMessage::rawHeaderFields( const QByteArray& field ) const |
|
{ |
|
if ( field.isEmpty() || !mMsg->Headers().FindField( field ) ) |
|
return QList<QByteArray>(); |
|
|
|
std::vector<DwFieldBody*> v = mMsg->Headers().AllFieldBodies( field.data() ); |
|
QList<QByteArray> headerFields; |
|
for ( uint i = 0; i < v.size(); ++i ) { |
|
headerFields.append( v[i]->AsString().c_str() ); |
|
} |
|
|
|
return headerFields; |
|
} |
|
|
|
QString KMMessage::headerField( const QByteArray& aName, EncodingMode encodingMode ) const |
|
{ |
|
if ( aName.isEmpty() ) { |
|
return QString(); |
|
} |
|
|
|
if ( !mMsg->Headers().FindField( aName ) ) { |
|
return QString(); |
|
} |
|
|
|
const char *fieldValue = mMsg->Headers().FieldBody( aName.data() ).AsString().c_str(); |
|
if ( encodingMode == NoEncoding ) { |
|
return QString( fieldValue ); |
|
} |
|
else { |
|
return decodeRFC2047String( fieldValue, charset() ); |
|
} |
|
} |
|
|
|
QStringList KMMessage::headerFields( const QByteArray& 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 QByteArray& aName) |
|
{ |
|
DwHeaders & header = mMsg->Headers(); |
|
DwField * field = header.FindField(aName); |
|
if (!field) return; |
|
|
|
header.RemoveField(field); |
|
mNeedsAssembly = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::removeHeaderFields(const QByteArray& aName) |
|
{ |
|
DwHeaders & header = mMsg->Headers(); |
|
while ( DwField * field = header.FindField(aName) ) { |
|
header.RemoveField(field); |
|
mNeedsAssembly = true; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setHeaderField( const QByteArray& aName, const QString& bValue, |
|
HeaderFieldType type, bool prepend, EncodingMode encodingMode ) |
|
{ |
|
#if 0 |
|
if ( type != Unstructured ) |
|
kDebug() << "( \"" << aName <<"\", \"" << bValue << "\"," << type << ")"; |
|
#endif |
|
if (aName.isEmpty()) return; |
|
|
|
DwHeaders& header = mMsg->Headers(); |
|
|
|
DwString str; |
|
DwField* field; |
|
QByteArray aValue; |
|
if (!bValue.isEmpty()) |
|
{ |
|
QString value = bValue; |
|
if ( type == Address ) |
|
value = KPIMUtils::normalizeAddressesAndEncodeIdn( value ); |
|
#if 0 |
|
if ( type != Unstructured ) |
|
kDebug() << "value: \"" << value <<"\""; |
|
#endif |
|
if ( encodingMode == NoEncoding ) { |
|
aValue = value.toAscii(); |
|
Q_ASSERT( QString::fromAscii( aValue ) == bValue ); |
|
} |
|
else { |
|
QByteArray encoding = autoDetectCharset( charset(), s->prefCharsets, value ); |
|
if (encoding.isEmpty()) |
|
encoding = "utf-8"; |
|
aValue = encodeRFC2047String( value, encoding ); |
|
} |
|
#if 0 |
|
if ( type != Unstructured ) |
|
kDebug() << "aValue: \"" << aValue <<"\""; |
|
#endif |
|
} |
|
// FIXME PORTING |
|
str = (const char*)aName; |
|
if (str[str.length()-1] != ':') str += ": "; |
|
else str += ' '; |
|
if ( !aValue.isEmpty() ) |
|
str += (const char*)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; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray KMMessage::typeStr() const |
|
{ |
|
DwHeaders& header = mMsg->Headers(); |
|
if (header.HasContentType()) return header.ContentType().TypeStr().c_str(); |
|
else return ""; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
int KMMessage::type( DwEntity *entity ) const |
|
{ |
|
if ( !entity ) |
|
entity = mMsg; |
|
DwHeaders& header = entity->Headers(); |
|
if ( header.HasContentType() ) |
|
return header.ContentType().Type(); |
|
else return DwMime::kTypeNull; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setTypeStr(const QByteArray& aStr) |
|
{ |
|
dwContentType().SetTypeStr(DwString(aStr)); |
|
dwContentType().Parse(); |
|
mNeedsAssembly = true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setType(int aType) |
|
{ |
|
dwContentType().SetType(aType); |
|
dwContentType().Assemble(); |
|
mNeedsAssembly = true; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray 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 QByteArray& 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 QByteArray& attr, |
|
const QByteArray& 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 QByteArray& attr, const QByteArray& val) |
|
{ |
|
if (mNeedsAssembly) mMsg->Assemble(); |
|
mNeedsAssembly = false; |
|
setDwMediaTypeParam( dwContentType(), attr, val ); |
|
mNeedsAssembly = true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray KMMessage::contentTransferEncodingStr() const |
|
{ |
|
DwHeaders& header = mMsg->Headers(); |
|
if (header.HasContentTransferEncoding()) |
|
return header.ContentTransferEncoding().AsString().c_str(); |
|
else return ""; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
int KMMessage::contentTransferEncoding( DwEntity *entity ) const |
|
{ |
|
if ( !entity ) |
|
entity = mMsg; |
|
|
|
DwHeaders& header = entity->Headers(); |
|
if ( header.HasContentTransferEncoding() ) |
|
return header.ContentTransferEncoding().AsEnum(); |
|
else return DwMime::kCteNull; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setContentTransferEncodingStr( const QByteArray& cteString, |
|
DwEntity *entity ) |
|
{ |
|
if ( !entity ) |
|
entity = mMsg; |
|
|
|
entity->Headers().ContentTransferEncoding().FromString( cteString ); |
|
entity->Headers().ContentTransferEncoding().Parse(); |
|
mNeedsAssembly = true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setContentTransferEncoding( int cte, DwEntity *entity ) |
|
{ |
|
if ( !entity ) |
|
entity = mMsg; |
|
|
|
entity->Headers().ContentTransferEncoding().FromEnum( cte ); |
|
mNeedsAssembly = true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
DwHeaders& KMMessage::headers() const |
|
{ |
|
return mMsg->Headers(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setNeedsAssembly() |
|
{ |
|
mNeedsAssembly = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::assembleIfNeeded() |
|
{ |
|
Q_ASSERT( mMsg ); |
|
|
|
if ( mNeedsAssembly ) { |
|
mMsg->Assemble(); |
|
mNeedsAssembly = false; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray KMMessage::body() const |
|
{ |
|
DwString body = mMsg->Body().AsString(); |
|
QByteArray str = body.c_str(); |
|
if ( str.length() != static_cast<int>( body.length() ) ) { |
|
kWarning() << "Body is binary but used as text!"; |
|
} |
|
return str; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray KMMessage::bodyDecodedBinary() 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; |
|
} |
|
|
|
int len = dwstr.size(); |
|
QByteArray ba(len,'\0'); |
|
memcpy(ba.data(),dwstr.data(),len); |
|
return ba; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray 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; |
|
} |
|
|
|
QByteArray result = Util::ByteArray( dwstr ); |
|
return result; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setBodyAndGuessCte( const QByteArray& aBuf, |
|
QList<int> & allowedCte, |
|
bool allow8Bit, |
|
bool willBeSigned, |
|
DwEntity *entity ) |
|
{ |
|
if ( !entity ) |
|
entity = mMsg; |
|
|
|
CharFreq cf( aBuf ); // it's safe to pass null arrays |
|
allowedCte = StringUtil::determineAllowedCtes( cf, allow8Bit, willBeSigned ); |
|
setCte( allowedCte[0], entity ); // choose best fitting |
|
setBodyEncodedBinary( aBuf, entity ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setBodyEncoded(const QByteArray& aStr) |
|
{ |
|
// Qt4: QCString and QByteArray have been merged; this method can be cleaned up |
|
setBodyEncodedBinary( aStr ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setBodyEncodedBinary( const QByteArray& bodyStr, DwEntity *entity ) |
|
{ |
|
if ( !entity ) |
|
entity = mMsg; |
|
|
|
DwString dwSrc = Util::dwString( bodyStr ); |
|
DwString dwResult; |
|
|
|
switch ( cte( entity ) ) |
|
{ |
|
case DwMime::kCteBase64: |
|
DwEncodeBase64( dwSrc, dwResult ); |
|
break; |
|
case DwMime::kCteQuotedPrintable: |
|
DwEncodeQuotedPrintable( dwSrc, dwResult ); |
|
break; |
|
default: |
|
dwResult = dwSrc; |
|
break; |
|
} |
|
|
|
entity->Body().FromString( dwResult ); |
|
entity->Body().Parse(); |
|
|
|
mNeedsAssembly = true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setBody(const QByteArray& aStr) |
|
{ |
|
mMsg->Body().FromString(aStr.data()); |
|
mNeedsAssembly = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setMultiPartBody( const QByteArray & 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(); |
|
QList< 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.last(); |
|
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; |
|
QList< 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.last(); |
|
parts.removeLast(); |
|
} ; |
|
if (curpart) |
|
curpart = curpart->Next(); |
|
} |
|
return idx; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
DwBodyPart * KMMessage::dwBodyPart( int aIdx ) const |
|
{ |
|
DwBodyPart *part, *curpart; |
|
QList< 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.last(); |
|
parts.removeLast(); |
|
} |
|
if (curpart) |
|
curpart = curpart->Next(); |
|
} |
|
return part; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
DwBodyPart * KMMessage::findDwBodyPart( int type, int subtype ) const |
|
{ |
|
DwBodyPart *part, *curpart; |
|
QList< 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() ) { |
|
kDebug() << curpart->Headers().ContentType().TypeStr().c_str() |
|
<< " " << curpart->Headers().ContentType().SubtypeStr().c_str(); |
|
} |
|
|
|
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.last(); |
|
parts.removeLast(); |
|
} ; |
|
if (curpart) |
|
curpart = curpart->Next(); |
|
} |
|
} |
|
return part; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
DwBodyPart * KMMessage::findDwBodyPart( const QByteArray& type, const QByteArray& subtype ) const |
|
{ |
|
DwBodyPart *part, *curpart; |
|
QList< 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() ) { |
|
kDebug() << curpart->Headers().ContentType().TypeStr().c_str() |
|
<< " " << curpart->Headers().ContentType().SubtypeStr().c_str(); |
|
} |
|
|
|
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.last(); |
|
parts.removeLast(); |
|
} ; |
|
if (curpart) |
|
curpart = curpart->Next(); |
|
} |
|
} |
|
return part; |
|
} |
|
|
|
|
|
void applyHeadersToMessagePart( DwHeaders& headers, KMMessagePart* aPart ) |
|
{ |
|
// Content-type |
|
QByteArray 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(QByteArray(param->Value().c_str()).toLower()); |
|
else if (!qstrnicmp(param->Attribute().c_str(), "name*", 5)) |
|
aPart->setName(KMMsgBase::decodeRFC2231String( |
|
param->Value().c_str())); |
|
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().c_str() ); |
|
else |
|
aPart->setBody( "" ); |
|
|
|
// Content-id |
|
if ( headers.HasContentId() ) { |
|
const QByteArray 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(""); |
|
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", aIdx ) ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::deleteBodyParts() |
|
{ |
|
mMsg->Body().DeleteBodyParts(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool KMMessage::deleteBodyPart( int partIndex ) |
|
{ |
|
KMMessagePart part; |
|
DwBodyPart *dwpart = findPart( partIndex ); |
|
if ( !dwpart ) |
|
return false; |
|
KMMessage::bodyPart( dwpart, &part, true ); |
|
if ( !part.isComplete() ) |
|
return false; |
|
|
|
DwBody *parentNode = dynamic_cast<DwBody*>( dwpart->Parent() ); |
|
if ( !parentNode ) |
|
return false; |
|
parentNode->RemoveBodyPart( dwpart ); |
|
|
|
// add dummy part to show that a attachment has been deleted |
|
KMMessagePart dummyPart; |
|
dummyPart.duplicate( part ); |
|
QString comment = i18n("This attachment has been deleted."); |
|
if ( !part.fileName().isEmpty() ) |
|
comment = i18n( "The attachment '%1' has been deleted.", part.fileName() ); |
|
dummyPart.setContentDescription( comment ); |
|
dummyPart.setBodyEncodedBinary( QByteArray() ); |
|
QByteArray cd = dummyPart.contentDisposition(); |
|
if ( cd.toLower().indexOf( "inline" ) == 0 ) { |
|
cd.replace( 0, 10, "attachment" ); |
|
dummyPart.setContentDisposition( cd ); |
|
} else if ( cd.isEmpty() ) { |
|
dummyPart.setContentDisposition( "attachment" ); |
|
} |
|
DwBodyPart* newDwPart = createDWBodyPart( &dummyPart ); |
|
parentNode->AddBodyPart( newDwPart ); |
|
getTopLevelPart()->Assemble(); |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
DwBodyPart* KMMessage::createDWBodyPart(const KMMessagePart* aPart) |
|
{ |
|
DwBodyPart* part = DwBodyPart::NewBodyPart(s->emptyString, 0); |
|
|
|
if ( !aPart ) |
|
return part; |
|
|
|
QByteArray charset = aPart->charset(); |
|
QByteArray type = aPart->typeStr(); |
|
QByteArray subtype = aPart->subtypeStr(); |
|
QByteArray cte = aPart->cteStr(); |
|
QByteArray contDesc = aPart->contentDescriptionEncoded(); |
|
QByteArray contDisp = aPart->contentDisposition(); |
|
QByteArray contID = aPart->contentId(); |
|
QByteArray encoding = autoDetectCharset(charset, s->prefCharsets, aPart->name()); |
|
if (encoding.isEmpty()) encoding = "utf-8"; |
|
QByteArray name = KMMsgBase::encodeRFC2231String(aPart->name(), encoding); |
|
bool RFC2231encoded = aPart->name() != QString(name); |
|
QByteArray 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); |
|
} |
|
} |
|
|
|
QByteArray additionalParam = aPart->additionalCTypeParamStr(); |
|
if( !additionalParam.isEmpty() ) |
|
{ |
|
QByteArray parAV; |
|
DwString parA, parV; |
|
int iL, i1, i2, iM; |
|
iL = additionalParam.length(); |
|
i1 = 0; |
|
i2 = additionalParam.indexOf(';', i1); |
|
while ( i1 < iL ) |
|
{ |
|
if( -1 == i2 ) |
|
i2 = iL; |
|
if( i1+1 < i2 ) { |
|
parAV = additionalParam.mid( i1, (i2-i1) ); |
|
iM = parAV.indexOf('='); |
|
if( -1 < iM ) |
|
{ |
|
parA = parAV.left( iM ).data(); |
|
parV = parAV.right( parAV.length() - iM - 1 ).data(); |
|
if( ('"' == parV.at(0)) && ('"' == parV.at(parV.length()-1)) ) |
|
{ |
|
parV.erase( 0, 1); |
|
parV.erase( parV.length()-1 ); |
|
} |
|
} |
|
else |
|
{ |
|
parA = parAV.data(); |
|
parV = ""; |
|
} |
|
DwParameter *param; |
|
param = new DwParameter; |
|
param->SetAttribute( parA ); |
|
param->SetValue( parV ); |
|
ct.AddParameter( param ); |
|
} |
|
i1 = i2+1; |
|
i2 = additionalParam.indexOf( ';', i1 ); |
|
} |
|
} |
|
|
|
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()) |
|
{ |
|
QByteArray encoding = autoDetectCharset(charset, s->prefCharsets, |
|
aPart->parameterValue()); |
|
if (encoding.isEmpty()) encoding = "utf-8"; |
|
QByteArray 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 ); |
|
|
|
if ( !contID.isEmpty() ) |
|
headers.ContentId().FromString( contID ); |
|
|
|
if (!aPart->body().isNull()) |
|
part->Body().FromString(aPart->body()); |
|
else |
|
part->Body().FromString(""); |
|
|
|
if (!aPart->partSpecifier().isNull()) |
|
part->SetPartId( DwString(aPart->partSpecifier().toLatin1()) ); |
|
|
|
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 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::readConfig() |
|
{ |
|
KMMsgBase::readConfig(); |
|
|
|
KConfigGroup config( KMKernel::config(), "General" ); |
|
|
|
|
|
{ // area for config group "Composer" |
|
KConfigGroup config( KMKernel::config(), "Composer" ); |
|
s->smartQuote = GlobalSettings::self()->smartQuote(); |
|
s->wordWrap = GlobalSettings::self()->wordWrap(); |
|
s->wrapCol = GlobalSettings::self()->lineWrapWidth(); |
|
if ((s->wrapCol == 0) || (s->wrapCol > 78)) |
|
s->wrapCol = 78; |
|
if (s->wrapCol < 30) |
|
s->wrapCol = 30; |
|
|
|
// cberzan: moved to CodecManager |
|
s->prefCharsets = config.readEntry("pref-charsets", QStringList() ); |
|
} |
|
|
|
{ // area for config group "Reader" |
|
KConfigGroup config( KMKernel::config(), "Reader" ); |
|
s->headerStrategy = HeaderStrategy::create( config.readEntry( "header-set-displayed", "rich" ) ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray 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 QByteArray &charset, DwEntity *entity ) |
|
{ |
|
kWarning( type( entity ) != DwMime::kTypeText ) |
|
<< "Trying to set a charset for a non-textual mimetype." << endl |
|
<< "Fix this caller:" << endl |
|
<< "====================================================================" << endl |
|
<< kBacktrace( 5 ) << endl |
|
<< "===================================================================="; |
|
|
|
if ( !entity ) |
|
entity = mMsg; |
|
|
|
DwMediaType &mType = entity->Headers().ContentType(); |
|
mType.Parse(); |
|
DwParameter *param = mType.FirstParameter(); |
|
while( param ) { |
|
|
|
// FIXME use the mimelib functions here for comparison. |
|
if ( !kasciistricmp( param->Attribute().c_str(), "charset" ) ) |
|
break; |
|
|
|
param = param->Next(); |
|
} |
|
if ( !param ) { |
|
param = new DwParameter; |
|
param->SetAttribute( "charset" ); |
|
mType.AddParameter( param ); |
|
} |
|
else |
|
mType.SetModified(); |
|
|
|
QByteArray lowerCharset = charset; |
|
kAsciiToLower( lowerCharset.data() ); |
|
param->SetValue( DwString( lowerCharset ) ); |
|
mType.Assemble(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::setStatus(const MessageStatus& 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, const MessageStatus& aStatus ) |
|
{ |
|
Q_ASSERT( aStatus.isReplied() || aStatus.isForwarded() || aStatus.isDeleted() ); |
|
|
|
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.isReplied() ) |
|
type += "reply"; |
|
else if ( aStatus.isForwarded() ) |
|
type += "forward"; |
|
else if ( aStatus.isDeleted() ) |
|
type += "deleted"; |
|
|
|
setHeaderField( "X-KMail-Link-Message", message ); |
|
setHeaderField( "X-KMail-Link-Type", type ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::getLink(int n, ulong *retMsgSerNum, MessageStatus& retStatus) const |
|
{ |
|
*retMsgSerNum = 0; |
|
retStatus.clear(); |
|
|
|
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.setReplied(); |
|
else if ( type == "forward" ) |
|
retStatus.setForwarded(); |
|
else if ( type == "deleted" ) |
|
retStatus.setDeleted(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
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) |
|
{ |
|
DwString content( data.data(), data.size() ); |
|
if ( numBodyParts() > 0 && |
|
partSpecifier != "0" && |
|
partSpecifier != "TEXT" ) |
|
{ |
|
QString specifier = partSpecifier; |
|
if ( partSpecifier.endsWith( QLatin1String(".HEADER") ) || |
|
partSpecifier.endsWith( QLatin1String(".MIME") ) ) { |
|
// get the parent bodypart |
|
specifier = partSpecifier.section( '.', 0, -2 ); |
|
} |
|
|
|
// search for the bodypart |
|
mLastUpdated = findDwBodyPart( getFirstDwBodyPart(), specifier ); |
|
if (!mLastUpdated) |
|
{ |
|
kWarning() << "Can not find part" << specifier; |
|
return; |
|
} |
|
if ( partSpecifier.endsWith( QLatin1String(".MIME") ) ) |
|
{ |
|
// update headers |
|
// get rid of EOL |
|
content.resize( content.length()-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( QLatin1String(".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( QLatin1String(".HEADER") ) ) |
|
{ |
|
// notify observers |
|
notify(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void KMMessage::updateAttachmentState( DwBodyPart *partGiven ) |
|
{ |
|
DwEntity *part = partGiven; |
|
DwBodyPart *firstPart = partGiven; |
|
|
|
if ( !part ) { |
|
part = firstPart = getFirstDwBodyPart(); |
|
} |
|
|
|
if ( !part ) { |
|
part = mMsg; // no part, use message itself |
|
} |
|
|
|
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() && |
|
!( StringUtil::isCryptoPart( part->Headers().ContentType().TypeStr().c_str(), |
|
part->Headers().ContentType().SubtypeStr().c_str(), |
|
part->Headers().HasContentDisposition() ? |
|
part->Headers().ContentDisposition().Filename().c_str() : |
|
QString() ) ) ) ) { |
|
setStatus( MessageStatus::statusHasAttachment() ); |
|
} |
|
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 ( firstPart && firstPart->Next() ) { |
|
updateAttachmentState( firstPart->Next() ); |
|
} else if ( attachmentState() == KMMsgAttachmentUnknown && |
|
mStatus.hasAttachment() ) { |
|
toggleStatus( MessageStatus::statusHasAttachment() ); |
|
} |
|
} |
|
|
|
void KMMessage::setBodyFromUnicode( const QString &str, DwEntity *entity ) |
|
{ |
|
QByteArray encoding = |
|
KMMsgBase::autoDetectCharset( charset(), |
|
preferredCharsets(), str ); |
|
if ( encoding.isEmpty() ) { |
|
encoding = "utf-8"; |
|
} |
|
const QTextCodec * codec = KMMsgBase::codecForName( encoding ); |
|
assert( codec ); |
|
QList<int> dummy; |
|
setCharset( encoding, entity ); |
|
setBodyAndGuessCte( codec->fromUnicode( str ), dummy, false /* no 8bit */, |
|
false, entity ); |
|
} |
|
|
|
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().toLatin1() ); |
|
} |
|
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() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
QByteArray KMMessage::mboxMessageSeparator() |
|
{ |
|
QByteArray str( KPIMUtils::firstEmailAddress( rawHeaderField("From") ) ); |
|
if ( str.isEmpty() ) |
|
str = "unknown@unknown.invalid"; |
|
QByteArray 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() |
|
{ |
|
s->pendingDeletes << this; |
|
} |
|
|
|
QByteArray KMMessage::defaultCharset() |
|
{ |
|
// cberzan: wtf is this used??? |
|
QByteArray retval; |
|
|
|
if (!s->prefCharsets.isEmpty()) |
|
retval = s->prefCharsets[0].toLatin1(); |
|
|
|
if (retval.isEmpty() || (retval == "locale")) { |
|
retval = QByteArray(kmkernel->networkCodec()->name()); |
|
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 s->prefCharsets; |
|
} |
|
|
|
#ifndef NDEBUG |
|
void KMMessage::dump( DwEntity *entity, int level ) |
|
{ |
|
if ( !entity ) { |
|
entity = mMsg; |
|
entity->Assemble(); |
|
} |
|
|
|
QString spaces; |
|
for ( int i = 1; i <= level; i++ ) |
|
spaces += " "; |
|
|
|
kDebug() << QString( spaces + "Headers of entity " + entity->partId() + ':' ); |
|
kDebug() << QString( spaces + entity->Headers().AsString().c_str() ); |
|
kDebug() << QString( spaces + "Body of entity " + entity->partId() + ':' ); |
|
kDebug() << QString( spaces + entity->Body().AsString().c_str() ); |
|
|
|
DwBodyPart *current = entity->Body().FirstBodyPart(); |
|
while ( current ) { |
|
dump( current, level + 1 ); |
|
current = current->Next(); |
|
} |
|
} |
|
#endif |
|
|
|
DwBodyPart* KMMessage::findPart( int index ) |
|
{ |
|
int accu = 0; |
|
return findPartInternal( getTopLevelPart(), index, accu ); |
|
} |
|
|
|
DwBodyPart* KMMessage::findPartInternal(DwEntity * root, int index, int & accu) |
|
{ |
|
accu++; |
|
if ( index < accu ) // should not happen |
|
return 0; |
|
DwBodyPart *current = dynamic_cast<DwBodyPart*>( root ); |
|
if ( index == accu ) |
|
return current; |
|
DwBodyPart *rv = 0; |
|
if ( root->Body().FirstBodyPart() ) |
|
rv = findPartInternal( root->Body().FirstBodyPart(), index, accu ); |
|
if ( !rv && current && current->Next() ) |
|
rv = findPartInternal( current->Next(), index, accu ); |
|
if ( !rv && root->Body().Message() ) |
|
rv = findPartInternal( root->Body().Message(), index, accu ); |
|
return rv; |
|
}
|
|
|